diff --git a/README.md b/README.md index 9682856..e838269 100644 --- a/README.md +++ b/README.md @@ -14,24 +14,28 @@ The versioned binaries are stored in `~/.solc-select/artifacts/`. ## Installation -### Using pip +### Using uv ```bash -pip3 install solc-select +uv tool install solc-select ``` -### Using uv (recommended for development) +### Using pip ```bash -uv tool install solc-select +pip3 install solc-select ``` To automatically install and use a version, run `solc-select use --always-install`. -### Running on ARM (Mac M1/M2) +### Running on macOS ARM (Mac M1 and newer) `solc-select` provides native ARM64 support for versions 0.8.5-0.8.23, and universal binary support for 0.8.24+. For versions older than 0.8.5, Rosetta is required. See the FAQ on [how to install Rosetta](#oserror-errno-86-bad-cpu-type-in-executable). +### Running on Linux ARM + +`solc-select` provides native ARM64 support for versions 0.8.31+. For versions older than 0.8.31, QEMU (`qemu-x86_64`) is required. Additionally, a libc binary (e.g., from package `libc6-amd64-cross`) and adequate `QEMU_LD_PREFIX` environment variable (e.g., `QEMU_LD_PREFIX=/usr/x86_64-linux-gnu`) might be necessary to execute certain solc binaries that are not built statically. + ## Usage ### Quick Start @@ -160,7 +164,7 @@ pip3 install solc-select==0.2.0 solc-select install ``` -### `solc-select` version changes, but `solc --version does not match` +### `solc-select` version changes, but `solc --version` does not match Users seem to be experiencing situations in which the following command is successful: diff --git a/solc_select/__main__.py b/solc_select/__main__.py index 90c88dd..22640d3 100644 --- a/solc_select/__main__.py +++ b/solc_select/__main__.py @@ -26,8 +26,7 @@ def solc_select_install(service: SolcService, versions: list[str]) -> None: """Handle the install command.""" if not versions: print("Available versions to install:") - installable = service.get_installable_versions() - for version in installable: + for version in service.get_installable_versions(): print(str(version)) else: success = service.install_versions(versions) @@ -42,21 +41,20 @@ def solc_select_use(service: SolcService, version: str, always_install: bool) -> def solc_select_versions(service: SolcService) -> None: """Handle the versions command.""" installed = service.get_installed_versions() - if installed: - try: - current_version, source = service.get_current_version() - except (NoVersionSetError, VersionNotInstalledError): - # No version is currently set or not installed, that's ok for the versions command - current_version = None - - installed_strs = [str(v) for v in installed] - for version_str in sort_versions(installed_strs): - if current_version and version_str == str(current_version): - print(f"{version_str} (current, set by {source})") - else: - print(version_str) - else: + if not installed: print("No solc version installed. Run `solc-select install --help` for more information") + return + + try: + current_version, source = service.get_current_version() + except (NoVersionSetError, VersionNotInstalledError): + current_version = None + + for version_str in sort_versions([str(v) for v in installed]): + if current_version and version_str == str(current_version): + print(f"{version_str} (current, set by {source})") + else: + print(version_str) def solc_select_upgrade(service: SolcService) -> None: @@ -72,7 +70,6 @@ def create_parser() -> argparse.ArgumentParser: dest="command", ) - # Install command parser_install = subparsers.add_parser( INSTALL_COMMAND, help="list and install available solc versions" ) @@ -83,20 +80,17 @@ def create_parser() -> argparse.ArgumentParser: default=[], ) - # Use command parser_use = subparsers.add_parser( 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") - # Versions command parser_versions = subparsers.add_parser( VERSIONS_COMMAND, help="prints out all installed solc versions" ) parser_versions.add_argument("versions", nargs="*", help=argparse.SUPPRESS) - # Upgrade command parser_upgrade = subparsers.add_parser(UPGRADE_COMMAND, help="upgrades solc-select") parser_upgrade.add_argument("upgrade", nargs="*", help=argparse.SUPPRESS) @@ -106,25 +100,19 @@ def create_parser() -> argparse.ArgumentParser: def solc_select() -> None: parser = create_parser() args = parser.parse_args() - - # Create service instance service = SolcService() try: if args.command == INSTALL_COMMAND: solc_select_install(service, args.versions) - 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) - elif args.command == VERSIONS_COMMAND: solc_select_versions(service) - elif args.command == UPGRADE_COMMAND: solc_select_upgrade(service) - else: parser.parse_args(["--help"]) sys.exit(0) @@ -154,7 +142,7 @@ def solc_select() -> None: print(f"Error: {e}", file=sys.stderr) sys.exit(1) except KeyboardInterrupt: - print("\nOperation cancelled by user", file=sys.stderr) + print("\nOperation cancelled", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Unexpected error: {e}", file=sys.stderr) @@ -168,7 +156,7 @@ def solc() -> None: try: service.execute_solc(sys.argv[1:]) except KeyboardInterrupt: - print("\nOperation cancelled by user", file=sys.stderr) + print("\nOperation cancelled", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Error executing solc: {e}", file=sys.stderr) diff --git a/solc_select/exceptions.py b/solc_select/exceptions.py index 0645f58..5c07bb2 100644 --- a/solc_select/exceptions.py +++ b/solc_select/exceptions.py @@ -1,16 +1,9 @@ -""" -Custom exception classes for solc-select. - -This module provides a structured exception hierarchy for better error handling -and more informative error messages throughout the application. -""" +"""Custom exception classes for solc-select.""" class SolcSelectError(Exception): """Base exception for all solc-select errors.""" - pass - class VersionNotFoundError(SolcSelectError): """Raised when requested version doesn't exist or isn't available.""" @@ -26,7 +19,6 @@ def __init__( self.suggestion = suggestion message = f"Version '{version}' not found" - if available_versions: if len(available_versions) <= 5: message += f". Available versions: {', '.join(available_versions)}" @@ -77,7 +69,6 @@ def __init__(self, version: str, platform: str, min_version: str | None = None): self.min_version = min_version message = f"Version '{version}' is not supported on {platform}" - if min_version: message += f". Minimum supported version is '{min_version}'" @@ -105,7 +96,6 @@ class InstallationError(SolcSelectError): def __init__(self, version: str, reason: str): self.version = version self.reason = reason - super().__init__(f"Failed to install version '{version}': {reason}") @@ -120,10 +110,8 @@ def __init__( self.original_error = original_error message = f"Network error during {operation}" - if url: message += f" from {url}" - if original_error: message += f": {original_error!s}" @@ -136,7 +124,6 @@ class VersionResolutionError(SolcSelectError): def __init__(self, requested: str, reason: str): self.requested = requested self.reason = reason - super().__init__(f"Could not resolve version '{requested}': {reason}") diff --git a/solc_select/infrastructure/http_client.py b/solc_select/infrastructure/http_client.py index e61816b..0c97569 100644 --- a/solc_select/infrastructure/http_client.py +++ b/solc_select/infrastructure/http_client.py @@ -1,9 +1,4 @@ -""" -HTTP client configuration for solc-select. - -This module provides centralized HTTP client configuration with -retry logic and proper timeout handling. -""" +"""HTTP client configuration for solc-select.""" from collections.abc import Mapping from typing import Any @@ -38,7 +33,6 @@ def create_http_session() -> requests.Session: """Create a new HTTP session with retry logic for rate limits and server errors.""" session = requests.Session() - # Configure retry strategy for 429s and server errors retry_strategy = Retry( total=5, backoff_factor=1, diff --git a/solc_select/models/platforms.py b/solc_select/models/platforms.py index bead2b1..4d4bac1 100644 --- a/solc_select/models/platforms.py +++ b/solc_select/models/platforms.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from ..constants import LINUX_AMD64, LINUX_ARM64, MACOSX_AMD64, WINDOWS_AMD64 -from ..platform_capabilities import PlatformCapability, PlatformIdentifier +from ..platform_capabilities import CAPABILITY_REGISTRY, PlatformCapability, PlatformIdentifier @dataclass(frozen=True) @@ -25,8 +25,6 @@ def __post_init__(self) -> None: def get_capability(self) -> PlatformCapability: """Get the capability declaration for this platform.""" - from ..platform_capabilities import CAPABILITY_REGISTRY - key = f"{self.os_type}-{self.architecture}" return CAPABILITY_REGISTRY.get(key, self._create_default_capability()) diff --git a/solc_select/models/versions.py b/solc_select/models/versions.py index e32be47..d209351 100644 --- a/solc_select/models/versions.py +++ b/solc_select/models/versions.py @@ -5,14 +5,25 @@ from packaging.version import Version +class SolcVersion(Version): + """Represents a Solidity compiler version.""" + + @classmethod + def parse(cls, version_str: str) -> "SolcVersion": + """Parse a version string into a SolcVersion instance.""" + if version_str == "latest": + raise ValueError("Cannot parse 'latest' - resolve to actual version first") + return cls(version_str) + + @dataclass(frozen=True) class VersionRange: """Inclusive version range [min, max]. None means unbounded.""" - min_version: "SolcVersion | None" = None - max_version: "SolcVersion | None" = None + min_version: SolcVersion | None = None + max_version: SolcVersion | None = None - def contains(self, version: "SolcVersion") -> bool: + def contains(self, version: SolcVersion) -> bool: """Check if version is within range (inclusive).""" above_minimum = self.min_version is None or version >= self.min_version below_maximum = self.max_version is None or version <= self.max_version @@ -30,14 +41,3 @@ def exact_range(cls, min_ver: str, max_ver: str) -> "VersionRange": min_version=SolcVersion.parse(min_ver), max_version=SolcVersion.parse(max_ver), ) - - -class SolcVersion(Version): - """Represents a Solidity compiler version.""" - - @classmethod - def parse(cls, version_str: str) -> "SolcVersion": - """Parse a version string into a SolcVersion instance.""" - if version_str == "latest": - raise ValueError("Cannot parse 'latest' - resolve to actual version first") - return cls(version_str) diff --git a/solc_select/repositories.py b/solc_select/repositories.py index 6a987fa..99577b0 100644 --- a/solc_select/repositories.py +++ b/solc_select/repositories.py @@ -1,7 +1,7 @@ """Repository implementations for fetching Solidity compiler versions.""" from functools import lru_cache -from typing import TYPE_CHECKING, Any +from typing import Any import requests @@ -11,11 +11,9 @@ CRYTIC_SOLC_ARTIFACTS, CRYTIC_SOLC_JSON, ) +from .models.platforms import Platform from .models.versions import SolcVersion -if TYPE_CHECKING: - from .models.platforms import Platform - class SolcRepository: """Repository for fetching Solidity compiler version information and artifacts.""" @@ -91,7 +89,7 @@ def get_checksums(self, version: SolcVersion) -> tuple[str, str | None]: return sha256_hash, keccak256_hash -def SoliditylangRepository(platform: "Platform", session: requests.Session) -> SolcRepository: +def SoliditylangRepository(platform: Platform, session: requests.Session) -> SolcRepository: """Create a Soliditylang repository for the given platform.""" platform_key = platform.get_soliditylang_key() return SolcRepository( diff --git a/solc_select/services/__init__.py b/solc_select/services/__init__.py index 5697709..67f0666 100644 --- a/solc_select/services/__init__.py +++ b/solc_select/services/__init__.py @@ -1,6 +1 @@ -""" -Service layer for solc-select. - -This package contains business logic services that orchestrate between -the domain models and infrastructure layers. -""" +"""Service layer for solc-select.""" diff --git a/solc_select/utils.py b/solc_select/utils.py index c51a52b..00d2c02 100644 --- a/solc_select/utils.py +++ b/solc_select/utils.py @@ -2,5 +2,5 @@ def sort_versions(versions: list[str]) -> list[str]: - """Sorts a list of versions following the component order (major/minor/patch)""" + """Sort versions by major/minor/patch order.""" return sorted(versions, key=Version) diff --git a/tests/test_network_isolation.py b/tests/test_network_isolation.py index 3a1ec49..65fef1a 100644 --- a/tests/test_network_isolation.py +++ b/tests/test_network_isolation.py @@ -6,6 +6,8 @@ import requests +from solc_select.services.solc_service import SolcService + class TestNetworkIsolation: """Test that solc operations don't make unnecessary network requests.""" @@ -23,7 +25,6 @@ def test_solc_version_no_network_calls_after_install( The isolated_solc_data fixture ensures the binary path resolution works correctly by redirecting VIRTUAL_ENV to point to the isolated test directory. """ - from solc_select.services.solc_service import SolcService # Phase 1: Install version (network calls expected/allowed) service = SolcService()