diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b157b6e8..fda48f40 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -38,11 +38,15 @@ jobs: export PYTHONPATH=. python3 -m multiversx_sdk_cli.cli config new test python3 -m multiversx_sdk_cli.cli config set github_api_token ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown - name: Setup test dependencies shell: bash run: | python3 -m multiversx_sdk_cli.cli deps install testwallets - python3 -m multiversx_sdk_cli.cli deps install rust - name: Run unit tests shell: bash run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 044f4b88..bba1e891 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -36,10 +36,14 @@ jobs: export PYTHONPATH=. python3 -m multiversx_sdk_cli.cli config new test python3 -m multiversx_sdk_cli.cli config set github_api_token ${{ secrets.GITHUB_TOKEN }} + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown - name: Setup test dependencies run: | python3 -m multiversx_sdk_cli.cli deps install testwallets - python3 -m multiversx_sdk_cli.cli deps install rust - name: Run unit tests run: | export PYTHONPATH=. diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 4ce518bd..ac721279 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -2,7 +2,7 @@ import logging import os from pathlib import Path -from typing import Any, List, Tuple +from typing import Any from multiversx_sdk import ( Address, @@ -13,25 +13,22 @@ ) from multiversx_sdk.abi import Abi -from multiversx_sdk_cli import cli_shared, projects, utils +from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.config import get_config_for_network_providers from multiversx_sdk_cli.constants import NUMBER_OF_SHARDS from multiversx_sdk_cli.contract_verification import trigger_contract_verification from multiversx_sdk_cli.contracts import SmartContract from multiversx_sdk_cli.cosign_transaction import cosign_transaction -from multiversx_sdk_cli.dependency_checker import check_if_rust_is_installed from multiversx_sdk_cli.docker import is_docker_installed, run_docker from multiversx_sdk_cli.errors import DockerMissingError, NoWalletProvided from multiversx_sdk_cli.interfaces import IAddress -from multiversx_sdk_cli.projects.core import get_project_paths_recursively -from multiversx_sdk_cli.projects.templates import Contract -from multiversx_sdk_cli.ux import show_message, show_warning +from multiversx_sdk_cli.ux import show_warning logger = logging.getLogger("cli.contracts") -def setup_parser(args: List[str], subparsers: Any) -> Any: +def setup_parser(args: list[str], subparsers: Any) -> Any: parser = cli_shared.add_group_subparser( subparsers, "contract", @@ -40,72 +37,14 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: subparsers = parser.add_subparsers() sub = cli_shared.add_command_subparser( - subparsers, - "contract", - "new", - "Create a new Smart Contract project based on a template.", - ) - sub.add_argument( - "--name", - help="The name of the contract. If missing, the name of the template will be used.", - ) - sub.add_argument("--template", required=True, help="the template to use") - sub.add_argument("--tag", help="the framework version on which the contract should be created") - sub.add_argument( - "--path", - type=str, - default=os.getcwd(), - help="the parent directory of the project (default: current directory)", + subparsers, "contract", "build", "Build a Smart Contract project. This command is DISABLED." ) - sub.set_defaults(func=create) - - sub = cli_shared.add_command_subparser( - subparsers, - "contract", - "templates", - "List the available Smart Contract templates.", - ) - sub.add_argument("--tag", help="the sc-meta framework version referred to") - sub.set_defaults(func=list_templates) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "build", "Build a Smart Contract project.") - _add_build_options_sc_meta(sub) sub.set_defaults(func=build) - sub = cli_shared.add_command_subparser(subparsers, "contract", "clean", "Clean a Smart Contract project.") - sub.add_argument( - "--path", - default=os.getcwd(), - help="the project directory (default: current directory)", - ) - sub.set_defaults(func=clean) - - sub = cli_shared.add_command_subparser(subparsers, "contract", "test", "Run tests.") - sub.add_argument( - "--path", - default=os.getcwd(), - help="the directory of the contract (default: %(default)s)", - ) - sub.add_argument( - "--go", - action="store_true", - help="this arg runs rust and go tests (default: false)", - ) - sub.add_argument( - "--scen", - action="store_true", - help="this arg runs scenarios (default: false). If `--scen` and `--go` are both specified, scen overrides the go argument", - ) - sub.add_argument( - "--nocapture", - action="store_true", - help="this arg prints the entire output of the vm (default: false)", - ) - sub.set_defaults(func=run_tests) - output_description = CLIOutputBuilder.describe( with_contract=True, with_transaction_on_network=True, with_simulation=True ) + sub = cli_shared.add_command_subparser( subparsers, "contract", @@ -279,96 +218,6 @@ def _add_project_arg(sub: Any): ) -def _add_build_options_sc_meta(sub: Any): - sub.add_argument( - "--path", - default=os.getcwd(), - help="the project directory (default: current directory)", - ) - sub.add_argument( - "--no-wasm-opt", - action="store_true", - default=False, - help="do not optimize wasm files after the build (default: %(default)s)", - ) - sub.add_argument( - "--wasm-symbols", - action="store_true", - default=False, - help="for rust projects, does not strip the symbols from the wasm output. Useful for analysing the bytecode. Creates larger wasm files. Avoid in production (default: %(default)s)", - ) - sub.add_argument( - "--wasm-name", - type=str, - help="for rust projects, optionally specify the name of the wasm bytecode output file", - ) - sub.add_argument( - "--wasm-suffix", - type=str, - help="for rust projects, optionally specify the suffix of the wasm bytecode output file", - ) - sub.add_argument( - "--target-dir", - type=str, - help="for rust projects, forward the parameter to Cargo", - ) - sub.add_argument( - "--wat", - action="store_true", - help="also generate a WAT file when building", - default=False, - ) - sub.add_argument( - "--mir", - action="store_true", - help="also emit MIR files when building", - default=False, - ) - sub.add_argument( - "--llvm-ir", - action="store_true", - help="also emit LL (LLVM) files when building", - default=False, - ) - sub.add_argument("--ignore", help="ignore all directories with these names. [default: target]") - sub.add_argument( - "--no-imports", - action="store_true", - default=False, - help="skips extracting the EI imports after building the contracts", - ) - sub.add_argument( - "--no-abi-git-version", - action="store_true", - default=False, - help="skips loading the Git version into the ABI", - ) - sub.add_argument( - "--twiggy-top", - action="store_true", - default=False, - help="generate a twiggy top report after building", - ) - sub.add_argument( - "--twiggy-paths", - action="store_true", - default=False, - help="generate a twiggy paths report after building", - ) - sub.add_argument( - "--twiggy-monos", - action="store_true", - default=False, - help="generate a twiggy monos report after building", - ) - sub.add_argument( - "--twiggy-dominators", - action="store_true", - default=False, - help="generate a twiggy dominators report after building", - ) - - def _add_build_options_args(sub: Any): sub.add_argument( "--debug", @@ -475,52 +324,13 @@ def _add_metadata_arg(sub: Any): sub.set_defaults(metadata_upgradeable=True, metadata_payable=False) -def list_templates(args: Any): - tag = args.tag - contract = Contract(tag) - templates = contract.get_contract_templates() - show_message(templates) - - -def create(args: Any): - name = args.name - template = args.template - tag = args.tag - path = Path(args.path) - - contract = Contract(tag, name, template, path) - contract.create_from_template() - - -def get_project_paths(args: Any) -> List[Path]: - base_path = Path(args.project) - recursive = bool(args.recursive) - if recursive: - return get_project_paths_recursively(base_path) - return [base_path] - - -def clean(args: Any): - check_if_rust_is_installed() - project_path = args.path - projects.clean_project(Path(project_path)) - - def build(args: Any): - project_paths = [Path(args.path)] - arg_list = cli_shared.convert_args_object_to_args_list(args) - - for project in project_paths: - projects.build_project(project, arg_list) - - show_warning( - "The primary tool for building smart contracts is `sc-meta`. Try using the `sc-meta all build` command." - ) - + message = """This command cannot build smart contracts anymore. -def run_tests(args: Any): - check_if_rust_is_installed() - projects.run_tests(args) +The primary tool for building smart contracts is `sc-meta`. +To install `sc-meta` check out the documentation: https://docs.multiversx.com/sdk-and-tools/troubleshooting/rust-setup. +After installing, use the `sc-meta all build` command. To lear more about `sc-meta`, check out this page: https://docs.multiversx.com/developers/meta/sc-meta-cli/#calling-build.""" + show_warning(message) def deploy(args: Any): @@ -679,7 +489,7 @@ def query(args: Any): utils.dump_out_json(result) -def _get_contract_arguments(args: Any) -> Tuple[List[Any], bool]: +def _get_contract_arguments(args: Any) -> tuple[list[Any], bool]: json_args = json.loads(Path(args.arguments_file).expanduser().read_text()) if args.arguments_file else None if json_args and args.arguments: diff --git a/multiversx_sdk_cli/dependencies/install.py b/multiversx_sdk_cli/dependencies/install.py index e3666ab0..c9fb8d29 100644 --- a/multiversx_sdk_cli/dependencies/install.py +++ b/multiversx_sdk_cli/dependencies/install.py @@ -6,7 +6,6 @@ from multiversx_sdk_cli.dependencies.modules import ( DependencyModule, GolangModule, - Rust, TestWalletsModule, ) @@ -51,7 +50,6 @@ def get_deps_dict() -> Dict[str, DependencyModule]: def get_all_deps() -> List[DependencyModule]: return [ - Rust(key="rust"), GolangModule(key="golang"), TestWalletsModule(key="testwallets"), ] diff --git a/multiversx_sdk_cli/dependencies/modules.py b/multiversx_sdk_cli/dependencies/modules.py index 4437520e..3d2c4df9 100644 --- a/multiversx_sdk_cli/dependencies/modules.py +++ b/multiversx_sdk_cli/dependencies/modules.py @@ -1,31 +1,21 @@ import logging import os -import platform import shutil from os import path from pathlib import Path -from typing import Dict, List, Optional - -from multiversx_sdk_cli import ( - config, - dependencies, - downloader, - errors, - myprocess, - utils, - workstation, -) +from typing import Optional + +from multiversx_sdk_cli import config, downloader, errors, utils, workstation from multiversx_sdk_cli.dependencies.resolution import ( DependencyResolution, get_dependency_resolution, ) -from multiversx_sdk_cli.ux import show_message, show_warning logger = logging.getLogger("modules") class DependencyModule: - def __init__(self, key: str, aliases: List[str] = []): + def __init__(self, key: str, aliases: list[str] = []): self.key = key self.aliases = aliases @@ -64,7 +54,7 @@ def uninstall(self, tag: str) -> None: def is_installed(self, tag: str) -> bool: raise NotImplementedError() - def get_env(self) -> Dict[str, str]: + def get_env(self) -> dict[str, str]: raise NotImplementedError() def get_resolution(self) -> DependencyResolution: @@ -75,7 +65,7 @@ class StandaloneModule(DependencyModule): def __init__( self, key: str, - aliases: List[str] = [], + aliases: list[str] = [], repo_name: Optional[str] = None, organisation: Optional[str] = None, ): @@ -166,7 +156,7 @@ def is_installed(self, tag: str) -> bool: raise errors.BadDependencyResolution(self.key, resolution) - def get_env(self) -> Dict[str, str]: + def get_env(self) -> dict[str, str]: resolution = self.get_resolution() directory = self.get_directory(config.get_dependency_tag(self.key)) parent_directory = self.get_parent_directory() @@ -198,198 +188,6 @@ def get_gopath(self) -> Path: return self.get_parent_directory() / "GOPATH" -class Rust(DependencyModule): - def is_installed(self, tag: str) -> bool: - which_rustc = shutil.which("rustc") - which_cargo = shutil.which("cargo") - which_sc_meta = shutil.which("sc-meta") - which_mx_scenario_go = shutil.which("mx-scenario-go") - which_wasm_opt = shutil.which("wasm-opt") - which_twiggy = shutil.which("twiggy") - logger.info(f"which rustc: {which_rustc}") - logger.info(f"which cargo: {which_cargo}") - logger.info(f"which sc-meta: {which_sc_meta}") - logger.info(f"which mx-scenario-go: {which_mx_scenario_go}") - logger.info(f"which wasm-opt: {which_wasm_opt}") - logger.info(f"which twiggy: {which_twiggy}") - - dependencies = [ - which_rustc, - which_cargo, - which_sc_meta, - which_wasm_opt, - which_twiggy, - ] - installed = all(dependency is not None for dependency in dependencies) - - if installed: - actual_version_installed = self._get_actual_installed_version() - - if tag in actual_version_installed: - logger.info(f"[{self.key} {tag}] is installed.") - elif "not found" in actual_version_installed: - show_warning("You have installed Rust without using `rustup`.") - else: - show_warning( - f"The Rust version you have installed does not match the recommended version.\nInstalled [{actual_version_installed}], expected [{tag}]." - ) - - return installed - - def _get_actual_installed_version(self) -> str: - args = ["rustup", "default"] - output = myprocess.run_process(args, dump_to_stdout=False) - return output.strip() - - def install(self, overwrite: bool) -> None: - self._check_install_env(apply_correction=overwrite) - - module = dependencies.get_module_by_key("rust") - tag: str = config.get_dependency_tag(module.key) - - if not overwrite: - show_warning( - f"We recommend using rust {tag}. If you'd like to overwrite your current version please run `mxpy deps install rust --overwrite`." - ) - logger.info(f"install: key={self.key}, tag={tag}, overwrite={overwrite}") - - if overwrite: - logger.info("Overwriting the current rust version...") - elif self.is_installed(""): - return - - self._install_rust(tag) - self._install_sc_meta() - self._install_wasm_opt() - self._install_twiggy() - show_message( - "To ensure sc-meta functions correctly, please install all the required dependencies by executing the following command: `sc-meta install all`." - ) - - def _check_install_env(self, apply_correction: bool = True): - """ - See https://rust-lang.github.io/rustup/installation/index.html#choosing-where-to-install. - """ - - current_cargo_home = os.environ.get("CARGO_HOME", None) - current_rustup_home = os.environ.get("RUSTUP_HOME", None) - if current_cargo_home: - show_warning( - f"""CARGO_HOME variable is set to: {current_cargo_home}. -This may cause problems with the installation.""" - ) - - if apply_correction: - show_warning("CARGO_HOME will be temporarily unset.") - os.environ["CARGO_HOME"] = "" - - if current_rustup_home: - show_warning( - f"""RUSTUP_HOME variable is set to: {current_rustup_home}. -This may cause problems with the installation of rust.""" - ) - - if apply_correction: - show_warning("RUSTUP_HOME will be temporarily unset.") - os.environ["RUSTUP_HOME"] = "" - - def _install_rust(self, tag: str) -> None: - installer_url = self._get_installer_url() - installer_path = self._get_installer_path() - - downloader.download(installer_url, str(installer_path)) - utils.mark_executable(str(installer_path)) - - if tag: - toolchain = tag - else: - toolchain = "stable" - - args = [ - str(installer_path), - "--verbose", - "--default-toolchain", - toolchain, - "--profile", - "minimal", - "--target", - "wasm32-unknown-unknown", - "-y", - ] - - logger.info("Installing rust.") - myprocess.run_process(args) - - def _install_sc_meta(self): - logger.info("Installing multiversx-sc-meta.") - tag = config.get_dependency_tag("sc-meta") - args = ["cargo", "install", "multiversx-sc-meta", "--locked"] - - if tag: - args.extend(["--version", tag]) - - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_wasm_opt(self): - logger.info("Installing wasm-opt. This may take a while.") - tag = config.get_dependency_tag("wasm-opt") - args = ["cargo", "install", "wasm-opt", "--version", tag] - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_twiggy(self): - logger.info("Installing twiggy.") - tag = config.get_dependency_tag("twiggy") - args = ["cargo", "install", "twiggy"] - - if tag: - args.extend(["--version", tag]) - - myprocess.run_process(args, env=self.get_cargo_env()) - - def _install_sc_meta_deps(self): - # this is needed for sc-meta to run the tests - # if os is Windows skip installation - os = platform.system().lower() - if os == "windows": - logger.warning("`sc-meta install all` command is not supported on windows") - return - - logger.info("Installing sc-meta dependencies.") - args = ["sc-meta", "install", "all"] - myprocess.run_process(args) - - def _get_installer_url(self) -> str: - if workstation.is_windows(): - return "https://win.rustup.rs" - return "https://sh.rustup.rs" - - def _get_installer_path(self) -> Path: - tools_folder = workstation.get_tools_folder() - - if workstation.is_windows(): - return tools_folder / "rustup-init.exe" - return tools_folder / "rustup.sh" - - def uninstall(self, tag: str): - directory = self.get_directory("") - if os.path.isdir(directory): - shutil.rmtree(directory) - - def get_directory(self, tag: str) -> Path: - tools_folder = workstation.get_tools_folder() - return tools_folder / "vendor-rust" - - def get_env(self) -> Dict[str, str]: - return dict(os.environ) - - def get_cargo_env(self) -> Dict[str, str]: - env = self.get_env() - cargo = Path("~/.cargo/bin").expanduser() - path = env["PATH"] - env["PATH"] = f"{str(cargo)}:{path}" - return env - - class TestWalletsModule(StandaloneModule): def __init__(self, key: str): super().__init__(key, []) diff --git a/multiversx_sdk_cli/dependency_checker.py b/multiversx_sdk_cli/dependency_checker.py deleted file mode 100644 index abf392a4..00000000 --- a/multiversx_sdk_cli/dependency_checker.py +++ /dev/null @@ -1,13 +0,0 @@ -from multiversx_sdk_cli import config, errors, ux -from multiversx_sdk_cli.dependencies.modules import Rust - - -def check_if_rust_is_installed(): - RUST_MODULE_KEY = "rust" - rust_module = Rust(RUST_MODULE_KEY) - if not rust_module.is_installed(""): - tag = config.get_dependency_tag(RUST_MODULE_KEY) - ux.show_critical_error( - "Rust is not installed on your machine. Run `mxpy deps install rust --overwrite` and try again." - ) - raise errors.DependencyMissing(RUST_MODULE_KEY, tag) diff --git a/multiversx_sdk_cli/projects/__init__.py b/multiversx_sdk_cli/projects/__init__.py deleted file mode 100644 index d562dc41..00000000 --- a/multiversx_sdk_cli/projects/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from multiversx_sdk_cli.projects.core import ( - build_project, - clean_project, - load_project, - run_tests, -) -from multiversx_sdk_cli.projects.project_base import Project -from multiversx_sdk_cli.projects.project_rust import ProjectRust -from multiversx_sdk_cli.projects.templates import Contract - -__all__ = [ - "build_project", - "clean_project", - "run_tests", - "load_project", - "Project", - "ProjectRust", - "Contract", -] diff --git a/multiversx_sdk_cli/projects/constants.py b/multiversx_sdk_cli/projects/constants.py deleted file mode 100644 index 0ee36a27..00000000 --- a/multiversx_sdk_cli/projects/constants.py +++ /dev/null @@ -1,2 +0,0 @@ -PROJECT_CONFIG_FILENAME = "multiversx.json" -OLD_PROJECT_CONFIG_FILENAME = "elrond.json" diff --git a/multiversx_sdk_cli/projects/core.py b/multiversx_sdk_cli/projects/core.py deleted file mode 100644 index fbcfba3f..00000000 --- a/multiversx_sdk_cli/projects/core.py +++ /dev/null @@ -1,63 +0,0 @@ -import logging -from pathlib import Path -from typing import Any, List - -from multiversx_sdk_cli import errors, guards -from multiversx_sdk_cli.projects import shared -from multiversx_sdk_cli.projects.constants import ( - OLD_PROJECT_CONFIG_FILENAME, - PROJECT_CONFIG_FILENAME, -) -from multiversx_sdk_cli.projects.project_base import Project -from multiversx_sdk_cli.projects.project_rust import ProjectRust - -logger = logging.getLogger("projects.core") - - -def load_project(directory: Path) -> Project: - guards.is_directory(directory) - - if shared.is_source_rust(directory): - return ProjectRust(directory) - else: - raise errors.NotSupportedProject(str(directory)) - - -def build_project(directory: Path, args: List[str]): - directory = directory.expanduser() - - logger.info("build_project.directory: %s", directory) - - project = load_project(directory) - outputs = project.build(args) - logger.info("Build ran.") - for output_wasm_file in outputs: - logger.info(f"WASM file generated: {output_wasm_file}") - - -def clean_project(directory: Path): - logger.info("clean_project.directory: %s", directory) - directory = directory.expanduser() - guards.is_directory(directory) - project = load_project(directory) - project.clean() - logger.info("Project cleaned.") - - -def run_tests(args: Any): - directory = Path(args.path) - - logger.info("run_tests.project: %s", directory) - - guards.is_directory(directory) - project = load_project(directory) - project.run_tests(args) - - -def get_project_paths_recursively(base_path: Path) -> List[Path]: - guards.is_directory(base_path) - old_markers = list(base_path.glob(f"**/{OLD_PROJECT_CONFIG_FILENAME}")) - new_markers = list(base_path.glob(f"**/{PROJECT_CONFIG_FILENAME}")) - project_marker_files = old_markers + new_markers - path_list = [marker_file.parent for marker_file in project_marker_files] - return sorted(path_list) diff --git a/multiversx_sdk_cli/projects/interfaces.py b/multiversx_sdk_cli/projects/interfaces.py deleted file mode 100644 index 3a14fa5d..00000000 --- a/multiversx_sdk_cli/projects/interfaces.py +++ /dev/null @@ -1,10 +0,0 @@ -from pathlib import Path -from typing import Any - - -class IProject: - def get_option(self, option_name: str) -> Any: - return None - - def get_file_wasm(self) -> Path: - return Path("") diff --git a/multiversx_sdk_cli/projects/migrations.py b/multiversx_sdk_cli/projects/migrations.py deleted file mode 100644 index b0ddda1a..00000000 --- a/multiversx_sdk_cli/projects/migrations.py +++ /dev/null @@ -1,23 +0,0 @@ -from pathlib import Path - -from multiversx_sdk_cli.projects.constants import ( - OLD_PROJECT_CONFIG_FILENAME, - PROJECT_CONFIG_FILENAME, -) - - -def migrate_project_config_file(project_path: Path): - new_config_file = project_path / PROJECT_CONFIG_FILENAME - old_config_file = project_path / OLD_PROJECT_CONFIG_FILENAME - - if old_config_file.exists(): - if new_config_file.exists(): - old_config_file.unlink() - else: - old_config_file.rename(new_config_file) - - -def migrate_project_templates(parent_folder: Path): - for project_folder in parent_folder.iterdir(): - if project_folder.is_dir(): - migrate_project_config_file(project_folder) diff --git a/multiversx_sdk_cli/projects/project_base.py b/multiversx_sdk_cli/projects/project_base.py deleted file mode 100644 index dd1a2e8a..00000000 --- a/multiversx_sdk_cli/projects/project_base.py +++ /dev/null @@ -1,195 +0,0 @@ -import logging -import shutil -from abc import abstractmethod -from os import path -from pathlib import Path -from typing import Any, Dict, List, Union, final - -from multiversx_sdk_cli import dependencies, errors, myprocess, utils -from multiversx_sdk_cli.projects.constants import PROJECT_CONFIG_FILENAME -from multiversx_sdk_cli.projects.interfaces import IProject -from multiversx_sdk_cli.projects.migrations import migrate_project_config_file - -logger = logging.getLogger("Project") - - -class Project(IProject): - - def __init__(self, directory: Path): - self.path = directory.expanduser().resolve() - self.directory = str(self.path) - - def build(self, args: List[str], options: Union[Dict[str, Any], None] = None) -> List[Path]: - migrate_project_config_file(self.path) - self.forwarded_args = args - self.options = options or dict() - self.debug = self.options.get("debug", False) - self._ensure_dependencies_installed() - self.perform_build() - contract_paths = self._do_after_build_custom() - self._do_after_build_core() - return contract_paths - - def clean(self): - utils.remove_folder(self.get_output_folder()) - - def _ensure_dependencies_installed(self): - module_keys = self.get_dependencies() - for module_key in module_keys: - if module_key == "": - continue - dependencies.install_module(module_key) - - def get_dependencies(self) -> List[str]: - raise NotImplementedError() - - def perform_build(self) -> None: - raise NotImplementedError() - - def get_file_wasm(self) -> Path: - return self.find_file_in_output("*.wasm") - - def find_file_globally(self, pattern: str) -> Path: - return self.find_file_in_folder(self.path, pattern) - - def find_file_in_output(self, pattern: str) -> Path: - folder = self.get_output_folder() - return self.find_file_in_folder(Path(folder), pattern) - - def find_file_in_folder(self, folder: Path, pattern: str) -> Path: - files = list(folder.rglob(pattern)) - - if len(files) == 0: - raise errors.KnownError(f"No file matches pattern [{pattern}].") - if len(files) > 1: - logger.warning(f"More files match pattern [{pattern}]. Will pick first:\n{files}") - - file = folder / files[0] - return Path(file).resolve() - - def find_wasm_files(self): - output_folder = Path(self.get_output_folder()) - wasm_files = output_folder.rglob("*.wasm") - main_wasm_files = list(filter(lambda wasm_path: not wasm_path.name.endswith("-dbg.wasm"), wasm_files)) - return main_wasm_files - - @abstractmethod - def _do_after_build_custom(self) -> List[Path]: - raise NotImplementedError() - - @final - def _do_after_build_core(self): - pass - - def _copy_to_output(self, source: Path, destination: Union[str, None] = None) -> Path: - output_folder = self.get_output_folder() - utils.ensure_folder(output_folder) - destination = path.join(output_folder, destination) if destination else output_folder - output_wasm_file = shutil.copy(str(source), destination) - return Path(output_wasm_file) - - def get_output_folder(self): - return path.join(self.directory, "output") - - def get_wasm_default_name(self, suffix: str = "") -> str: - return f"{self.path.name}{suffix}.wasm" - - def get_wasm_path(self, wasm_name: str) -> Path: - return Path(self.get_output_folder(), wasm_name).resolve() - - def get_wasm_default_path(self) -> Path: - return self.get_wasm_path(self.get_wasm_default_name()) - - def get_wasm_view_default_path(self) -> Path: - return self.get_wasm_path(self.get_wasm_default_name("-view")) - - def get_bytecode(self): - bytecode: bytes = self.get_file_wasm().read_bytes() - bytecode_hex = bytecode.hex() - return bytecode_hex - - def load_config(self): - self.ensure_config_file() - config_file = self.get_config_file() - config = utils.read_json_file(str(config_file)) - return config - - def get_config_file(self) -> Path: - return self.path / PROJECT_CONFIG_FILENAME - - def ensure_config_file(self) -> None: - config_file = self.get_config_file() - - if not config_file.exists(): - utils.write_json_file(str(config_file), self.default_config()) - logger.info("created default project configuration") - - def default_config(self) -> Dict[str, Any]: - return dict() - - def run_tests(self, args: Any): - command = ["sc-meta", "test"] - - if args.path: - command.extend(["--path", str(Path(args.path).expanduser())]) - - if args.go: - command.append("--go") - - if args.scen: - command.append("--scen") - - if args.nocapture: - command.append("--nocapture") - - myprocess.run_process(command) - - -def glob_files(folder: Path, pattern: str) -> List[Path]: - files = folder.rglob(pattern) - return [Path(folder / file).resolve() for file in files] - - -def exclude_files(files: List[Path], to_exclude: List[Path]) -> List[Path]: - return list(set(files).difference(to_exclude)) - - -def rename_wasm_files(paths: List[Path], name: Union[str, None]) -> List[Path]: - if name is None: - return paths - new_paths = [adjust_wasm_filename(path, name) for path in paths] - for old_path, new_path in zip(paths, new_paths): - old_path.rename(new_path) - return new_paths - - -def get_contract_suffix(name: str) -> str: - for suffix in ["-view.wasm", ".wasm"]: - if name.endswith(suffix): - return suffix - return "" - - -def remove_suffix(name: str, suffix: str) -> str: - if not name.endswith(suffix) or len(suffix) == 0: - return name - return name[: -len(suffix)] - - -def adjust_wasm_filename(path: Path, name_hint: str) -> Path: - """ - Adjusts the wasm's filename by using a name hint - - >>> adjust_wasm_filename(Path("test/my-contract.wasm"), "hello.wasm") - PosixPath('test/hello.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "hello.wasm") - PosixPath('test/hello-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "hello") - PosixPath('test/hello-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract.wasm"), "world-view.wasm") - PosixPath('test/world-view.wasm') - >>> adjust_wasm_filename(Path("test/my-contract-view.wasm"), "world-view.wasm") - PosixPath('test/world-view-view.wasm') - """ - new_name = remove_suffix(name_hint, ".wasm") + get_contract_suffix(path.name) - return path.with_name(new_name) diff --git a/multiversx_sdk_cli/projects/project_rust.py b/multiversx_sdk_cli/projects/project_rust.py deleted file mode 100644 index fba6849e..00000000 --- a/multiversx_sdk_cli/projects/project_rust.py +++ /dev/null @@ -1,117 +0,0 @@ -import logging -import subprocess -from pathlib import Path -from typing import Any, Dict, List - -from multiversx_sdk_cli import dependencies, errors, utils, workstation -from multiversx_sdk_cli.constants import DEFAULT_CARGO_TARGET_DIR_NAME -from multiversx_sdk_cli.dependencies.modules import Rust -from multiversx_sdk_cli.projects.project_base import Project - -logger = logging.getLogger("ProjectRust") - - -class ProjectRust(Project): - def __init__(self, directory: Path): - super().__init__(directory) - - def clean(self): - env = self.get_env() - - args = ["sc-meta", "all", "clean", "--path", self.directory] - - subprocess.check_call(args, env=env) - - def get_meta_folder(self): - return self.path / "meta" - - def get_wasm_view_folder(self): - return self.path / "wasm-view" - - def perform_build(self): - meta = self.has_meta() - if not meta: - raise errors.NotSupportedProject("The project does not have a meta crate") - - self.run_meta() - - def prepare_build_wasm_args(self, args: List[str]): - args.extend( - [ - "--target=wasm32-unknown-unknown", - "--release", - "--out-dir", - self.get_output_folder(), - ] - ) - - def run_meta(self): - env = self.get_env() - - args = ["sc-meta", "all", "build"] - - args.extend(self.forwarded_args) - - try: - subprocess.check_call(args, env=env) - except subprocess.CalledProcessError as err: - raise errors.BuildError(f"error code = {err.returncode}, see output") - - def _ensure_cargo_target_dir(self, target_dir: str): - default_target_dir = str(workstation.get_tools_folder() / DEFAULT_CARGO_TARGET_DIR_NAME) - target_dir = target_dir or default_target_dir - utils.ensure_folder(target_dir) - return target_dir - - def has_meta(self): - return (self.get_meta_folder() / "Cargo.toml").exists() - - def has_wasm_view(self): - return (self.get_wasm_view_folder() / "Cargo.toml").exists() - - def has_abi(self): - return (self.get_abi_folder() / "Cargo.toml").exists() - - def get_abi_filepath(self): - return self.get_abi_folder() / "abi.json" - - def get_abi_folder(self): - return Path(self.directory, "abi") - - def _do_after_build_custom(self) -> List[Path]: - outputs = [self.get_wasm_default_path()] - if self.has_wasm_view(): - outputs.append(self.get_wasm_view_default_path()) - return outputs - - def get_dependencies(self): - return ["rust"] - - def get_env(self): - return dependencies.get_module_by_key("rust").get_env() - - def build_wasm_with_debug_symbols(self, build_options: Dict[str, Any]): - rust_module = Rust("rust") - rust_module.install(overwrite=False) - - cwd = self.path - env = self.get_env() - target_dir: str = build_options.get("target-dir", "") - target_dir = self._ensure_cargo_target_dir(target_dir) - - args = [ - "sc-meta", - "all", - "build", - "--wasm-symbols", - "--wasm-suffix", - "dbg", - "--no-wasm-opt", - "--target-dir", - target_dir, - ] - - try: - subprocess.check_call(args, env=env, cwd=cwd) - except subprocess.CalledProcessError as err: - raise errors.BuildError(f"error code = {err.returncode}, see output") diff --git a/multiversx_sdk_cli/projects/shared.py b/multiversx_sdk_cli/projects/shared.py deleted file mode 100644 index 25cdbfd4..00000000 --- a/multiversx_sdk_cli/projects/shared.py +++ /dev/null @@ -1,15 +0,0 @@ -import logging -from pathlib import Path - -logger = logging.getLogger("projects.shared") - - -def is_source_rust(directory: Path) -> bool: - return _directory_contains_file(directory, "Cargo.toml") - - -def _directory_contains_file(directory: Path, name_suffix: str) -> bool: - for file in directory.iterdir(): - if str(file).lower().endswith(name_suffix.lower()): - return True - return False diff --git a/multiversx_sdk_cli/projects/templates.py b/multiversx_sdk_cli/projects/templates.py deleted file mode 100644 index ae487303..00000000 --- a/multiversx_sdk_cli/projects/templates.py +++ /dev/null @@ -1,56 +0,0 @@ -import logging -from pathlib import Path -from typing import List, Union - -from multiversx_sdk_cli import myprocess -from multiversx_sdk_cli.dependency_checker import check_if_rust_is_installed - -logger = logging.getLogger("projects.templates") - - -class Contract: - def __init__( - self, - tag: Union[str, None] = None, - name: Union[str, None] = None, - template: str = "", - path: Path = Path(), - ) -> None: - self.tag = tag - self.name = name - self.template = template - self.path = path - - def get_contract_templates(self) -> str: - self._ensure_dependencies_installed() - args = self._prepare_args_to_list_templates() - templates = myprocess.run_process(args=args, dump_to_stdout=False) - return templates - - def create_from_template(self) -> None: - self._ensure_dependencies_installed() - args = self._prepare_args_to_create_new_contract_from_template() - myprocess.run_process(args) - - def _ensure_dependencies_installed(self): - logger.info("Checking if the necessarry dependencies are installed.") - check_if_rust_is_installed() - - def _prepare_args_to_list_templates(self) -> List[str]: - args = ["sc-meta", "templates"] - - if self.tag: - args.extend(["--tag", self.tag]) - - return args - - def _prepare_args_to_create_new_contract_from_template(self) -> List[str]: - args = ["sc-meta", "new", "--template", self.template, "--path", str(self.path)] - - if self.name: - args.extend(["--name", self.name]) - - if self.tag: - args.extend(["--tag", self.tag]) - - return args diff --git a/multiversx_sdk_cli/tests/test_cli_contracts.py b/multiversx_sdk_cli/tests/test_cli_contracts.py index 887d32b6..3679e062 100644 --- a/multiversx_sdk_cli/tests/test_cli_contracts.py +++ b/multiversx_sdk_cli/tests/test_cli_contracts.py @@ -2,85 +2,15 @@ from pathlib import Path from typing import Any -import pytest - from multiversx_sdk_cli.cli import main parent = Path(__file__).parent +(parent / "testdata-out").mkdir(exist_ok=True) -def test_contract_new(): - main( - [ - "contract", - "new", - "--template", - "adder", - "--path", - f"{parent}/testdata-out/SANDBOX", - ] - ) - assert Path.is_dir(parent / "testdata-out" / "SANDBOX" / "adder") - - -def test_contract_new_with_bad_code(): - # we change the contract code so the build would fail so we can catch the error - main( - [ - "contract", - "new", - "--template", - "adder", - "--path", - f"{parent}/testdata-out/SANDBOX", - "--name", - "adder-bad-src", - ] - ) - - assert Path.is_dir(parent / "testdata-out" / "SANDBOX" / "adder-bad-src") - replace_variable_with_unknown_variable_for_adder() - - -def replace_variable_with_unknown_variable_for_adder(): - # this is done in order to replace the value added in the adder contract with a unknown variable - with open( - parent / "testdata-out" / "SANDBOX" / "adder-bad-src" / "src" / "adder_bad_src.rs", - "r", - ) as f: - contract_lines = f.readlines() - - for index, line in reversed(list(enumerate(contract_lines))): - if "value" in line: - contract_lines[index] = line.replace("value", "unknown_variable") - break - - with open( - parent / "testdata-out" / "SANDBOX" / "adder-bad-src" / "src" / "adder_bad_src.rs", - "w", - ) as f: - f.writelines(contract_lines) - - -@pytest.mark.skip_on_windows def test_contract_build(): - main(["contract", "build", "--path", f"{parent}/testdata-out/SANDBOX/adder"]) - - assert Path.is_file(parent / "testdata-out" / "SANDBOX" / "adder" / "output" / "adder.wasm") - - -@pytest.mark.skip_on_windows -def test_bad_contract_build(capsys: Any): - ERROR = "Build error: error code = 101, see output." - - main(["contract", "build", "--path", f"{parent}/testdata-out/SANDBOX/adder-bad-src"]) - - out, _ = capsys.readouterr() - - if ERROR in out: - assert True - else: - assert False + return_code = main(["contract", "build"]) + assert not return_code def test_contract_deploy(): diff --git a/multiversx_sdk_cli/tests/test_cli_deps.py b/multiversx_sdk_cli/tests/test_cli_deps.py index 63162d71..8c131656 100644 --- a/multiversx_sdk_cli/tests/test_cli_deps.py +++ b/multiversx_sdk_cli/tests/test_cli_deps.py @@ -1,36 +1,8 @@ -import shutil -from pathlib import Path - import pytest from multiversx_sdk_cli.cli import main -def test_deps_install_rust(): - return_code = main(["deps", "install", "rust", "--overwrite"]) - assert return_code == 0 - - -def test_deps_check_rust(): - return_code = main(["deps", "check", "rust"]) - assert True if return_code == 0 else False - - which_rustc = shutil.which("rustc") - assert which_rustc and Path.is_file(Path(which_rustc)) - - which_cargo = shutil.which("cargo") - assert which_cargo and Path.is_file(Path(which_cargo)) - - which_sc_meta = shutil.which("sc-meta") - assert which_sc_meta and Path.is_file(Path(which_sc_meta)) - - which_wasm_opt = shutil.which("wasm-opt") - assert which_wasm_opt and Path.is_file(Path(which_wasm_opt)) - - which_twiggy = shutil.which("twiggy") - assert which_twiggy and Path.is_file(Path(which_twiggy)) - - def test_deps_install_testwallets(): return_code = main(["deps", "install", "testwallets"]) assert return_code == 0