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

Fix "deps check all" command #388

Merged
merged 7 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 31 additions & 8 deletions multiversx_sdk_cli/cli_deps.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
from typing import Any
from typing import Any, List, Tuple

from multiversx_sdk_cli import cli_shared, config, dependencies, errors
from multiversx_sdk_cli.dependencies.install import get_deps_dict
from multiversx_sdk_cli.dependencies.modules import DependencyModule

logger = logging.getLogger("cli.deps")

Expand Down Expand Up @@ -34,16 +35,38 @@ def install(args: Any):

def check(args: Any):
name: str = args.name
module = dependencies.get_module_by_key(name)
tag_to_check: str = config.get_dependency_tag(module.key)

if name == "all":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After re-thinking about this, maybe we can drop check all and install all? On the long run, maybe it's better (less issues, less corner cases) if we do.

Let's double check with @ovidiuolteanu and @ccorcoveanu. I feel deps install all is quite exotic.

Comment on lines +38 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cargo does not seem to be loaded correctly when trying to install all deps

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested and it seems to work now 👍

all_dependencies = dependencies.get_all_deps()
missing_dependencies: List[Tuple[str, str]] = []

for dependency in all_dependencies:
tag_to_check: str = config.get_dependency_tag(dependency.key)
is_installed = check_module_is_installed(dependency, tag_to_check)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not handle rust the complete way, as done below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


if not is_installed:
missing_dependencies.append((dependency.key, tag_to_check))

if len(missing_dependencies):
raise errors.DependenciesMissing(missing_dependencies)
return
else:
module = dependencies.get_module_by_key(name)
tag_to_check: str = config.get_dependency_tag(module.key)

is_installed = check_module_is_installed(module, tag_to_check)
if is_installed and name != "rust":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Rust, we don't display this log info? Or without this if the message will be duplicated by the one from modules.py? I think better to have it duplicated on the CLI than to have this custom if here (e.g. in the future we will forget its reason).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the if statement the message will be duplicated by the one in modules.py. Why I added it it's because if you have a different Rust version other than the recommended one, you'll see a warning that the versions don't match but the log will show that the correct version is installed since it takes the tag from the config.
A problem that could occur with the other dependencies as well, now that I think about it, but is less common.

logger.info(f"[{module.key} {tag_to_check}] is installed.")
return
elif not is_installed:
raise errors.DependencyMissing(module.key, tag_to_check)


def check_module_is_installed(module: DependencyModule, tag_to_check: str) -> bool:
resolution: str = config.get_dependency_resolution(module.key)
resolution = resolution if resolution else "HOST"

logger.info(f"Checking dependency: module = {module.key}, tag = {tag_to_check}, resolution = {resolution}")

installed = module.is_installed(tag_to_check)
if installed:
logger.info(f"[{name} {tag_to_check}] is installed.")
return

raise errors.DependencyMissing(name, tag_to_check)
return installed
43 changes: 3 additions & 40 deletions multiversx_sdk_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from pathlib import Path
from typing import Any, Dict, List

import semver

from multiversx_sdk_cli import errors, utils
from multiversx_sdk_cli.ux import show_warning

