Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add compile py command #441

Merged
merged 40 commits into from
Mar 11, 2024
Merged
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d8813d1
feat: add compile py command
PatrickDinh Feb 28, 2024
2b94a38
chore: fix PuyaPy casing
PatrickDinh Feb 28, 2024
cd966fa
chore: draft docs
PatrickDinh Feb 28, 2024
ddf4d5f
chore: generate docs
PatrickDinh Feb 28, 2024
a6ca216
chore: test and document
PatrickDinh Feb 28, 2024
7120d1e
chore: docs
PatrickDinh Feb 28, 2024
3ce4a79
chore: improve detect puyapy version
PatrickDinh Feb 29, 2024
e6a7780
chore: docs
PatrickDinh Feb 29, 2024
c14ed79
chore: fix test
PatrickDinh Feb 29, 2024
5af9e0b
chore: clean up
PatrickDinh Feb 29, 2024
1641ff1
chore: bug and docs
PatrickDinh Feb 29, 2024
b1fb1b8
chore: code comment
PatrickDinh Feb 29, 2024
01f0015
chore: first test
PatrickDinh Feb 29, 2024
61ac65b
chore: fix tests
PatrickDinh Feb 29, 2024
c60fa7d
chore: fix tests
PatrickDinh Feb 29, 2024
24470b6
chore: fix tests
PatrickDinh Feb 29, 2024
71c3bd0
chore: tests
PatrickDinh Feb 29, 2024
6d8dde4
chore: test
PatrickDinh Feb 29, 2024
e183619
chore: turns out I don't need this
PatrickDinh Feb 29, 2024
50d9ef2
chore: move --version flag to the group
PatrickDinh Mar 1, 2024
b4f5d59
chore: fix the tests
PatrickDinh Mar 1, 2024
a5c40df
Merge remote-tracking branch 'origin/main' into integrate-puya
PatrickDinh Mar 3, 2024
ace7e4a
chore: disable compile_group command
PatrickDinh Mar 3, 2024
fab3315
chore: add the compile group back, hidden from users
PatrickDinh Mar 4, 2024
2f4ad48
chore: delete doc
PatrickDinh Mar 4, 2024
3f043d9
chore: pr feedback
PatrickDinh Mar 4, 2024
78a1b87
chore: install puya during CI
PatrickDinh Mar 4, 2024
b350faa
Don't check for PuyaPy outputs
PatrickDinh Mar 4, 2024
550465d
chore: only run puyapy tests for python 3.12
PatrickDinh Mar 5, 2024
2e1e02b
chore: oops
PatrickDinh Mar 5, 2024
6386367
chore: run tests with --no-color flag
PatrickDinh Mar 5, 2024
1da08b1
chore: fix tests
PatrickDinh Mar 5, 2024
3d9ed2b
chore: only skip the e2e tests
PatrickDinh Mar 5, 2024
28085ec
chore: address PR feedback
PatrickDinh Mar 6, 2024
f067ede
Merge remote-tracking branch 'origin/main' into integrate-puya
PatrickDinh Mar 6, 2024
6043f6f
chore: support "compile python" and "compile py"
PatrickDinh Mar 11, 2024
0960710
Merge remote-tracking branch 'origin/main' into integrate-puya
PatrickDinh Mar 11, 2024
636384e
chore: show all sub commands
PatrickDinh Mar 11, 2024
5ae5fc4
chore: PR feedback
PatrickDinh Mar 11, 2024
51a79ab
chore: clean up
PatrickDinh Mar 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: pr feedback
  • Loading branch information
PatrickDinh committed Mar 4, 2024
commit 3f043d94792b4d7fad1bbc20e11bc51830fae914
5 changes: 5 additions & 0 deletions src/algokit/cli/compile.py
Original file line number Diff line number Diff line change
@@ -58,4 +58,9 @@ def compile_py_command(context: click.Context, puya_args: list[str]) -> None:
click.echo(run_result.output)

if run_result.exit_code != 0:
click.secho(
"An error occurred during compile. Ensure supplied files are valid PuyaPy code before retrying.",
err=True,
fg="red",
)
raise click.exceptions.Exit(run_result.exit_code)
27 changes: 27 additions & 0 deletions tests/compile/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
VALID_PUYA_CONTRACT_FILE_CONTENT = """
from puyapy import Contract, Txn, log


class HelloWorldContract(Contract):
def approval_program(self) -> bool:
name = Txn.application_args(0)
log(b"Hello, " + name)
return True

def clear_state_program(self) -> bool:
return True
"""

