Skip to content
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
13 changes: 5 additions & 8 deletions solc_select/constants.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import os
from pathlib import Path

# DIRs path
if "VIRTUAL_ENV" in os.environ:
HOME_DIR = Path(os.environ["VIRTUAL_ENV"])
else:
HOME_DIR = Path.home()
SOLC_SELECT_DIR = HOME_DIR.joinpath(".solc-select")
ARTIFACTS_DIR = SOLC_SELECT_DIR.joinpath("artifacts")
# Directory paths
HOME_DIR = Path(os.environ.get("VIRTUAL_ENV", Path.home()))
SOLC_SELECT_DIR = HOME_DIR / ".solc-select"
ARTIFACTS_DIR = SOLC_SELECT_DIR / "artifacts"

# CLI Commands
# CLI commands
INSTALL_COMMAND = "install"
USE_COMMAND = "use"
VERSIONS_COMMAND = "versions"
Expand Down
39 changes: 19 additions & 20 deletions solc_select/infrastructure/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,19 @@ def get_current_version(self) -> SolcVersion | None:
return None

global_version_file = self.config_dir / "global-version"
if global_version_file.exists():
try:
with open(global_version_file, encoding="utf-8") as f:
return SolcVersion.parse(f.read().strip())
except (OSError, ValueError):
return None
if not global_version_file.exists():
return None

return None
try:
version_text = global_version_file.read_text(encoding="utf-8").strip()
return SolcVersion.parse(version_text)
except (OSError, ValueError):
return None

def set_global_version(self, version: SolcVersion) -> None:
"""Set the global version."""
global_version_file = self.config_dir / "global-version"
with open(global_version_file, "w", encoding="utf-8") as f:
f.write(str(version))
global_version_file.write_text(str(version), encoding="utf-8")

def get_version_source(self) -> str:
"""Get the source of the current version setting."""
Expand Down Expand Up @@ -77,17 +76,17 @@ def get_installed_versions(self) -> list[SolcVersion]:

installed = []
for item in self.artifacts_dir.iterdir():
if item.is_dir() and item.name.startswith("solc-"):
version_str = item.name.replace("solc-", "")
try:
version = SolcVersion.parse(version_str)
if self.is_installed(version):
installed.append(version)
except ValueError:
continue

installed.sort()
return installed
if not (item.is_dir() and item.name.startswith("solc-")):
continue
version_str = item.name.removeprefix("solc-")
try:
version = SolcVersion.parse(version_str)
if self.is_installed(version):
installed.append(version)
except ValueError:
pass

return sorted(installed)

def is_installed(self, version: SolcVersion) -> bool:
"""Check if a version is installed."""
Expand Down
8 changes: 3 additions & 5 deletions solc_select/models/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,9 @@ def __post_init__(self) -> None:
raise ValueError("Download URL cannot be empty")
if not self.checksum_sha256:
raise ValueError("SHA256 checksum cannot be empty")
# Normalize by removing 0x prefix
if self.checksum_sha256.startswith("0x"):
object.__setattr__(self, "checksum_sha256", self.checksum_sha256[2:])
if self.checksum_keccak256 and self.checksum_keccak256.startswith("0x"):
object.__setattr__(self, "checksum_keccak256", self.checksum_keccak256[2:])
self.checksum_sha256 = self.checksum_sha256.removeprefix("0x")
if self.checksum_keccak256:
self.checksum_keccak256 = self.checksum_keccak256.removeprefix("0x")

@property
def is_zip_archive(self) -> bool:
Expand Down
10 changes: 7 additions & 3 deletions solc_select/models/platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,22 @@ def __post_init__(self) -> None:

def get_capability(self) -> PlatformCapability:
"""Get the capability declaration for this platform."""
key = f"{self.os_type}-{self.architecture}"
return CAPABILITY_REGISTRY.get(key, self._create_default_capability())
identifier = self.to_identifier()
return CAPABILITY_REGISTRY.get(str(identifier), self._create_default_capability())

def _create_default_capability(self) -> PlatformCapability:
"""Create default capability (native-only, no emulation)."""
platform_id = PlatformIdentifier(self.os_type, self.architecture)
platform_id = self.to_identifier()
return PlatformCapability(
host_platform=platform_id,
native_support=platform_id,
emulation_capabilities=[],
)

def to_identifier(self) -> PlatformIdentifier:
"""Convert to a PlatformIdentifier."""
return PlatformIdentifier(self.os_type, self.architecture)

@classmethod
def current(cls) -> "Platform":
"""Get the current system platform."""
Expand Down
3 changes: 3 additions & 0 deletions solc_select/platform_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class PlatformIdentifier:
os_type: str # 'linux', 'darwin', 'windows'
architecture: str # 'amd64', 'arm64'

def __str__(self) -> str:
return f"{self.os_type}-{self.architecture}"


@dataclass(frozen=True)
class EmulationCapability:
Expand Down
22 changes: 5 additions & 17 deletions solc_select/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,10 @@ def __init__(
has_latest_release: bool = False,
):
self.session = session
self._base_url = base_url
self._list_url = list_url
self.base_url = base_url
self.list_url = list_url
self._has_latest_release = has_latest_release

@property
def base_url(self) -> str:
return self._base_url

@property
def list_url(self) -> str:
return self._list_url

@lru_cache(maxsize=5) # noqa: B019
def _fetch_list_json(self) -> dict[str, Any]:
response = self.session.get(self.list_url)
Expand Down Expand Up @@ -77,14 +69,10 @@ def get_checksums(self, version: SolcVersion) -> tuple[str, str | None]:
if not matches or not matches[0]["sha256"]:
raise ValueError(f"Unable to retrieve checksum for {version}")