Expand Down Expand Up @@ -146,7 +144,6 @@ def _guard_valid_config_deletion(name: str):
def get_defaults() -> Dict[str, Any]:
return {
"dependencies.vmtools.tag": "v1.4.60",
"dependencies.mx_sdk_rs.tag": "latest",
"dependencies.vmtools.urlTemplate.linux": "https://github.com/multiversx/mx-chain-vm-go/archive/{TAG}.tar.gz",
"dependencies.vmtools.urlTemplate.osx": "https://github.com/multiversx/mx-chain-vm-go/archive/{TAG}.tar.gz",
"dependencies.vmtools.urlTemplate.windows": "https://github.com/multiversx/mx-chain-vm-go/archive/{TAG}.tar.gz",
Expand All @@ -156,9 +153,9 @@ def get_defaults() -> Dict[str, Any]:
"dependencies.golang.urlTemplate.linux": "https://golang.org/dl/{TAG}.linux-amd64.tar.gz",
"dependencies.golang.urlTemplate.osx": "https://golang.org/dl/{TAG}.darwin-amd64.tar.gz",
"dependencies.golang.urlTemplate.windows": "https://golang.org/dl/{TAG}.windows-amd64.zip",
"dependencies.twiggy.tag": "latest",
"dependencies.sc-meta.tag": "latest",
"dependencies.testwallets.tag": "latest",
"dependencies.twiggy.tag": "",
"dependencies.sc-meta.tag": "",
"dependencies.testwallets.tag": "v1.0.0",
"dependencies.testwallets.urlTemplate.linux": "https://github.com/multiversx/mx-sdk-testwallets/archive/{TAG}.tar.gz",
"dependencies.testwallets.urlTemplate.osx": "https://github.com/multiversx/mx-sdk-testwallets/archive/{TAG}.tar.gz",
"dependencies.testwallets.urlTemplate.windows": "https://github.com/multiversx/mx-sdk-testwallets/archive/{TAG}.tar.gz",
Expand Down Expand Up @@ -254,42 +251,8 @@ def determine_final_args(argv: List[str], config_args: Dict[str, Any]) -> List[s

def get_dependency_directory(key: str, tag: str) -> Path:
parent_directory = get_dependency_parent_directory(key)
if tag == 'latest':
if not parent_directory.is_dir():
return parent_directory / tag
tag = get_latest_semver_from_directory(parent_directory)

return parent_directory / tag


def get_dependency_parent_directory(key: str) -> Path:
return SDK_PATH / key


def get_latest_semver_from_directory(directory: Path) -> str:
subdirs = [subdir.name for subdir in directory.iterdir()]
try:
return get_latest_semver(subdirs)
except IndexError:
raise Exception(f'no versions found in {directory}')


def get_latest_semver(versions: List[str]) -> str:
semantic_versions = parse_strings_to_semver(versions)
latest_version = sorted(semantic_versions).pop()
return 'v' + str(latest_version)


def parse_strings_to_semver(version_strings: List[str]) -> List[semver.VersionInfo]:
versions = []
for version_string in version_strings:
try:
# Omit the 'v' prefix of the version string
version_string = version_string[1:]
version = semver.VersionInfo.parse(version_string)
except ValueError:
continue

versions.append(version)

return versions
7 changes: 5 additions & 2 deletions multiversx_sdk_cli/dependencies/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from multiversx_sdk_cli.dependencies.install import install_module, get_module_directory, get_module_by_key, get_golang
from multiversx_sdk_cli.dependencies.install import (get_all_deps, get_golang,
get_module_by_key,
get_module_directory,
install_module)

__all__ = ["install_module", "get_module_directory", "get_module_by_key", "get_golang"]
__all__ = ["install_module", "get_module_directory", "get_module_by_key", "get_golang", "get_all_deps"]
8 changes: 4 additions & 4 deletions multiversx_sdk_cli/dependencies/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

def install_module(key: str, overwrite: bool = False):
if key == 'all':
modules = _get_all_deps()
modules = get_all_deps()
else:
modules = [get_module_by_key(key)]

Expand All @@ -29,7 +29,7 @@ def get_module_directory(key: str) -> Path:


def get_module_by_key(key: str) -> DependencyModule:
matches = [module for module in _get_all_deps() if module.key == key or key in module.aliases]
matches = [module for module in get_all_deps() if module.key == key or key in module.aliases]
if len(matches) != 1:
raise errors.UnknownDependency(key)

Expand All @@ -39,15 +39,15 @@ def get_module_by_key(key: str) -> DependencyModule:
def get_deps_dict() -> Dict[str, DependencyModule]:
deps: Dict[str, DependencyModule] = dict()

for module in _get_all_deps():
for module in get_all_deps():
deps[module.key] = module
for alias in module.aliases:
deps[alias] = module

return deps


def _get_all_deps() -> List[DependencyModule]:
def get_all_deps() -> List[DependencyModule]:
return [
Rust(key="rust"),
GolangModule(key="golang"),
Expand Down
54 changes: 29 additions & 25 deletions multiversx_sdk_cli/dependencies/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ def install(self, overwrite: bool) -> None:
# We install the default tag
tag = config.get_dependency_tag(self.key)

if tag == 'latest':
tag = self.get_latest_release()

logger.info(f"install: key={self.key}, tag={tag}, overwrite={overwrite}")

if self._should_skip(tag, overwrite):
Expand Down Expand Up @@ -60,9 +57,6 @@ def is_installed(self, tag: str) -> bool:
def get_env(self) -> Dict[str, str]:
raise NotImplementedError()

def get_latest_release(self) -> str:
raise NotImplementedError()

def get_resolution(self) -> DependencyResolution:
return get_dependency_resolution(self.key)

Expand Down Expand Up @@ -135,14 +129,6 @@ def _get_download_url(self, tag: str) -> str:
url = url.replace("{TAG}", tag)
return url

def get_latest_release(self) -> str:
if self.repo_name is None or self.organisation is None:
raise ValueError(f'{self.key}: repo_name or organisation not specified')

org_repo = f'{self.organisation}/{self.repo_name}'
tag = utils.query_latest_release_tag(org_repo)
return tag

def _get_archive_path(self, tag: str) -> Path:
tools_folder = Path(workstation.get_tools_folder())
archive = tools_folder / f"{self.key}.{tag}.{self.archive_type}"
Expand Down Expand Up @@ -252,9 +238,6 @@ def get_env(self) -> Dict[str, str]:
def get_gopath(self) -> Path:
return self.get_parent_directory() / "GOPATH"

def get_latest_release(self) -> str:
raise errors.UnsupportedConfigurationValue("Golang tag must always be explicit, not latest")


class Rust(DependencyModule):
def is_installed(self, tag: str) -> bool:
Expand All @@ -270,7 +253,24 @@ def is_installed(self, tag: str) -> bool:
logger.info(f"which twiggy: {which_twiggy}")

dependencies = [which_rustc, which_cargo, which_sc_meta, which_wasm_opt, which_twiggy]
return all(dependency is not None for dependency in dependencies)
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)
Expand Down Expand Up @@ -338,26 +338,26 @@ def _install_sc_meta(self):
tag = config.get_dependency_tag("sc-meta")
args = ["cargo", "install", "multiversx-sc-meta", "--locked"]

if tag != "latest":
if tag:
args.extend(["--version", tag])

myprocess.run_process(args)
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)
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 != "latest":
if tag:
args.extend(["--version", tag])

myprocess.run_process(args)
myprocess.run_process(args, env=self.get_cargo_env())

def _get_installer_url(self) -> str:
if workstation.is_windows():
Expand All @@ -383,8 +383,12 @@ def get_directory(self, tag: str) -> Path:
def get_env(self) -> Dict[str, str]:
return dict(os.environ)

def get_latest_release(self) -> str:
raise errors.UnsupportedConfigurationValue("Rust tag must either be explicit, empty or 'nightly'")
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):
Expand Down
16 changes: 13 additions & 3 deletions multiversx_sdk_cli/errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

