Skip to content

Commit 4c76ddd

Browse files
feat(hermes): Roll Charon rust toolchain in Cargo.toml automatically (#3172)
This patch modifies the `roll-pinned-prebuilts.py` script to detect the host platform and hook into the archive extraction phase to extract both the `charon` and `charon-driver` binaries. It executes `charon rustc -- --version` to query the rustc version compiled into Charon. It then parses this version and bumps the date by 1 day to construct the associated rustup `nightly-YYYY-MM-DD` toolchain tag. The parsed toolchain name is then populated into `package.metadata.build_rs.charon_rust_toolchain` in `Cargo.toml`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 23c63dd commit 4c76ddd

1 file changed

Lines changed: 101 additions & 3 deletions

File tree

tools/hermes/tools/roll-pinned-prebuilts.py

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
import tempfile
99
import sys
1010
import argparse
11+
import subprocess
12+
import platform
13+
import re
14+
import stat
15+
from datetime import datetime, timedelta
1116
from pathlib import Path
1217
import tomlkit
1318
from requests.exceptions import RequestException
@@ -133,6 +138,21 @@ def calculate_sha256(data):
133138
return hashlib.sha256(data).hexdigest()
134139

135140

141+
def get_host_platform():
142+
"""Detects the current host platform in the format used by Hermes."""
143+
system = platform.system().lower()
144+
machine = platform.machine().lower()
145+
146+
if system == "linux" and machine == "x86_64":
147+
return "linux-x86_64"
148+
elif system == "darwin":
149+
if machine in ["arm64", "aarch64"]:
150+
return "macos-aarch64"
151+
elif machine == "x86_64":
152+
return "macos-x86_64"
153+
return None
154+
155+
136156
def get_asset_checksums(release, repo_name):
137157
"""Downloads all platform archives and calculates required checksums.
138158
@@ -143,8 +163,17 @@ def get_asset_checksums(release, repo_name):
143163
144164
The latter allows Hermes to detect and repair corruption of individual
145165
binaries in a local toolchain installation.
166+
167+
Also extracts the `charon` binary for the host platform to query the
168+
Rust toolchain version it was built against.
146169
"""
147170
checksums = {}
171+
host_platform = get_host_platform()
172+
if host_platform is None:
173+
print(f"ERROR: Unsupported host platform: {platform.system()} {platform.machine()}")
174+
sys.exit(1)
175+
176+
charon_rust_toolchain = None
148177

149178
with tempfile.TemporaryDirectory() as tmp_dir:
150179
tmp_path = Path(tmp_dir)
@@ -195,6 +224,49 @@ def get_asset_checksums(release, repo_name):
195224
)
196225
found_binaries.add(name)
197226

227+
# If this is charon on the host platform, extract it to disk to query its rustc version.
228+
if platform == host_platform and name in ["charon", "charon-driver"]:
229+
bin_path = tmp_path / name
230+
bin_path.write_bytes(binary_data)
231+
bin_path.chmod(bin_path.stat().st_mode | stat.S_IEXEC)
232+
233+
# Now that all binaries are extracted for the host platform, run charon.
234+
if platform == host_platform:
235+
charon_bin_path = tmp_path / "charon"
236+
if charon_bin_path.exists():
237+
try:
238+
print(" Querying charon rustc version...")
239+
# charon internally calls charon-driver, so they must both be extracted.
240+
# Add the temp directory to PATH so charon can find charon-driver
241+
env = os.environ.copy()
242+
env["PATH"] = f"{tmp_path}:{env.get('PATH', '')}"
243+
result = subprocess.run(
244+
[str(charon_bin_path), "rustc", "--", "--version"],
245+
capture_output=True,
246+
text=True,
247+
env=env
248+
)
249+
if result.returncode != 0:
250+
# It might fail because rustc is not actually installed correctly,
251+
# or because of some missing library, but charon might still print
252+
# its version to stderr or stdout before failing.
253+
pass
254+
version_output = result.stdout.strip() or result.stderr.strip()
255+
match = re.search(r'rustc .* \(.* (\d{4}-\d{2}-\d{2})\)', version_output)
256+
if not match:
257+
raise CompatibilityError(
258+
f"Could not parse rustc version from charon output: '{version_output}'"
259+
)
260+
date_str = match.group(1)
261+
date_obj = datetime.strptime(date_str, "%Y-%m-%d")
262+
toolchain_date = date_obj + timedelta(days=1)
263+
charon_rust_toolchain = f"nightly-{toolchain_date.strftime('%Y-%m-%d')}"
264+
print(f" Found Charon Rust toolchain: {charon_rust_toolchain}")
265+
except subprocess.CalledProcessError as e:
266+
raise CompatibilityError(f"Failed to execute charon to get rustc version: {e}")
267+
except FileNotFoundError as e:
268+
raise CompatibilityError(f"Failed to execute charon to get rustc version: {e}")
269+
198270
# Verify that we found the expected binaries for this repo.
199271
expected = ["aeneas", "charon", "charon-driver"]
200272
missing = [b for b in expected if b not in found_binaries]
@@ -214,7 +286,12 @@ def get_asset_checksums(release, repo_name):
214286
except tarfile.TarError as e:
215287
raise CompatibilityError(f"Failed to extract archive {asset_name}: {e}")
216288

