Skip to content

Commit be6f199

Browse files
authored
[anneal] Don't copy Lean build artifacts from ~/.anneal (#3344)
Prior to this change, Lake verification relied on recursively copying the entire global precompiled toolchain package directory (~5,000 `.olean` files, ~5GB) into a build directory. This was obviously slow and caused massive disk bloat. In this commit, we instead: - Copy only those files which Lake will attempt to write, and symlink all other files. In practice, this means that only the smallest files are actually copied. (Note: Copying is necessary *at all* because Lake will write to some files in the directories of a package's *dependencies* if those dependencies are filesystem-local. Without copying, this would result in concurrent writes to the user-global `~/.anneal/toolchain` directory.) - Mark the `~/.anneal/toolchain/<toolchain>` directory as recursively read-only to ensure that any attempted writes fail loudly. Release 0.1.0-alpha.22. gherrit-pr-id: Gks63dnqyjzxt6s6sgowdaz63rogw5kz3
1 parent c67c8ec commit be6f199

8 files changed

Lines changed: 400 additions & 408 deletions

File tree

anneal/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

anneal/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [".", "tools/doc_gen"]
44
[package]
55
name = "cargo-anneal"
66
edition = "2024"
7-
version = "0.1.0-alpha.21"
7+
version = "0.1.0-alpha.22"
88
description = "Formally verify that your safety comments are correct."
99
categories = [
1010
"development-tools::cargo-plugins",

anneal/Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ RUN cargo build && \
8585
# command fetches and builds the dependencies.
8686
ENV ANNEAL_TOOLCHAIN_DIR=/opt/anneal_toolchain
8787
RUN cargo run && \
88+
chmod -R u+w /opt/anneal_toolchain && \
8889
LEAN_DIR=$(find /opt/anneal_toolchain/.anneal/toolchain/ -type d -path "*/backends/lean" | head -n 1) && \
8990
cd $LEAN_DIR && \
90-
lake exe graph imports.dot --to Aeneas,Aeneas.Std.Core,Aeneas.Std.WP,Aeneas.Tactic.Solver.ScalarTac,Aeneas.Std.Scalar.Core --include-deps && \
91-
python3 /workspace/tools/prune_mathlib.py imports.dot .lake/packages/mathlib
91+
lake exe graph /workspace/imports.dot --to Aeneas,Aeneas.Std.Core,Aeneas.Std.WP,Aeneas.Tactic.Solver.ScalarTac,Aeneas.Std.Scalar.Core --include-deps && \
92+
python3 /workspace/tools/prune_mathlib.py /workspace/imports.dot ../../packages/mathlib && \
93+
chmod -R a-w /opt/anneal_toolchain
9294

9395
# Ensure the integration target directory exists.
9496
RUN mkdir -p /cache/anneal_target

anneal/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl std::ops::Div<PositiveUsize> for usize {
109109
Install Anneal and its required toolchains (Charon and Aeneas):
110110

111111
```bash
112-
cargo install cargo-anneal@0.1.0-alpha.21
112+
cargo install cargo-anneal@0.1.0-alpha.22
113113
cargo anneal setup
114114
```
115115

anneal/src/aeneas.rs

Lines changed: 244 additions & 98 deletions
Large diffs are not rendered by default.

anneal/src/setup.rs

Lines changed: 122 additions & 227 deletions
Large diffs are not rendered by default.

anneal/src/util.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::path::PathBuf;
1+
use std::path::{Path, PathBuf};
22

33
use anyhow::{Context, Result};
44
use fs2::FileExt;
5+
use walkdir::WalkDir;
56

67
/// Represents an active, exclusive lock on a directory.
78
///
@@ -68,3 +69,28 @@ impl DirLock {
6869
}
6970
}
7071
}
72+
73+
/// Walks a directory recursively and replaces string patterns inside `.trace`
74+
/// files. This is used to patch non-portable paths generated by Lake.
75+
pub fn patch_trace_files(dir: &Path, replacements: &[(&str, &str)]) -> Result<()> {
76+
if dir.exists() {
77+
let walker = WalkDir::new(dir).into_iter();
78+
for entry in walker {
79+
let entry = entry.context("Failed to walk directory for trace patching")?;
80+
let path = entry.path();
81+
if path.is_file() && path.extension().map_or(false, |ext| ext == "trace") {
82+
let content = std::fs::read_to_string(path)
83+
.with_context(|| format!("Failed to read trace file {:?}", path))?;
84+
let mut new_content = content.clone();
85+
for (from, to) in replacements {
86+
new_content = new_content.replace(from, to);
87+
}
88+
if new_content != content {
89+
std::fs::write(path, new_content)
90+
.with_context(|| format!("Failed to write trace file {:?}", path))?;
91+
}
92+
}
93+
}
94+
}
95+
Ok(())
96+
}

anneal/tests/integration.rs

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -274,63 +274,6 @@ impl TestContext {
274274
Some(temp)
275275
};
276276

277-
let lean_root = sandbox_root.join("target/anneal/anneal_test_target/lean");
278-
fs::create_dir_all(&lean_root)?;
279-
280-
// We skip seeding the Lean workspace cache for mock setup tests because
281-
// they do not run the full verification pipeline and do not need Lean.
282-
// 1. Seed the Lean workspace cache so Lake skips Mathlib downloads.
283-
284-
// The Lean manifest dictates which dependencies Lake needs to
285-
// resolve. We copy this directly from the global cache to ensure
286-
// the test sandbox observes exactly the same dependency tree as
287-
// the precompiled artifacts. If we did not copy this lockfile,
288-
// Lake would attempt to resolve dependencies from scratch. Since
289-
// our dependencies specify floating branches rather than explicit
290-
// git hashes in their configuration files, a fresh resolution
291-
// could map to newer commits. A mismatch in a single commit hash
292-
// invalidates the shared compilation cache, causing Lean to
293-
// redundantly recompile the entire dependency tree (e.g.,
294-
// Mathlib) from source. Copying the manifest guarantees a
295-
// cache hit.
296-
let source_manifest = toolchain_path.join("backends/lean").join("lake-manifest.json");
297-
let target_manifest = lean_root.join("lake-manifest.json");
298-
if source_manifest.exists() {
299-
fs::copy(&source_manifest, &target_manifest)?;
300-
let mut perms = fs::metadata(&target_manifest)?.permissions();
301-
#[allow(clippy::permissions_set_readonly_false)]
302-
perms.set_readonly(false);
303-
fs::set_permissions(&target_manifest, perms)?;
304-
305-
// Inject aeneas dependency into manifest
306-
if let Ok(content) = fs::read_to_string(&target_manifest) {
307-
if let Ok(mut json) = serde_json::from_str::<serde_json::Value>(&content) {
308-
if let Some(packages) = json.get_mut("packages").and_then(|v| v.as_array_mut())
309-
{
310-
let aeneas_url =
311-
format!("file://{}/backends/lean", toolchain_path.display());
312-
let entry = serde_json::json!({
313-
"url": aeneas_url,
314-
"type": "git",
315-
"name": "aeneas",
316-
"subDir": null,
317-
"scope": "",
318-
"rev": "main",
319-
"inputRev": "main",
320-
"inherited": false,
321-
"configFile": "lakefile.lean",
322-
"manifestFile": "lake-manifest.json"
323-
});
324-
packages.push(entry);
325-
326-
if let Ok(new_content) = serde_json::to_string_pretty(&json) {
327-
let _ = fs::write(&target_manifest, new_content);
328-
}
329-
}
330-
}
331-
}
332-
}
333-
334277
// Copy extra inputs based on config.
335278
for extra in &config.extra_inputs {
336279
let extra_path = test_case_root.join(extra);
@@ -454,9 +397,6 @@ echo "---END-INVOCATION---" >> "{}"
454397
cmd.env("ELAN_HOME", elan_home);
455398
}
456399

457-
let toolchain_path = get_toolchain_path();
458-
let lean_backend_dir = toolchain_path.join("backends/lean");
459-
460400
// Resolve Mocks
461401

462402
// Re-organizing execution flow:
@@ -521,11 +461,6 @@ echo "---END-INVOCATION---" >> "{}"
521461
cmd.env("ANNEAL_FORCE_TTY", "1");
522462
cmd.env("FORCE_COLOR", "1");
523463

524-
cmd.env("ANNEAL_INTEGRATION_TEST_LEAN_CACHE_DIR", &lean_backend_dir);
525-
// Set `LAKE_CACHE_DIR` to point to the global cache in the toolchain
526-
// directory to share build artifacts across tests and avoid redundant
527-
// recompilation.
528-
cmd.env("LAKE_CACHE_DIR", toolchain_path.join("lake-cache"));
529464
cmd.env("ANNEAL_USE_PATH_FOR_TOOLS", "1");
530465
cmd.env("RAYON_NUM_THREADS", "1");
531466

@@ -544,18 +479,6 @@ echo "---END-INVOCATION---" >> "{}"
544479
cmd.env("PATH", new_path);
545480
cmd.env("RUSTFLAGS", rustflags);
546481

547-
// Configure git to trust all directories in this test sandbox
548-
// Configure Git to trust all directories within this test sandbox.
549-
// This is required because tests run as the host user but may access
550-
// files created by the `anneal` user in the Docker image, triggering
551-
// Git's 'dubious ownership' security check.
552-
let status = std::process::Command::new("git")
553-
.args(["config", "--global", "--add", "safe.directory", "*"])
554-
.env("HOME", &self.home_dir)
555-
.status()
556-
.expect("Failed to run git config");
557-
assert!(status.success(), "git config failed");
558-
559482
// Redirect HOME to the persistent home directory within the sandbox.
560483
// This ensures that the toolchain is looked up and potentially
561484
// repaired/reinstalled in a location that is isolated from the

0 commit comments

Comments
 (0)