Skip to content

Commit 3c1a24a

Browse files
committed
Add support for ARM64 Darwin binaries
This adds support to pull 0.8.5..0.8.23 solc binaries from https://github.com/alloy-rs/solc-builds when running on ARM64 darwin. This repository is also used by solc.nix and svm-rs.
1 parent 2a6c1ae commit 3c1a24a

File tree

4 files changed

+124
-34
lines changed

4 files changed

+124
-34
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ To automatically install and use a version, run `solc-select use <version> --alw
3030

3131
### Running on ARM (Mac M1/M2)
3232

33-
`solc` older than 0.8.24 requires Rosetta to be installed. See the FAQ on [how to install Rosetta](#oserror-errno-86-bad-cpu-type-in-executable).
33+
`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).
3434

3535
## Usage
3636

@@ -107,10 +107,10 @@ Feel free to stop by our [Slack channel](https://empirehacking.slack.com/) for h
107107

108108
On newer `solc-select` versions, this might show as `solc binaries for macOS are
109109
Intel-only. Please install Rosetta on your Mac to continue.` or `solc binaries
110-
previous to 0.8.24 for macOS are Intel-only. Please install Rosetta on your Mac
110+
previous to 0.8.5 for macOS are Intel-only. Please install Rosetta on your Mac
111111
to continue.`
112112

113-
`solc` releases earlier than 0.8.24 require Rosetta to be installed. To see
113+
`solc` releases earlier than 0.8.5 require Rosetta to be installed. Versions 0.8.5-0.8.23 run natively on ARM64, and 0.8.24+ use universal binaries. To see
114114
whether you have Rosetta installed on your Mac, run
115115

116116
```bash

solc_select/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@
2626
CRYTIC_SOLC_JSON = (
2727
"https://raw.githubusercontent.com/crytic/solc/new-list-json/linux/amd64/list.json"
2828
)
29+
30+
ALLOY_SOLC_ARTIFACTS = "https://raw.githubusercontent.com/alloy-rs/solc-builds/203ef20a24a6c2cb763e1c8c4c1836e85db2512d/macosx/aarch64/"
31+
ALLOY_SOLC_JSON = "https://raw.githubusercontent.com/alloy-rs/solc-builds/203ef20a24a6c2cb763e1c8c4c1836e85db2512d/macosx/aarch64/list.json"
32+
33+
# Alloy ARM64 version range (0.8.24+ are universal binaries)
34+
ALLOY_ARM64_MIN_VERSION = "0.8.5"
35+
ALLOY_ARM64_MAX_VERSION = "0.8.23"

solc_select/solc_select.py

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
import hashlib
44
import json
55
import os
6-
import platform
76
import re
87
import shutil
98
import subprocess
109
import sys
1110
import urllib.request
1211
from pathlib import Path
12+
from typing import Optional, Tuple
1313
from zipfile import ZipFile
1414

1515
from Crypto.Hash import keccak
1616
from packaging.version import Version
1717

1818
from .constants import (
19+
ALLOY_ARM64_MAX_VERSION,
20+
ALLOY_ARM64_MIN_VERSION,
21+
ALLOY_SOLC_ARTIFACTS,
22+
ALLOY_SOLC_JSON,
1923
ARTIFACTS_DIR,
2024
CRYTIC_SOLC_ARTIFACTS,
2125
CRYTIC_SOLC_JSON,
@@ -25,23 +29,16 @@
2529
SOLC_SELECT_DIR,
2630
WINDOWS_AMD64,
2731
)
28-
from .utils import mac_binary_is_universal, mac_can_run_intel_binaries
32+
from .utils import (
33+
get_arch,
34+
mac_binary_is_native,
35+
mac_binary_is_universal,
36+
mac_can_run_intel_binaries,
37+
)
2938

3039
Path.mkdir(ARTIFACTS_DIR, parents=True, exist_ok=True)
3140

3241

33-
def get_arch() -> str:
34-
"""Get the current system architecture."""
35-
machine = platform.machine().lower()
36-
if machine in ["x86_64", "amd64"]:
37-
return "amd64"
38-
elif machine in ["aarch64", "arm64"]:
39-
return "arm64"
40-
elif machine in ["i386", "i686"]:
41-
return "386"
42-
return machine
43-
44-
4542
def check_emulation_available() -> bool:
4643
"""Check if x86_64 emulation is available."""
4744
if get_arch() != "arm64":
@@ -90,22 +87,34 @@ def warn_about_arm64(force: bool = False) -> None:
9087
print("\n⚠️ WARNING: ARM64 Architecture Detected", file=sys.stderr)
9188
print("=" * 50, file=sys.stderr)
9289

93-
if check_emulation_available():
94-
if sys.platform == "darwin":
95-
print("✓ Rosetta 2 detected - will use emulation for x86 binaries", file=sys.stderr)
90+
show_remediation = False
91+
92+
if sys.platform == "darwin":
93+
print("✓ Native ARM64 binaries available for versions 0.8.5-0.8.23", file=sys.stderr)
94+
print("✓ Universal binaries available for versions 0.8.24+", file=sys.stderr)
95+
if check_emulation_available():
96+
print("✓ Rosetta 2 detected - will use emulation for older versions", file=sys.stderr)
97+
print(" Note: Performance will be slower for emulated versions", file=sys.stderr)
9698
else:
97-
print("✓ qemu-x86_64 detected - will use emulation for x86 binaries", file=sys.stderr)
98-
print(" Note: Performance will be slower than native execution", file=sys.stderr)
99-
else:
100-
if sys.platform == "darwin":
10199
print(
102-
"✗ solc binaries are x86_64 only, and Rosetta 2 is not available", file=sys.stderr
100+
"⚠ Rosetta 2 not available - versions prior to 0.8.5 are x86_64 only and will not work",
101+
file=sys.stderr,
103102
)
103+
show_remediation = True
104+
elif sys.platform == "linux":
105+
if check_emulation_available():
106+
print("✓ qemu-x86_64 detected - will use emulation for x86 binaries", file=sys.stderr)
107+
print(" Note: Performance will be slower than native execution", file=sys.stderr)
104108
else:
105109
print("✗ solc binaries are x86_64 only, and qemu is not installed", file=sys.stderr)
110+
show_remediation = True
111+
else:
112+
show_remediation = True
113+
114+
if show_remediation:
106115
print("\nTo use solc-select on ARM64, you can:", file=sys.stderr)
107-
print(" 1. Install qemu for x86_64 emulation:", file=sys.stderr)
108-
if sys.platform.startswith("linux"):
116+
print(" 1. Install software for x86_64 emulation:", file=sys.stderr)
117+
if sys.platform == "linux":
109118
print(" sudo apt-get install qemu-user-static # Debian/Ubuntu", file=sys.stderr)
110119
print(" sudo dnf install qemu-user-static # Fedora", file=sys.stderr)
111120
print(" sudo pacman -S qemu-user-static # Arch", file=sys.stderr)
@@ -139,8 +148,12 @@ def halt_incompatible_system(path: Path) -> None:
139148
if mac_binary_is_universal(path):
140149
return
141150

151+
# If the binary is native to this architecture, we can run it
152+
if mac_binary_is_native(path):
153+
return
154+
142155
raise argparse.ArgumentTypeError(
143-
"solc binaries previous to 0.8.24 for macOS are Intel-only. Please install Rosetta on your Mac to continue. Refer to the solc-select README for instructions."
156+
"solc binaries previous to 0.8.5 for macOS are Intel-only. Please install Rosetta on your Mac to continue. Refer to the solc-select README for instructions."
144157
)
145158
# TODO: check for Linux aarch64 (e.g. RPi), presence of QEMU+binfmt
146159

@@ -252,6 +265,14 @@ def is_older_windows(version: str) -> bool:
252265
return soliditylang_platform() == WINDOWS_AMD64 and Version(version) <= Version("0.7.1")
253266

254267

268+
def is_alloy_aarch64_version(version: str) -> bool:
269+
return (
270+
sys.platform == "darwin"
271+
and get_arch() == "arm64"
272+
and Version(ALLOY_ARM64_MIN_VERSION) <= Version(version) <= Version(ALLOY_ARM64_MAX_VERSION)
273+
)
274+
275+
255276
def verify_checksum(version: str) -> None:
256277
(sha256_hash, keccak256_hash) = get_soliditylang_checksums(version)
257278

@@ -265,16 +286,21 @@ def verify_checksum(version: str) -> None:
265286
sha256_factory.update(chunk)
266287
keccak_factory.update(chunk)
267288

268-
local_sha256_file_hash = f"0x{sha256_factory.hexdigest()}"
269-
local_keccak256_file_hash = f"0x{keccak_factory.hexdigest()}"
289+
local_sha256_file_hash = sha256_factory.hexdigest()
290+
local_keccak256_file_hash = keccak_factory.hexdigest()
270291

271-
if sha256_hash != local_sha256_file_hash or keccak256_hash != local_keccak256_file_hash:
292+
if sha256_hash != local_sha256_file_hash:
272293
raise argparse.ArgumentTypeError(
273-
f"Error: Checksum mismatch {soliditylang_platform()} - {version}"
294+
f"Error: SHA256 checksum mismatch {soliditylang_platform()} - {version}"
295+
)
296+
297+
if keccak256_hash is not None and keccak256_hash != local_keccak256_file_hash:
298+
raise argparse.ArgumentTypeError(
299+
f"Error: Keccak256 checksum mismatch {soliditylang_platform()} - {version}"
274300
)
275301

276302

277-
def get_soliditylang_checksums(version: str) -> (str, str):
303+
def get_soliditylang_checksums(version: str) -> Tuple[str, Optional[str]]:
278304
(_, list_url) = get_url(version=version)
279305
# pylint: disable=consider-using-with
280306
list_json = urllib.request.urlopen(list_url).read()
@@ -286,7 +312,16 @@ def get_soliditylang_checksums(version: str) -> (str, str):
286312
f"Error: Unable to retrieve checksum for {soliditylang_platform()} - {version}"
287313
)
288314

289-
return matches[0]["sha256"], matches[0]["keccak256"]
315+
sha256_hash = matches[0]["sha256"]
316+
keccak256_hash = matches[0].get("keccak256")
317+
318+
# Normalize checksums by removing 0x prefix if present
319+
if sha256_hash and sha256_hash.startswith("0x"):
320+
sha256_hash = sha256_hash[2:]
321+
if keccak256_hash and keccak256_hash.startswith("0x"):
322+
keccak256_hash = keccak256_hash[2:]
323+
324+
return sha256_hash, keccak256_hash
290325

291326

292327
def get_url(version: str = "", artifact: str = "") -> (str, str):
@@ -296,6 +331,18 @@ def get_url(version: str = "", artifact: str = "") -> (str, str):
296331
CRYTIC_SOLC_ARTIFACTS + artifact,
297332
CRYTIC_SOLC_JSON,
298333
)
334+
elif sys.platform == "darwin" and get_arch() == "arm64":
335+
if version != "" and is_alloy_aarch64_version(version):
336+
return (
337+
ALLOY_SOLC_ARTIFACTS + artifact,
338+
ALLOY_SOLC_JSON,
339+
)
340+
else:
341+
# Fall back to Intel binaries for versions outside supported range
342+
return (
343+
f"https://binaries.soliditylang.org/{MACOSX_AMD64}/{artifact}",
344+
f"https://binaries.soliditylang.org/{MACOSX_AMD64}/list.json",
345+
)
299346
return (
300347
f"https://binaries.soliditylang.org/{soliditylang_platform()}/{artifact}",
301348
f"https://binaries.soliditylang.org/{soliditylang_platform()}/list.json",
@@ -370,6 +417,19 @@ def get_available_versions() -> [str]:
370417
github_json = urllib.request.urlopen(list_url).read()
371418
additional_linux_versions = json.loads(github_json)["releases"]
372419
available_releases.update(additional_linux_versions)
420+
elif sys.platform == "darwin" and get_arch() == "arm64":
421+
# Fetch Alloy versions for ARM64 Darwin
422+
alloy_json = urllib.request.urlopen(ALLOY_SOLC_JSON).read()
423+
alloy_releases = json.loads(alloy_json)["releases"]
424+
# Filter to only include versions in the supported range (0.8.24+ are already universal)
425+
filtered_alloy_releases = {
426+
version: release
427+
for version, release in alloy_releases.items()
428+
if Version(ALLOY_ARM64_MIN_VERSION)
429+
<= Version(version)
430+
<= Version(ALLOY_ARM64_MAX_VERSION)
431+
}
432+
available_releases.update(filtered_alloy_releases)
373433

374434
return available_releases
375435

solc_select/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
from packaging.version import Version
88

99

10+
def get_arch() -> str:
11+
"""Get the current system architecture."""
12+
machine = platform.machine().lower()
13+
if machine in ["x86_64", "amd64"]:
14+
return "amd64"
15+
elif machine in ["aarch64", "arm64"]:
16+
return "arm64"
17+
elif machine in ["i386", "i686"]:
18+
return "386"
19+
return machine
20+
21+
1022
def mac_binary_is_universal(path: Path):
1123
"""Check if the Mac binary is Universal or not. Will throw an exception if run on non-macOS."""
1224
assert sys.platform == "darwin"
@@ -17,6 +29,17 @@ def mac_binary_is_universal(path: Path):
1729
return result.returncode == 0 and is_universal
1830

1931

32+
def mac_binary_is_native(path: Path):
33+
"""Check if the Mac binary matches the current system architecture. Will throw an exception if run on non-macOS."""
34+
assert sys.platform == "darwin"
35+
result = subprocess.run(["/usr/bin/file", str(path)], capture_output=True, check=False)
36+
output = result.stdout.decode()
37+
38+
arch_in_file = "arm64" if get_arch() == "arm64" else "x86_64"
39+
is_native = "Mach-O" in output and arch_in_file in output
40+
return result.returncode == 0 and is_native
41+
42+
2043
def mac_can_run_intel_binaries() -> bool:
2144
"""Check if the Mac is Intel or M1 with available Rosetta. Will throw an exception if run on non-macOS."""
2245
assert sys.platform == "darwin"

0 commit comments

Comments
 (0)