From a2545c2f2e4a62e24fdcb8f5242c96f1f79ed7e9 Mon Sep 17 00:00:00 2001 From: cats2101 Date: Sun, 3 May 2026 00:07:00 +0000 Subject: [PATCH] feat: make auto-install the default for `solc-select use` Closes #197. `solc-select use ` now downloads the requested version if it is not already installed. The previous opt-in `--always-install` flag is kept as a no-op (with a deprecation warning) so existing scripts keep working, and a new `--offline` flag opts out of downloading and restores the prior fail-if-missing behavior. Internally, `SolcService.switch_global_version` renames the `always_install` parameter to `auto_install` and flips its default from False to True. --- README.md | 32 +++++++++--- solc_select/__main__.py | 23 +++++++-- solc_select/services/solc_service.py | 16 ++++-- tests/unit/services/test_solc_service.py | 20 ++++---- tests/unit/test_main.py | 63 ++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 tests/unit/test_main.py diff --git a/README.md b/README.md index e838269..716bc2b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ uv tool install solc-select pip3 install solc-select ``` -To automatically install and use a version, run `solc-select use --always-install`. +To install and use a version in one step, run `solc-select use `. By default, +`use` will download the requested version if it is not already installed. Pass `--offline` +to disable this and require the version to be present locally. ### Running on macOS ARM (Mac M1 and newer) @@ -41,8 +43,8 @@ To automatically install and use a version, run `solc-select use --alw ### Quick Start ```bash -# Install and set a specific Solidity version -solc-select use 0.8.19 --always-install +# Install and set a specific Solidity version (auto-installs if missing) +solc-select use 0.8.19 # Check the current version solc --version @@ -74,14 +76,27 @@ solc, the solidity compiler commandline interface Version: 0.5.2+commit.1df8f40c.Linux.g++ ``` -By default, solc-select will halt if you try to use a version that you do not have installed already. Use the `--always-install` flags to bypass this. +By default, `solc-select use` will download the requested version if it is not already +installed: ```shell -$ solc-select use 0.8.1 --always-install +$ solc-select use 0.8.1 Installing '0.8.1'... Version '0.8.1' installed. +Switched global version to 0.8.1 +``` + +To disable downloading and require the version to already be installed locally, pass +`--offline`: + +```shell +$ solc-select use 0.8.1 --offline +Error: Version '0.8.1' is not installed. ``` +The legacy `--always-install` flag is accepted for backward compatibility but is now a +no-op (auto-install is the default). + ### Available Commands ```shell @@ -97,8 +112,11 @@ solc-select use 0.8.19 # List installed versions solc-select versions -# Install and switch to a version in one command -solc-select use 0.8.19 --always-install +# Install and switch to a version in one command (default behavior) +solc-select use 0.8.19 + +# Switch only if the version is already installed (no download) +solc-select use 0.8.19 --offline ``` ## Getting Help diff --git a/solc_select/__main__.py b/solc_select/__main__.py index 22640d3..9abbfa2 100644 --- a/solc_select/__main__.py +++ b/solc_select/__main__.py @@ -33,9 +33,9 @@ def solc_select_install(service: SolcService, versions: list[str]) -> None: sys.exit(0 if success else 1) -def solc_select_use(service: SolcService, version: str, always_install: bool) -> None: +def solc_select_use(service: SolcService, version: str, offline: bool) -> None: """Handle the use command.""" - service.switch_global_version(version, always_install, silent=False) + service.switch_global_version(version, auto_install=not offline, silent=False) def solc_select_versions(service: SolcService) -> None: @@ -84,7 +84,16 @@ def create_parser() -> argparse.ArgumentParser: USE_COMMAND, help="change the version of global solc compiler" ) parser_use.add_argument("version", help="solc version you want to use (eg: 0.4.25)", nargs="?") - parser_use.add_argument("--always-install", action="store_true") + parser_use.add_argument( + "--offline", + action="store_true", + help="do not download missing versions; fail if the requested version is not installed", + ) + # Deprecated: kept as a no-op so existing scripts/CI pipelines do not break. + # Auto-install is now the default behavior of `use`. + parser_use.add_argument( + "--always-install", action="store_true", dest="always_install", help=argparse.SUPPRESS + ) parser_versions = subparsers.add_parser( VERSIONS_COMMAND, help="prints out all installed solc versions" @@ -108,7 +117,13 @@ def solc_select() -> None: elif args.command == USE_COMMAND: if not args.version: parser.error("the following arguments are required: version") - solc_select_use(service, args.version, args.always_install) + if args.always_install: + print( + "Warning: --always-install is deprecated and now a no-op; " + "auto-install is the default. Pass --offline to disable.", + file=sys.stderr, + ) + solc_select_use(service, args.version, args.offline) elif args.command == VERSIONS_COMMAND: solc_select_versions(service) elif args.command == UPGRADE_COMMAND: diff --git a/solc_select/services/solc_service.py b/solc_select/services/solc_service.py index 16a9fb5..7f7ff21 100644 --- a/solc_select/services/solc_service.py +++ b/solc_select/services/solc_service.py @@ -97,18 +97,24 @@ def install_versions(self, version_strings: list[str], silent: bool = False) -> return False def switch_global_version( - self, version_str: str, always_install: bool = False, silent: bool = False + self, version_str: str, auto_install: bool = True, silent: bool = False ) -> None: - """Switch to a different global version.""" + """Switch to a different global version. + + When ``auto_install`` is True (the default), missing versions are + downloaded and installed before switching. Pass ``auto_install=False`` + (e.g. via the ``--offline`` CLI flag) to require that the version is + already installed. + """ version = self.version_manager.validate_version(version_str) if self.filesystem.is_installed(version): self.filesystem.set_global_version(version) if not silent: print(f"Switched global version to {version}") - elif always_install: + elif auto_install: if self.install_versions([str(version)], silent): - self.switch_global_version(str(version), always_install=False, silent=silent) + self.switch_global_version(str(version), auto_install=False, silent=silent) else: raise InstallationError(str(version), "Installation failed") else: @@ -144,7 +150,7 @@ def upgrade_architecture(self) -> None: def execute_solc(self, args: list[str]) -> None: """Execute solc with the current version.""" if not self.get_installed_versions(): - self.switch_global_version("latest", always_install=True, silent=True) + self.switch_global_version("latest", auto_install=True, silent=True) try: version, _ = self.get_current_version() diff --git a/tests/unit/services/test_solc_service.py b/tests/unit/services/test_solc_service.py index 9220e65..7d74a40 100644 --- a/tests/unit/services/test_solc_service.py +++ b/tests/unit/services/test_solc_service.py @@ -138,7 +138,7 @@ def test_switch_already_installed( else: assert captured.out == "" - def test_switch_not_installed_no_auto_install(self, solc_service, mock_dependencies): + def test_switch_not_installed_offline(self, solc_service, mock_dependencies): """Raises VersionNotInstalledError when version not installed and auto_install=False.""" version = SolcVersion("0.8.19") available = [SolcVersion("0.8.19"), SolcVersion("0.8.20")] @@ -147,13 +147,15 @@ def test_switch_not_installed_no_auto_install(self, solc_service, mock_dependenc mock_dependencies["version_manager"].get_available_versions.return_value = available with pytest.raises(VersionNotInstalledError) as exc_info: - solc_service.switch_global_version("0.8.19") + solc_service.switch_global_version("0.8.19", auto_install=False) assert "0.8.19" in str(exc_info.value) assert "is not installed" in str(exc_info.value) - def test_switch_not_installed_with_auto_install(self, solc_service, mock_dependencies, capsys): - """Installs then switches when always_install=True.""" + def test_switch_not_installed_auto_install_default( + self, solc_service, mock_dependencies, capsys + ): + """Auto-install is the default: installs then switches without an explicit flag.""" version = SolcVersion("0.8.19") mock_dependencies["version_manager"].validate_version.return_value = version # First call: not installed, second call: installed @@ -162,7 +164,7 @@ def test_switch_not_installed_with_auto_install(self, solc_service, mock_depende mock_dependencies["version_manager"].get_available_versions.return_value = [version] mock_dependencies["artifact_manager"].install_versions.return_value = True - solc_service.switch_global_version("0.8.19", always_install=True) + solc_service.switch_global_version("0.8.19") # Should call install_versions with the version (silent=False for internal call) mock_dependencies["version_manager"].resolve_version_strings.assert_called_once_with( @@ -195,7 +197,7 @@ def test_switch_invalid_version(self, solc_service, mock_dependencies): assert "0.8.99" in str(exc_info.value) def test_switch_installation_fails(self, solc_service, mock_dependencies): - """Raises InstallationError when installation fails with always_install=True.""" + """Raises InstallationError when auto-install is enabled and installation fails.""" version = SolcVersion("0.8.19") mock_dependencies["version_manager"].validate_version.return_value = version mock_dependencies["filesystem"].is_installed.return_value = False @@ -204,7 +206,7 @@ def test_switch_installation_fails(self, solc_service, mock_dependencies): mock_dependencies["artifact_manager"].install_versions.return_value = False with pytest.raises(InstallationError) as exc_info: - solc_service.switch_global_version("0.8.19", always_install=True) + solc_service.switch_global_version("0.8.19") assert "0.8.19" in str(exc_info.value) assert "Installation failed" in str(exc_info.value) @@ -224,7 +226,7 @@ def test_switch_not_available_raises_version_not_found(self, solc_service, mock_ mock_dependencies["version_manager"].get_available_versions.return_value = available with pytest.raises(VersionNotFoundError) as exc_info: - solc_service.switch_global_version("0.4.3") + solc_service.switch_global_version("0.4.3", auto_install=False) assert "0.4.3" in str(exc_info.value) # Should show first 5 available versions @@ -387,7 +389,7 @@ def test_execute_solc_auto_install_latest( solc_service.execute_solc(["--version"]) - # Should have called switch_global_version with "latest" and always_install=True + # Should have called switch_global_version with "latest" and auto_install=True mock_dependencies["version_manager"].validate_version.assert_called_with("latest") mock_subprocess.assert_called_once_with([str(binary_path), "--version"], check=True) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py new file mode 100644 index 0000000..c55bffb --- /dev/null +++ b/tests/unit/test_main.py @@ -0,0 +1,63 @@ +"""Tests for the CLI entry point in solc_select.__main__.""" + +from unittest.mock import patch + +import pytest + +from solc_select.__main__ import solc_select + + +@pytest.fixture +def patched_service(): + """Patch SolcService so we can assert on switch_global_version calls.""" + with patch("solc_select.__main__.SolcService") as service_cls: + yield service_cls.return_value + + +class TestUseCommand: + """Tests for the `solc-select use` CLI command.""" + + def test_use_defaults_to_auto_install(self, patched_service, monkeypatch): + """`solc-select use 0.8.19` auto-installs by default.""" + monkeypatch.setattr("sys.argv", ["solc-select", "use", "0.8.19"]) + + solc_select() + + patched_service.switch_global_version.assert_called_once_with( + "0.8.19", auto_install=True, silent=False + ) + + def test_use_offline_disables_auto_install(self, patched_service, monkeypatch): + """`--offline` flips auto_install to False.""" + monkeypatch.setattr("sys.argv", ["solc-select", "use", "0.8.19", "--offline"]) + + solc_select() + + patched_service.switch_global_version.assert_called_once_with( + "0.8.19", auto_install=False, silent=False + ) + + def test_use_always_install_is_deprecated_noop(self, patched_service, monkeypatch, capsys): + """The legacy `--always-install` flag still parses but warns and is a no-op.""" + monkeypatch.setattr("sys.argv", ["solc-select", "use", "0.8.19", "--always-install"]) + + solc_select() + + patched_service.switch_global_version.assert_called_once_with( + "0.8.19", auto_install=True, silent=False + ) + captured = capsys.readouterr() + assert "--always-install is deprecated" in captured.err + + def test_use_offline_overrides_legacy_always_install(self, patched_service, monkeypatch): + """`--offline` wins even if the deprecated `--always-install` is also passed.""" + monkeypatch.setattr( + "sys.argv", + ["solc-select", "use", "0.8.19", "--always-install", "--offline"], + ) + + solc_select() + + patched_service.switch_global_version.assert_called_once_with( + "0.8.19", auto_install=False, silent=False + )