from pathlib import Path
from typing import Any, Union
from typing import Any, List, Tuple, Union


class KnownError(Exception):
Expand Down Expand Up @@ -49,6 +49,16 @@ def __init__(self, name: str, tag: str):
super().__init__(f"Dependency missing: {name} {tag}")


class DependenciesMissing(KnownError):
def __init__(self, dependencies: List[Tuple[str, str]]):
message = "Dependencies missing: \n"

for dependency in dependencies:
message += f"{dependency[0]} {dependency[1]}\n"

super().__init__(message.rstrip("\n"))


class UnknownDependency(KnownError):
def __init__(self, name: str):
super().__init__(f"Unknown dependency: {name}")
Expand Down Expand Up @@ -80,8 +90,8 @@ def __init__(self, action_or_item: str, platform: str):


class BuildError(KnownError):
def __init__(self, message, inner=None):
super().__init__(f"Build error: {message}.", inner)
def __init__(self, message: str):
super().__init__(f"Build error: {message}.")


class UnknownArgumentFormat(KnownError):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from pathlib import Path

from multiversx_sdk_cli import config, dependencies, myprocess, utils
from multiversx_sdk_cli import dependencies, myprocess, utils
from multiversx_sdk_cli.errors import BadFile
from multiversx_sdk_cli.projects.report.features.report_option import \
ReportFeature
Expand Down
12 changes: 12 additions & 0 deletions multiversx_sdk_cli/tests/test_cli_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,15 @@ def test_deps_install_testwallets():
def test_deps_check_testwallets():
return_code = main(["deps", "check", "testwallets"])
assert return_code == 0


@pytest.mark.skip_on_windows
def test_deps_install_all():
return_code = main(["deps", "install", "all"])
assert return_code == 0


@pytest.mark.skip_on_windows
def test_deps_check_all():
return_code = main(["deps", "check", "all"])
assert return_code == 0
25 changes: 0 additions & 25 deletions multiversx_sdk_cli/tests/test_modules.py

This file was deleted.

Loading
Loading