Skip to content

Commit

Permalink
feat: auto-install dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrickAlphaC committed Feb 22, 2025
1 parent fa0c8ae commit 9c7eb0f
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 20 deletions.
29 changes: 26 additions & 3 deletions moccasin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ def main(argv: list) -> int:
return 0


def generate_main_parser_and_sub_parsers() -> Tuple[
argparse.ArgumentParser, argparse.Action
]:
def generate_main_parser_and_sub_parsers() -> (
Tuple[argparse.ArgumentParser, argparse.Action]
):
parent_parser = create_parent_parser()
main_parser = argparse.ArgumentParser(
prog="Moccasin CLI",
Expand Down Expand Up @@ -140,6 +140,12 @@ def generate_main_parser_and_sub_parsers() -> Tuple[
help="Optional argument to compile a specific contract.",
)

compile_parser.add_argument(
"--no-install",
help="Do not install the requirements before compiling.",
action="store_true",
)

zksync_ground = compile_parser.add_mutually_exclusive_group()
zksync_ground.add_argument(
"--network", help=f"Alias of the network (from the {CONFIG_NAME})."
Expand All @@ -162,6 +168,11 @@ def generate_main_parser_and_sub_parsers() -> Tuple[
type=str,
nargs="?",
)
test_parser.add_argument(
"--no-install",
help="Do not install the requirements before running the tests.",
action="store_true",
)
add_network_args_to_parser(test_parser)
add_account_args_to_parser(test_parser)

Expand Down Expand Up @@ -322,6 +333,12 @@ def generate_main_parser_and_sub_parsers() -> Tuple[
type=str,
default="./script/deploy.py",
)
run_parser.add_argument(
"--no-install",
help="Do not install the requirements before running the script.",
action="store_true",
)

add_network_args_to_parser(run_parser)
add_account_args_to_parser(run_parser)

Expand All @@ -339,6 +356,12 @@ def generate_main_parser_and_sub_parsers() -> Tuple[
help=f"Name of your named contract in your {CONFIG_NAME} to deploy.",
type=str,
)
deploy_parser.add_argument(
"--no-install",
help="Do not install the requirements before deploying.",
action="store_true",
)

add_network_args_to_parser(deploy_parser)
add_account_args_to_parser(deploy_parser)

Expand Down
7 changes: 6 additions & 1 deletion moccasin/commands/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from vyper.exceptions import VersionException, _BaseVyperException

from moccasin._sys_path_and_config_setup import _patch_sys_path, get_sys_paths_list
from moccasin.commands.install import mox_install
from moccasin.config import Config, get_config, initialize_global_config
from moccasin.constants.vars import (
BUILD_FOLDER,
Expand All @@ -23,7 +24,7 @@
IS_WINDOWS,
MOCCASIN_GITHUB,
)
from moccasin.logging import logger
from moccasin.logging import logger, set_log_level


def main(args: Namespace) -> int:
Expand All @@ -32,6 +33,10 @@ def main(args: Namespace) -> int:

is_zksync: bool = _set_zksync_test_env_if_applicable(args, config)

if not args.no_install:
mox_install(config=config, quiet=True, override_logger=True)
set_log_level(quiet=args.quiet, debug=args.debug)

with _patch_sys_path(get_sys_paths_list(config)):
if args.contract_or_contract_path:
contract_path = config.find_contract(args.contract_or_contract_path)
Expand Down
7 changes: 6 additions & 1 deletion moccasin/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
_setup_network_and_account_from_config_and_cli,
get_sys_paths_list,
)
from moccasin.commands.install import mox_install
from moccasin.config import get_config, initialize_global_config
from moccasin.logging import logger
from moccasin.logging import logger, set_log_level


def main(args: Namespace) -> int:
config = initialize_global_config()

if not args.no_install:
mox_install(config=config, quiet=True, override_logger=True)
set_log_level(quiet=args.quiet, debug=args.debug)

# Set up the environment (add necessary paths to sys.path, etc.)
with _patch_sys_path(get_sys_paths_list(config)):
_setup_network_and_account_from_config_and_cli(
Expand Down
105 changes: 94 additions & 11 deletions moccasin/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import requests # type: ignore
import tomli_w
from packaging.requirements import Requirement
from packaging.version import parse as parse_version
from tqdm import tqdm

from moccasin._dependency_utils import (
Expand All @@ -23,18 +25,45 @@
)
from moccasin.config import get_or_initialize_config
from moccasin.constants.vars import GITHUB, PACKAGE_VERSION_FILE, PYPI, REQUEST_HEADERS
from moccasin.logging import logger
from moccasin.logging import logger, set_log_level


def main(args: Namespace):
requirements = args.requirements
config = get_or_initialize_config()
requirements = args.requirements if hasattr(args, "requirements") else []
no_install = args.no_install if hasattr(args, "no_install") else False
quiet = args.quiet if hasattr(args, "quiet") else False
debug = args.debug if hasattr(args, "debug") else False
return mox_install(
requirements=requirements,
no_install=no_install,
quiet=quiet,
debug=debug,
override_logger=False,
)


def mox_install(
requirements=[],
no_install=None,
config=None,
quiet=False,
debug=False,
override_logger=False,
):
"""@dev IMPORTANT, this function can override the logger level, it's good to
reset it after calling this function.
"""
if quiet:
set_log_level(quiet=quiet, debug=debug)
if no_install:
return 0
if config is None:
config = get_or_initialize_config()
if len(requirements) == 0:
requirements = config.get_dependencies()
if len(requirements) == 0:
logger.info("No dependencies to install.")
return 0

pip_requirements = []
github_requirements = []
for requirement in requirements:
Expand All @@ -44,16 +73,23 @@ def main(args: Namespace):
pip_requirements.append(requirement)
install_path: Path = config.get_base_dependencies_install_path()
if len(pip_requirements) > 0:
_pip_installs(pip_requirements, install_path.joinpath(PYPI), args.quiet)
_pip_installs(
pip_requirements, install_path.joinpath(PYPI), quiet, override_logger
)
if len(github_requirements) > 0:
_github_installs(github_requirements, install_path.joinpath(GITHUB), args.quiet)
_github_installs(
github_requirements, install_path.joinpath(GITHUB), quiet, override_logger
)
return 0


# Much of this code thanks to brownie
# https://github.com/eth-brownie/brownie/blob/master/brownie/_config.py
def _github_installs(
github_ids: list[str], base_install_path: Path, quiet: bool = False
github_ids: list[str],
base_install_path: Path,
quiet: bool = False,
override_logger=False,
):
logger.info(f"Installing {len(github_ids)} GitHub packages...")
for package_id in github_ids:
Expand Down Expand Up @@ -91,8 +127,10 @@ def _github_installs(
installed_version = versions.get(f"{org}/{repo}", None)
if installed_version == version:
logger.info(f"{org}/{repo} already installed at version {version}")
return f"{org}/{repo}@{version}"
continue
else:
if override_logger:
set_log_level(quiet=False)
logger.info(
f"Updating {org}/{repo} from {installed_version} to {version}"
)
Expand Down Expand Up @@ -215,10 +253,50 @@ def _get_download_url_from_tag(org: str, repo: str, version: str, headers: dict)
)


def _pip_installs(package_ids: list[str], base_install_path: Path, quiet: bool = False):
def _pip_installs(
package_ids: list[str],
base_install_path: Path,
quiet: bool = False,
override_logger=False,
):
logger.info(f"Installing {len(package_ids)} pip packages...")
cmd = []
cmd = ["uv", "pip", "install", *package_ids, "--target", str(base_install_path)]

# Check if they are already installed on the right version
packages_to_install = []
for package_id in package_ids:
name, version_spec = parse_package_req(package_id)

if base_install_path.joinpath(name).exists():
dist_info = next(base_install_path.glob(f"{name}-*.dist-info"))
installed_version = dist_info.name.replace(f"{name}-", "").replace(
".dist-info", ""
)

if version_spec:
if not version_spec.contains(parse_version(installed_version)):
logger.info(
f"{name} {installed_version} installed but {package_id} required."
)
packages_to_install.append(package_id)
continue
else:
packages_to_install.append(package_id)

if len(packages_to_install) == 0:
logger.info("All packages already installed.")
return 0

if override_logger:
set_log_level(quiet=False)

cmd = [
"uv",
"pip",
"install",
*packages_to_install,
"--target",
str(base_install_path),
]

capture_output = quiet
try:
Expand All @@ -230,3 +308,8 @@ def _pip_installs(package_ids: list[str], base_install_path: Path, quiet: bool =
sys.exit(1)

_write_new_dependencies(package_ids, DependencyType.PIP)


def parse_package_req(package_id):
req = Requirement(package_id)
return req.name, next(iter(req.specifier)) if req.specifier else None
8 changes: 7 additions & 1 deletion moccasin/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
_setup_network_and_account_from_config_and_cli,
get_sys_paths_list,
)
from moccasin.commands.install import mox_install
from moccasin.config import get_config, initialize_global_config
from moccasin.logging import logger
from moccasin.logging import logger, set_log_level


def main(args: Namespace) -> int:
initialize_global_config()

if not args.no_install:
mox_install(config=get_config(), quiet=True, override_logger=True)
set_log_level(quiet=args.quiet, debug=args.debug)

run_script(
args.script_name_or_path,
network=args.network,
Expand Down
7 changes: 7 additions & 0 deletions moccasin/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
_setup_network_and_account_from_config_and_cli,
get_sys_paths_list,
)
from moccasin.commands.install import mox_install
from moccasin.config import Config, get_config, initialize_global_config
from moccasin.constants.vars import TESTS_FOLDER
from moccasin.logging import set_log_level

HYPOTHESIS_ARGS: list[str] = ["hypothesis-seed"]

Expand Down Expand Up @@ -50,6 +52,11 @@

def main(args: Namespace) -> int:
initialize_global_config()

if not args.no_install:
mox_install(config=get_config(), quiet=True, override_logger=True)
set_log_level(quiet=args.quiet, debug=args.debug)

pytest_args = []

# This is not in PYTEST_ARGS
Expand Down
4 changes: 1 addition & 3 deletions tests/data/complex_project/moccasin.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
[project]
dependencies = [
"snekmate==0.1.0",
]
dependencies = ["snekmate==0.1.0"]
src = "contracts"
out = "build"
save_abi_path = "abis"
Expand Down

0 comments on commit 9c7eb0f

Please sign in to comment.