217-
return checksums
289+
if charon_rust_toolchain is None:
290+
raise CompatibilityError(
291+
f"Could not find charon binary for host platform {host_platform} in releases."
292+
)
293+
294+
return checksums, charon_rust_toolchain
218295

219296

220297
def update_cargo_toml(aeneas_meta, charon_meta, dry_run=False):
@@ -233,6 +310,7 @@ def update_cargo_toml(aeneas_meta, charon_meta, dry_run=False):
233310
print(f" Aeneas Tag: {aeneas_meta['tag']}")
234311
print(f" Lean Toolchain: {aeneas_meta['lean_toolchain']}")
235312
print(f" Charon Version: {charon_meta['version']}")
313+
print(f" Charon Rust Toolchain: {charon_meta['rust_toolchain']}")
236314
return
237315

238316
print(f"Updating {CARGO_TOML_PATH}...")
@@ -251,6 +329,7 @@ def update_cargo_toml(aeneas_meta, charon_meta, dry_run=False):
251329
metadata["build_rs"]["aeneas_rev"] = aeneas_meta["commit"]
252330
metadata["build_rs"]["lean_toolchain"] = aeneas_meta["lean_toolchain"]
253331
metadata["build_rs"]["charon_version"] = charon_meta["version"]
332+
metadata["build_rs"]["charon_rust_toolchain"] = charon_meta["rust_toolchain"]
254333

255334
# Update the hermes.dependencies section, which defines the download URLs
256335
# and checksums used by the 'cargo hermes setup' command.
@@ -296,6 +375,20 @@ def main():
296375
if args.aeneas_tag and args.charon_tag:
297376
print(f"Using forced tags: Aeneas={args.aeneas_tag}, Charon={args.charon_tag}")
298377
target_aeneas_release = get_release_by_tag(AENEAS_REPO, args.aeneas_tag)
378+
a_commit = get_commit_from_tag(args.aeneas_tag)
379+
lean_toolchain = fetch_file_content(AENEAS_REPO, a_commit, "backends/lean/lean-toolchain")
380+
if lean_toolchain:
381+
lean_toolchain = lean_toolchain.strip()
382+
print(f" Fetching checksums from Aeneas artifact...")
383+
a_checksums, charon_rust_toolchain = get_asset_checksums(target_aeneas_release, AENEAS_REPO)
384+
target_aeneas_meta = {
385+
"tag": args.aeneas_tag,
386+
"commit": a_commit,
387+
"lean_toolchain": lean_toolchain,
388+
"checksums": a_checksums,
389+
"version": args.charon_tag,
390+
"charon_rust_toolchain": charon_rust_toolchain,
391+
}
299392
else:
300393
print("Fetching releases from GitHub...")
301394
aeneas_releases = get_automated_releases(AENEAS_REPO)
@@ -340,7 +433,7 @@ def main():
340433

341434
print(f" Fetching checksums from Aeneas artifact...")
342435
try:
343-
a_checksums = get_asset_checksums(a_rel, AENEAS_REPO)
436+
a_checksums, charon_rust_toolchain = get_asset_checksums(a_rel, AENEAS_REPO)
344437
except CompatibilityError as e:
345438
print(f" Skipping {a_tag}: {e}")
346439
continue
@@ -352,6 +445,7 @@ def main():
352445
"lean_toolchain": lean_toolchain,
353446
"checksums": a_checksums,
354447
"version": charon_version,
448+
"charon_rust_toolchain": charon_rust_toolchain,
355449
}
356450
break
357451

@@ -362,8 +456,12 @@ def main():
362456
print(f"\nTargeting:")
363457
print(f" Aeneas Tag: {target_aeneas_meta['tag']}")
364458
print(f" Charon Version: {target_aeneas_meta['version']}")
459+
print(f" Charon Rust Toolchain: {target_aeneas_meta['charon_rust_toolchain']}")
365460

366-
charon_meta = {"version": target_aeneas_meta["version"]}
461+
charon_meta = {
462+
"version": target_aeneas_meta["version"],
463+
"rust_toolchain": target_aeneas_meta["charon_rust_toolchain"],
464+
}
367465
update_cargo_toml(target_aeneas_meta, charon_meta, dry_run=args.dry_run)
368466
if not args.dry_run:
369467
print("\nSuccessfully rolled toolchain updates!")

0 commit comments

Comments
 (0)