Skip to content

Commit 61e46b9

Browse files
committed
[anneal] In setup, recursively cache Lean sources
Initialize all transitive Lean library dependencies of the Aeneas Lean library as local Git repositories, and rewrite dependencies to point to these as filesystem-local Git remotes. During `verify`, `lake build` clones any source code it doesn't already have access to. This ensures that this at least clones from the local filesystem instead of from the internet. gherrit-pr-id: Ghmd3zurxjuy6q66eay4blnbt7sfg7wlz
1 parent 11515dc commit 61e46b9

5 files changed

Lines changed: 609 additions & 2 deletions

File tree

anneal/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ RUN cargo build && \
8585
# command fetches and builds the dependencies.
8686
ENV ANNEAL_TOOLCHAIN_DIR=/opt/anneal_toolchain
8787
RUN cargo run && \
88-
LEAN_DIR=$(find /opt/anneal_toolchain/.anneal/toolchain/ -type d -name "lean" | head -n 1) && \
88+
LEAN_DIR=$(find /opt/anneal_toolchain/.anneal/toolchain/ -type d -path "*/backends/lean" | head -n 1) && \
8989
cd $LEAN_DIR && \
9090
lake exe graph imports.dot --to Aeneas,Aeneas.Std.Core,Aeneas.Std.WP,Aeneas.Tactic.Solver.ScalarTac,Aeneas.Std.Scalar.Core --include-deps && \
9191
python3 /workspace/tools/prune_mathlib.py imports.dot .lake/packages/mathlib

anneal/src/aeneas.rs

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ use std::{
1414
fmt::Write,
1515
fs,
1616
io::{BufRead, BufReader},
17-
process::Stdio,
17+
path::Path,
18+
process::{Command, Stdio},
1819
sync::{Arc, Mutex},
1920
};
2021

2122
use anyhow::{Context, Result, bail};
2223
use indicatif::{ProgressBar, ProgressStyle};
24+
use walkdir::WalkDir;
2325

2426
use crate::{generate, resolve::LockedRoots, scanner::AnnealArtifact, setup::Tool};
2527