INVALID_PUYA_CONTRACT_FILE_CONTENT = """
from puyapy import Contract, Txn, log


class HelloWorldContract(Contract):
def approval_program(self) -> bool:
name = Txn.application_args_invalid(0)
log(b"Hello, " + name)
return True

def clear_state_program(self) -> bool:
return True
"""
68 changes: 53 additions & 15 deletions tests/compile/test_py.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import os
from pathlib import Path

import pytest
from approvaltests.namer import NamerFactory
from pytest_mock import MockerFixture

from tests.utils.approvals import verify
from tests.compile.conftest import INVALID_PUYA_CONTRACT_FILE_CONTENT, VALID_PUYA_CONTRACT_FILE_CONTENT
from tests.utils.approvals import normalize_path, verify
from tests.utils.click_invoker import invoke
from tests.utils.proc_mock import ProcMock

@@ -13,8 +16,18 @@ def _normalize_path(path: Path) -> str:


@pytest.fixture()
def hello_world_contract_path() -> Path:
return Path(__file__).parent / "hello_world_contract.py"
def dummy_contract_path() -> Path:
return Path(__file__).parent / "dummy_contract.py"


@pytest.fixture(autouse=True)
def cwd(tmp_path_factory: pytest.TempPathFactory) -> Path:
return tmp_path_factory.mktemp("cwd", numbered=True)


@pytest.fixture()
def output_path(cwd: Path) -> Path:
return cwd / "output"


def test_compile_py_help(mocker: MockerFixture) -> None:
@@ -28,25 +41,25 @@ def test_compile_py_help(mocker: MockerFixture) -> None:
verify(result.output)


def test_puyapy_is_not_installed_anywhere(hello_world_contract_path: Path, mocker: MockerFixture) -> None:
def test_puyapy_is_not_installed_anywhere(dummy_contract_path: Path, mocker: MockerFixture) -> None:
proc_mock = ProcMock()
proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "-h"], exit_code=1, output=["Puyapy not found"])
proc_mock.should_bad_exit_on(["puyapy", "-h"], exit_code=1, output=["Puyapy not found"])

proc_mock.set_output(["pipx", "--version"], ["1.0.0"])

proc_mock.set_output(["pipx", "install", "puya"], ["Puyapy is installed"])
proc_mock.set_output(["puyapy", str(hello_world_contract_path)], ["Done"])
proc_mock.set_output(["puyapy", str(dummy_contract_path)], ["Done"])

mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen

result = invoke(f"compile py {_normalize_path(hello_world_contract_path)}")
result = invoke(f"compile py {_normalize_path(dummy_contract_path)}")

assert result.exit_code == 0
verify(result.output)


def test_specificed_puyapy_version_is_not_installed(hello_world_contract_path: Path, mocker: MockerFixture) -> None:
def test_specificed_puyapy_version_is_not_installed(dummy_contract_path: Path, mocker: MockerFixture) -> None:
current_version = "1.0.0"
target_version = "1.1.0"

