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
Show file tree
Hide file tree
Changes from 25 commits
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
2 changes: 2 additions & 0 deletions src/algokit/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import click

from algokit.cli.bootstrap import bootstrap_group
from algokit.cli.compile import compile_group
from algokit.cli.completions import completions_group
from algokit.cli.config import config_group
from algokit.cli.deploy import deploy_command
Expand Down Expand Up @@ -49,3 +50,4 @@ def algokit(*, skip_version_check: bool) -> None:
algokit.add_command(deploy_command)
algokit.add_command(dispenser_group)
algokit.add_command(task_group)
algokit.add_command(compile_group)
61 changes: 61 additions & 0 deletions src/algokit/cli/compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import logging

import click

from algokit.core.compile import find_valid_puyapy_command
from algokit.core.proc import run

logger = logging.getLogger(__name__)


@click.group("compile", hidden=True)
@click.option(
"-v",
"--version",
"version",
required=False,
default=None,
help=(
"Compiler version, for example, 1.0.0. "
"If the version isn't specified, AlgoKit will check if the compiler is installed locally, and execute that. "
"If the compiler is not found, it will install the latest version. "
"If the version is specified, AlgoKit will check if the local compiler's version satisfies, and execute that. "
"Otherwise, AlgoKit will install the specifed compiler version."
),
)
@click.pass_context
def compile_group(context: click.Context, version: str | None) -> None:
"""Compile high level language smart contracts to TEAL"""
context.ensure_object(dict)
context.obj["version"] = version


@compile_group.command(
"py",
short_help="Compile Python to TEAL with PuyaPy",
help="Compile Python to TEAL with PuyaPy, review https://github.com/algorandfoundation/puya for usage",
context_settings={
"ignore_unknown_options": True,
},
add_help_option=False,
)
@click.argument("puya_args", nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def compile_py_command(context: click.Context, puya_args: list[str]) -> None:
"""
Compile Python contract(s) to TEAL with PuyaPy
"""
version = str(context.obj["version"]) if context.obj["version"] else None

puya_command = find_valid_puyapy_command(version)

run_result = run(
[
*puya_command,
*puya_args,
],
)
click.echo(run_result.output)

if run_result.exit_code != 0:
raise click.exceptions.Exit(run_result.exit_code)
aorumbayev marked this conversation as resolved.
Show resolved Hide resolved
80 changes: 80 additions & 0 deletions src/algokit/core/compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from collections.abc import Iterator
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved

from algokit.core.proc import run
from algokit.core.utils import extract_version_triple, find_valid_pipx_command


def find_valid_puyapy_command(version: str | None) -> list[str]:
return _find_puya_command_at_version(version) if version is not None else _find_puya_command()


def _find_puya_command_at_version(version: str) -> list[str]:
"""
Find puya command with a specific version.
If the puya version isn't installed, install it with pipx run.
"""
for puyapy_command in _get_candidates_puyapy_commands():
try:
puyapy_version_result = run([*puyapy_command, "--version"])
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved
except OSError:
pass # in case of path/permission issues, go to next candidate
else:
if puyapy_version_result.exit_code == 0 and (
extract_version_triple(version) == extract_version_triple(puyapy_version_result.output)
):
return puyapy_command

pipx_command = find_valid_pipx_command(
"Unable to find pipx install so that `PuyaPy` compiler can be installed; "
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved
"please install pipx via https://pypa.github.io/pipx/ "
"and then try `algokit compile py ...` again."
)

return [
*pipx_command,
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved
"run",
f"puya=={version}",
]


def _find_puya_command() -> list[str]:
"""
Find puya command.
If puya isn't installed, install the latest version with pipx.
"""
for puyapy_command in _get_candidates_puyapy_commands():
try:
puyapy_help_result = run([*puyapy_command, "-h"])
except OSError:
pass # in case of path/permission issues, go to next candidate
else:
if puyapy_help_result.exit_code == 0:
return puyapy_command

pipx_command = find_valid_pipx_command(
"Unable to find pipx install so that `PuyaPy` compiler can be installed; "
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved
"please install pipx via https://pypa.github.io/pipx/ "
"and then try `algokit compile py ...` again."
)
_install_puyapy_with_pipx(pipx_command)
return ["puyapy"]


def _install_puyapy_with_pipx(pipx_command: list[str]) -> None:
run(
[
*pipx_command,
"install",
"puya",
],
bad_return_code_error_message=(
"Unable to install puya via pipx; please install puya manually and try `algokit compile py ...` again."
),
)


def _get_candidates_puyapy_commands() -> Iterator[list[str]]:
# when puya is installed at the project level
yield ["poetry", "run", "puyapy"]
# when puya is installed at the global level
yield ["puyapy"]
Empty file added tests/compile/__init__.py
Empty file.
93 changes: 93 additions & 0 deletions tests/compile/test_py.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from pathlib import Path

import pytest
from pytest_mock import MockerFixture

from tests.utils.approvals import verify
from tests.utils.click_invoker import invoke
from tests.utils.proc_mock import ProcMock


def _normalize_path(path: Path) -> str:
return str(path.absolute()).replace("\\", r"\\")


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


def test_compile_py_help(mocker: MockerFixture) -> None:
proc_mock = ProcMock()
proc_mock.set_output(["poetry", "run", "puyapy", "-h"], output=["Puyapy help"])

mocker.patch("algokit.core.proc.Popen").side_effect = proc_mock.popen
result = invoke("compile py -h")

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


def test_puyapy_is_not_installed_anywhere(hello_world_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"])

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

result = invoke(f"compile py {_normalize_path(hello_world_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:
current_version = "1.0.0"
target_version = "1.1.0"

proc_mock = ProcMock()
proc_mock.set_output(["poetry", "run", "puyapy", "--version"], output=[current_version])
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"])

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)}")

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


def test_puyapy_is_installed_in_project(hello_world_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"])

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

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

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


def test_puyapy_is_installed_globally(hello_world_contract_path: Path, mocker: MockerFixture) -> None:
PatrickDinh marked this conversation as resolved.
Show resolved Hide resolved
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"])

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

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

assert result.exit_code == 0
verify(result.output)
5 changes: 5 additions & 0 deletions tests/compile/test_py.test_compile_py_help.approved.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}'
DEBUG: poetry: Puyapy help
DEBUG: Running 'poetry run puyapy -h' in '{current_working_directory}'
DEBUG: poetry: Puyapy help
Puyapy help
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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: puyapy: Done
Done
Original file line number Diff line number Diff line change
@@ -0,0 +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: poetry: Done
Done
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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 not found
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: puyapy: Done
Done
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DEBUG: Running 'poetry run puyapy --version' in '{current_working_directory}'
DEBUG: poetry: 1.0.0
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: pipx: Done
Done