@@ -376,6 +378,193 @@ pub fn generate_lean_workspace(roots: &LockedRoots, artifacts: &[AnnealArtifact]
376378
write_if_changed(&lean_generated_root.join("Generated.lean"), &generated_imports)
377379
.context("Failed to write Generated.lean")?;
378380

381+
// 1. Write Manifest
382+
println!("Writing modified manifest to generated workspace...");
383+
let lean_root = roots.lean_root();
384+
let toolchain = crate::setup::Toolchain::resolve()?;
385+
let toolchain_manifest_path =
386+
toolchain.root.join("backends").join("lean").join("lake-manifest.json");
387+
let user_manifest_path = lean_root.join("lake-manifest.json");
388+
389+
if toolchain_manifest_path.exists() {
390+
let content = fs::read_to_string(&toolchain_manifest_path)
391+
.context("Failed to read toolchain manifest")?;
392+
let mut manifest: serde_json::Value =
393+
serde_json::from_str(&content).context("Failed to parse toolchain manifest")?;
394+
395+
// Change name to anneal_verification
396+
manifest["name"] = serde_json::Value::String("anneal_verification".to_string());
397+
398+
// Read actual HEAD commit of Aeneas in toolchain
399+
let toolchain_aeneas_dir = toolchain.root.join("backends").join("lean");
400+
let output = Command::new("git")
401+
.args(["rev-parse", "HEAD"])
402+
.current_dir(&toolchain_aeneas_dir)
403+
.output()
404+
.context("Failed to run `git rev-parse HEAD` in toolchain Aeneas")?;
405+
406+
if !output.status.success() {
407+
bail!("`git rev-parse HEAD` failed in toolchain Aeneas");
408+
}
409+
let aeneas_rev = String::from_utf8(output.stdout)
410+
.context("Failed to parse git output")?
411+
.trim()
412+
.to_string();
413+
414+
// Inject aeneas dependency
415+
let aeneas_dep = serde_json::json!({
416+
"configFile": "lakefile.lean",
417+
"inherited": false,
418+
"inputRev": "main",
419+
"manifestFile": "lake-manifest.json",
420+
"name": "aeneas",
421+
"rev": aeneas_rev,
422+
"scope": "",
423+
"subDir": null,
424+
"type": "git",
425+
"url": format!("file://{}", toolchain_aeneas_dir.to_str().unwrap())
426+
});
427+
428+
if let Some(packages) = manifest["packages"].as_array_mut() {
429+
packages.insert(0, aeneas_dep);
430+
} else {
431+
bail!("Manifest packages is not an array");
432+
}
433+
434+
let new_content = serde_json::to_string_pretty(&manifest)
435+
.context("Failed to serialize modified manifest")?;
436+
fs::write(&user_manifest_path, new_content).context("Failed to write user manifest")?;
437+
} else {
438+
bail!("Toolchain manifest missing at {}", toolchain_manifest_path.display());
439+
}
440+
441+
// 2. Materialize Packages (Manual Copy)
442+
println!("Materializing packages by copying from toolchain...");
443+
let user_project_packages = lean_root.join(".lake").join("packages");
444+
445+
// Ensure .lake/packages exists
446+
fs::create_dir_all(&user_project_packages)
447+
.context("Failed to create .lake/packages directory")?;
448+
449+
// Copy Aeneas
450+
let toolchain_aeneas_dir = toolchain.root.join("backends").join("lean");
451+
let user_aeneas_dir = user_project_packages.join("aeneas");
452+
453+
if toolchain_aeneas_dir.exists() {
454+
if user_aeneas_dir.exists() {
455+
fs::remove_dir_all(&user_aeneas_dir)
456+
.context("Failed to remove existing Aeneas directory in workspace")?;
457+
}
458+
let status = Command::new("cp")
459+
.args(["-r", toolchain_aeneas_dir.to_str().unwrap(), user_aeneas_dir.to_str().unwrap()])
460+
.status()
461+
.context("Failed to copy Aeneas directory")?;
462+
if !status.success() {
463+
bail!("Failed to copy Aeneas directory from toolchain to workspace");
464+
}
465+
466+
// Add Git remote 'origin' to match manifest
467+
let status = Command::new("git")
468+
.args([
469+
"remote",
470+
"add",
471+
"origin",
472+
&format!("file://{}", toolchain_aeneas_dir.to_str().unwrap()),
473+
])
474+
.current_dir(&user_aeneas_dir)
475+
.status()
476+
.context("Failed to run `git remote add` in Aeneas clone")?;
477+
if !status.success() {
478+
bail!("`git remote add` failed in Aeneas clone");
479+
}
480+
}
481+
482+
// Copy dependencies
483+
let toolchain_packages_dir = toolchain.root.join("packages");
484+
if toolchain_packages_dir.exists() {
485+
for entry in fs::read_dir(&toolchain_packages_dir)
486+
.context("Failed to read toolchain packages directory")?
487+
{
488+
let entry = entry.context("Failed to read directory entry")?;
489+
let path = entry.path();
490+
if path.is_dir() {
491+
let pkg_name = path.file_name().unwrap().to_str().unwrap();
492+
let user_pkg_dir = user_project_packages.join(pkg_name);
493+
494+
if user_pkg_dir.exists() {
495+
fs::remove_dir_all(&user_pkg_dir)
496+
.context("Failed to remove existing package directory in workspace")?;
497+
}
498+
let status = Command::new("cp")
499+
.args(["-r", path.to_str().unwrap(), user_pkg_dir.to_str().unwrap()])
500+
.status()
501+
.context("Failed to copy package directory")?;
502+
if !status.success() {
503+
bail!("Failed to copy package directory for {}", pkg_name);
504+
}
505+
506+
// Update Git remote URL to match manifest
507+
let status = Command::new("git")
508+
.args([
509+
"remote",
510+
"set-url",
511+
"origin",
512+
&format!("file://{}", path.to_str().unwrap()),
513+
])
514+
.current_dir(&user_pkg_dir)
515+
.status()
516+
.context("Failed to run `git remote set-url` in package clone")?;
517+
if !status.success() {
518+
bail!("`git remote set-url` failed in package clone for {}", pkg_name);
519+
}
520+
}
521+
}
522+
}
523+
524+
// 3. Post-Process Traces in the Clone
525+
println!("Post-processing traces in the clone...");
526+
let toolchain_aeneas = toolchain.root.join("backends").join("lean");
527+
let toolchain_aeneas_str = toolchain_aeneas.to_str().unwrap();
528+
let user_project_aeneas = user_project_packages.join("aeneas");
529+
let user_project_aeneas_str = user_project_aeneas.to_str().unwrap();
530+
let toolchain_packages_str = toolchain_packages_dir.to_str().unwrap();
531+
let user_project_packages_str = user_project_packages.to_str().unwrap();
532+
533+
let process_build_dir = |dir: &Path| -> Result<()> {
534+
if dir.exists() {
535+
let walker = WalkDir::new(dir).into_iter();
536+
for entry in walker {
537+
let entry = entry.context("Failed to walk build directory")?;
538+
let path = entry.path();
539+
if path.is_file() && path.extension().map_or(false, |ext| ext == "trace") {
540+
let content = fs::read_to_string(path).context("Failed to read trace file")?;
541+
let mut new_content =
542+
content.replace(toolchain_aeneas_str, user_project_aeneas_str);
543+
new_content =
544+
new_content.replace(toolchain_packages_str, user_project_packages_str);
545+
546+
if new_content != content {
547+
fs::write(path, new_content).context("Failed to write trace file")?;
548+
}
549+
}
550+
}
551+
}
552+
Ok(())
553+
};
554+
555+
// Process all packages in .lake/packages
556+
if user_project_packages.exists() {
557+
for entry in
558+
fs::read_dir(&user_project_packages).context("Failed to read user project packages")?
559+
{
560+
let entry = entry.context("Failed to read directory entry")?;
561+
let path = entry.path();
562+
if path.is_dir() {
563+
process_build_dir(&path.join(".lake").join("build"))?;
564+
}
565+
}
566+
}
567+
379568
Ok(())
380569
}
381570

0 commit comments

Comments
 (0)