@@ -55,39 +68,64 @@ def test_specificed_puyapy_version_is_not_installed(hello_world_contract_path: P
proc_mock.should_bad_exit_on(["puyapy", "--version"], exit_code=1, output=["Puyapy not found"])

proc_mock.set_output(["pipx", "--version"], ["1.0.0"])
proc_mock.set_output(["pipx", "run", f"puya=={target_version}", str(hello_world_contract_path)], ["Done"])
proc_mock.set_output(["pipx", "run", f"puya=={target_version}", str(dummy_contract_path)], ["Done"])

mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen

result = invoke(f"compile --version {target_version} py {_normalize_path(hello_world_contract_path)}")
result = invoke(f"compile --version {target_version} py {_normalize_path(dummy_contract_path)}")

assert result.exit_code == 0
verify(result.output)


def test_puyapy_is_installed_in_project(hello_world_contract_path: Path, mocker: MockerFixture) -> None:
def test_puyapy_is_installed_in_project(dummy_contract_path: Path, mocker: MockerFixture) -> None:
proc_mock = ProcMock()
proc_mock.set_output(["poetry", "run", "puyapy", "-h"], output=["Puyapy help"])
proc_mock.set_output(["poetry", "run", "puyapy", str(hello_world_contract_path)], ["Done"])
proc_mock.set_output(["poetry", "run", "puyapy", str(dummy_contract_path)], ["Done"])

mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen

result = invoke(f"compile py {_normalize_path(hello_world_contract_path)}")
result = invoke(f"compile py {_normalize_path(dummy_contract_path)}")

assert result.exit_code == 0
verify(result.output)


def test_puyapy_is_installed_globally(hello_world_contract_path: Path, mocker: MockerFixture) -> None:
def test_puyapy_is_installed_globally(dummy_contract_path: Path, mocker: MockerFixture) -> None:
proc_mock = ProcMock()
proc_mock.should_bad_exit_on(["poetry", "run", "puyapy", "-h"], exit_code=1, output=["Puyapy not found"])

proc_mock.set_output(["puyapy", "-h"], output=["Puyapy help"])
proc_mock.set_output(["puyapy", str(hello_world_contract_path)], ["Done"])
proc_mock.set_output(["puyapy", str(dummy_contract_path)], ["Done"])

mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen

result = invoke(f"compile py {_normalize_path(hello_world_contract_path)}")
result = invoke(f"compile py {_normalize_path(dummy_contract_path)}")

assert result.exit_code == 0
verify(result.output)


def test_valid_contract(cwd: Path, output_path: Path) -> None:
contract_path = cwd / "contract.py"
contract_path.write_text(VALID_PUYA_CONTRACT_FILE_CONTENT)
result = invoke(f"compile py {_normalize_path(contract_path)} --out-dir {_normalize_path(output_path)}")

assert result.exit_code == 0

for d, __, files in os.walk(output_path):
for file in files:
content = (d / Path(file)).read_text()
normalize_content = normalize_path(content, str(cwd), "{temp_output_directory}")
verify(normalize_content, options=NamerFactory.with_parameters(file))


def test_invalid_contract(cwd: Path, output_path: Path) -> None:
contract_path = cwd / "contract.py"
contract_path.write_text(INVALID_PUYA_CONTRACT_FILE_CONTENT)
result = invoke(f"compile py {_normalize_path(contract_path)} --out-dir {_normalize_path(output_path)}")

assert result.exit_code == 1

normalize_output = normalize_path(result.output, str(cwd), "{temp_output_directory}")
verify(normalize_output)
67 changes: 67 additions & 0 deletions tests/compile/test_py.test_invalid_contract.approved.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}'
DEBUG: poetry: usage: puya [-h] [-O {0,1,2}] [--output-teal | --no-output-teal]
DEBUG: poetry: [--output-arc32 | --no-output-arc32] [--out-dir OUT_DIR]
DEBUG: poetry: [--log-level {notset,debug,info,warning,error,fatal}] [-g {0,1,2}]
DEBUG: poetry: [--output-awst | --no-output-awst]
DEBUG: poetry: [--output-ssa-ir | --no-output-ssa-ir]
DEBUG: poetry: [--output-optimization-ir | --no-output-optimization-ir]
DEBUG: poetry: [--output-destructured-ir | --no-output-destructured-ir]
DEBUG: poetry: [--output-memory-ir | --no-output-memory-ir]
DEBUG: poetry: [--target-avm-version {10}]
DEBUG: poetry: [--locals-coalescing-strategy {root_operand,root_operand_excluding_args,aggressive}]
DEBUG: poetry: PATH [PATH ...]
DEBUG: poetry:
DEBUG: poetry: positional arguments:
DEBUG: poetry: PATH
DEBUG: poetry:
DEBUG: poetry: options:
DEBUG: poetry: -h, --help show this help message and exit
DEBUG: poetry: -O {0,1,2}, --optimization-level {0,1,2}
DEBUG: poetry: set optimization level (default: 1)
DEBUG: poetry: --output-teal, --no-output-teal
DEBUG: poetry: Output TEAL (default: True)
DEBUG: poetry: --output-arc32, --no-output-arc32
DEBUG: poetry: Output ARC32 application.json (default: True)
DEBUG: poetry: --out-dir OUT_DIR path for outputting artefacts (default: False)
DEBUG: poetry: --log-level {notset,debug,info,warning,error,fatal}
DEBUG: poetry: Minimum level to log to console (default: info)
DEBUG: poetry: -g {0,1,2}, --debug-level {0,1,2}
DEBUG: poetry: debug information level (default: 1)
DEBUG: poetry: --output-awst, --no-output-awst
DEBUG: poetry: output parsed result of AST (default: False)
DEBUG: poetry: --output-ssa-ir, --no-output-ssa-ir
DEBUG: poetry: output IR in SSA form (default: False)
DEBUG: poetry: --output-optimization-ir, --no-output-optimization-ir
DEBUG: poetry: output IR after each optimization (default: False)
DEBUG: poetry: --output-destructured-ir, --no-output-destructured-ir
DEBUG: poetry: output IR after SSA destructuring and before codegen
DEBUG: poetry: (default: False)
DEBUG: poetry: --output-memory-ir, --no-output-memory-ir
DEBUG: poetry: output MIR before lowering to TealOps (default: False)
DEBUG: poetry: --target-avm-version {10}
DEBUG: poetry: --locals-coalescing-strategy {root_operand,root_operand_excluding_args,aggressive}
DEBUG: poetry: Strategy choice for out-of-ssa local variable
DEBUG: poetry: coalescing. The best choice for your app is best
DEBUG: poetry: determined through experimentation (default:
DEBUG: poetry: root_operand)
DEBUG: Running 'poetry run puyapy {temp_output_directory}/contract.py --out-dir {temp_output_directory}/output' in '{current_working_directory}'
DEBUG: poetry: {temp_output_directory}/contract.py:7 error: "type[Txn]" has no attribute "application_args_invalid" [attr-defined]
DEBUG: poetry: name = Txn.application_args_invalid(0)
DEBUG: poetry: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
DEBUG: poetry: {temp_output_directory}/contract.py:7 error: Expression has type "Any" [misc]
DEBUG: poetry: name = Txn.application_args_invalid(0)
DEBUG: poetry: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DEBUG: poetry: {temp_output_directory}/contract.py:8 error: Expression has type "Any" [misc]
DEBUG: poetry: log(b"Hello, " + name)
DEBUG: poetry: ^~~~~~~~~~~~~~~~~
{temp_output_directory}/contract.py:7 error: "type[Txn]" has no attribute "application_args_invalid" [attr-defined]
name = Txn.application_args_invalid(0)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
{temp_output_directory}/contract.py:7 error: Expression has type "Any" [misc]
name = Txn.application_args_invalid(0)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{temp_output_directory}/contract.py:8 error: Expression has type "Any" [misc]
log(b"Hello, " + name)
^~~~~~~~~~~~~~~~~