sha256_hash = matches[0]["sha256"]
sha256_hash = matches[0]["sha256"].removeprefix("0x")
keccak256_hash = matches[0].get("keccak256")

# Normalize checksums by removing 0x prefix if present
if sha256_hash and sha256_hash.startswith("0x"):
sha256_hash = sha256_hash[2:]
if keccak256_hash and keccak256_hash.startswith("0x"):
keccak256_hash = keccak256_hash[2:]
if keccak256_hash:
keccak256_hash = keccak256_hash.removeprefix("0x")

return sha256_hash, keccak256_hash

Expand Down
33 changes: 18 additions & 15 deletions solc_select/services/artifact_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ def verify_checksum(self, artifact: SolcArtifact, file_handle: BufferedRandom) -
if artifact.checksum_keccak256 and artifact.checksum_keccak256 != local_keccak256:
raise ChecksumMismatchError(artifact.checksum_keccak256, local_keccak256, "Keccak256")

def _download_artifact(self, artifact: SolcArtifact) -> None:
"""Download artifact and verify checksums."""
response = self.session.get(artifact.download_url, stream=True)
response.raise_for_status()

with open(artifact.file_path, "w+b", opener=partial(os.open, mode=0o664)) as f:
try:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
except KeyboardInterrupt:
if artifact.file_path.exists():
artifact.file_path.unlink(missing_ok=True)
raise

self.verify_checksum(artifact, f)

def download_and_install(self, version: SolcVersion, silent: bool = False) -> bool:
"""Download and install a Solidity compiler version."""
if self.filesystem.is_installed(version):
Expand All @@ -115,21 +132,7 @@ def download_and_install(self, version: SolcVersion, silent: bool = False) -> bo
self.filesystem.ensure_artifact_directory(version)

try:
response = self.session.get(artifact.download_url, stream=True)
response.raise_for_status()

with open(artifact.file_path, "w+b", opener=partial(os.open, mode=0o664)) as f:
try:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
except KeyboardInterrupt:
# Clean up partially downloaded file on interrupt
if artifact.file_path.exists():
artifact.file_path.unlink(missing_ok=True)
raise

self.verify_checksum(artifact, f)
self._download_artifact(artifact)

if artifact.is_zip_archive:
self._extract_zip_archive(artifact)
Expand Down
2 changes: 1 addition & 1 deletion solc_select/services/platform_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def warn_about_arm64_compatibility(self, force: bool = False) -> None:
if self.platform.architecture != "arm64":
return

warning_file = SOLC_SELECT_DIR.joinpath(".arm64_warning_shown")
warning_file = SOLC_SELECT_DIR / ".arm64_warning_shown"
if not force and warning_file.exists():
return

Expand Down
16 changes: 7 additions & 9 deletions solc_select/services/repository_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def find_repository_for_version(
if not exact or str(version) in repo.available_versions:
return repo, target_platform

platform_list = ", ".join(str(p) for p in runnable_platforms)
platform_list = ", ".join(map(str, runnable_platforms))
raise VersionNotFoundError(
str(version),
available_versions=[],
Expand Down Expand Up @@ -124,15 +124,13 @@ def _create_repository(
repository_id = manifest.repository_id

if repository_id == "soliditylang":
platform_obj = Platform(os_type=platform.os_type, architecture=platform.architecture)
platform_obj = Platform(platform.os_type, platform.architecture)
return SoliditylangRepository(platform_obj, self.session)

factories = {
"crytic": CryticRepository,
"alloy": AlloyRepository,
}
factory = factories.get(repository_id)
if factory is not None:
return factory(self.session)
if repository_id == "crytic":
return CryticRepository(self.session)

if repository_id == "alloy":
return AlloyRepository(self.session)

raise ValueError(f"Unknown repository: {repository_id}")
8 changes: 2 additions & 6 deletions solc_select/services/solc_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, platform: Platform | None = None):
)
self.platform_service = PlatformService(platform)

def get_current_version(self) -> tuple[SolcVersion | None, str]:
def get_current_version(self) -> tuple[SolcVersion, str]:
"""Get the current version and its source."""
version = self.filesystem.get_current_version()
source = self.filesystem.get_version_source()
Expand All @@ -57,8 +57,7 @@ def get_current_version(self) -> tuple[SolcVersion | None, str]:
raise NoVersionSetError()

if not self.filesystem.is_installed(version):
installed_versions = self.filesystem.get_installed_versions()
installed_strs = [str(v) for v in installed_versions]
installed_strs = [str(v) for v in self.filesystem.get_installed_versions()]
raise VersionNotInstalledError(str(version), installed_strs, source)

return version, source
Expand Down Expand Up @@ -153,9 +152,6 @@ def execute_solc(self, args: list[str]) -> None:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)

if version is None:
sys.exit(1)

binary_path = self.filesystem.get_binary_path(version)
artifact = self.artifact_manager.create_local_artifact_metadata(version)

Expand Down
12 changes: 4 additions & 8 deletions solc_select/services/version_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,10 @@ def resolve_version_strings(self, version_strings: list[str]) -> list[SolcVersio
if "all" in version_strings:
return self.get_available_versions()

versions = []
for version_str in version_strings:
if version_str == "latest":
versions.append(self.get_latest_version())
else:
versions.append(self.validate_version(version_str))

return versions
return [
self.get_latest_version() if v == "latest" else self.validate_version(v)
for v in version_strings
]

def get_installable_versions(self, installed_versions: list[SolcVersion]) -> list[SolcVersion]:
"""Get versions that can be installed (not already installed)."""
Expand Down