An error occurred during compile. Ensure supplied files are valid PuyaPy code before retrying.
Empty file.
Original file line number Diff line number Diff line change
@@ -2,6 +2,6 @@ DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}'
DEBUG: poetry: Puyapy not found
DEBUG: Running 'puyapy -h' in '{current_working_directory}'
DEBUG: puyapy: Puyapy help
DEBUG: Running 'puyapy {current_working_directory}/tests/compile/hello_world_contract.py' in '{current_working_directory}'
DEBUG: Running 'puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}'
DEBUG: puyapy: Done
Done
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}'
DEBUG: poetry: Puyapy help
DEBUG: Running 'poetry run puyapy {current_working_directory}/tests/compile/hello_world_contract.py' in '{current_working_directory}'
DEBUG: Running 'poetry run puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}'
DEBUG: poetry: Done
Done
Original file line number Diff line number Diff line change
@@ -6,6 +6,6 @@ DEBUG: Running 'pipx --version' in '{current_working_directory}'
DEBUG: pipx: 1.0.0
DEBUG: Running 'pipx install puya' in '{current_working_directory}'
DEBUG: pipx: Puyapy is installed
DEBUG: Running 'puyapy {current_working_directory}/tests/compile/hello_world_contract.py' in '{current_working_directory}'
DEBUG: Running 'puyapy {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}'
DEBUG: puyapy: Done
Done
Original file line number Diff line number Diff line change
@@ -4,6 +4,6 @@ DEBUG: Running 'puyapy --version' in '{current_working_directory}'
DEBUG: puyapy: Puyapy not found
DEBUG: Running 'pipx --version' in '{current_working_directory}'
DEBUG: pipx: 1.0.0
DEBUG: Running 'pipx run puya==1.1.0 {current_working_directory}/tests/compile/hello_world_contract.py' in '{current_working_directory}'
DEBUG: Running 'pipx run puya==1.1.0 {current_working_directory}/tests/compile/dummy_contract.py' in '{current_working_directory}'
DEBUG: pipx: Done
Done
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma version 10

contract.HelloWorldContract.approval_program:
// {temp_output_directory}/contract.py:8
// log(b"Hello, " + name)
byte "Hello, "
// {temp_output_directory}/contract.py:7
// name = Txn.application_args(0)
txna ApplicationArgs 0
// {temp_output_directory}/contract.py:8
// log(b"Hello, " + name)
concat
log
// {temp_output_directory}/contract.py:9
// return True
int 1
return
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma version 10

contract.HelloWorldContract.clear_state_program:
// {temp_output_directory}/contract.py:12
// return True
int 1
return
Loading