Skip to content

Commit 978880b

Browse files
mdittmerjoshlf
authored andcommitted
[anneal] Move setup and CI onto Nix archive
Share the exocrate installer between Anneal and v2, and have non-v2 Anneal install the same Nix-built omnibus archive that v2 already produces. The generated Lean workspace can then use the archive layout directly instead of cloning and rewriting Lake dependencies at runtime. Update Anneal CI to build the archive with Nix, install it before root tests and example checks, and run the Lean-heavy integration suite serially. The workflow no longer depends on a Docker image that pre-bakes the old setup output. gherrit-pr-id: Gefzjo7bqy3imk4ti4im5wep4jz3upvrm
1 parent b7d9acf commit 978880b

37 files changed

Lines changed: 1114 additions & 4044 deletions

File tree

.github/workflows/anneal.yml

Lines changed: 135 additions & 347 deletions
Large diffs are not rendered by default.

anneal/Cargo.lock

Lines changed: 93 additions & 1121 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

anneal/Cargo.toml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ publish = true
2121

2222
exclude = [".*", "testdata"]
2323

24+
# FIXME: Replace these placeholder archive URLs and hashes before publishing
25+
# this crate; `cargo anneal setup` uses this metadata by default when callers
26+
# do not provide a local archive.
27+
[package.metadata.exocrate.linux.x86_64]
28+
sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
29+
url = "https://example.com/linux-x86_64.tar.zst"
30+
31+
[package.metadata.exocrate.macos.x86_64]
32+
sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
33+
url = "https://example.com/macos-x86_64.tar.zst"
34+
35+
[package.metadata.exocrate.linux.aarch64]
36+
sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
37+
url = "https://example.com/linux-aarch64.tar.zst"
38+
39+
[package.metadata.exocrate.macos.aarch64]
40+
sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
41+
url = "https://example.com/macos-aarch64.tar.zst"
42+
2443
[dependencies]
2544
anyhow = "1.0.102"
2645
cargo_metadata = "0.23.1"
@@ -46,16 +65,14 @@ thiserror = "2.0.18"
4665
walkdir = "2.5.0"
4766
indicatif = { version = "0.18.4", features = ["improved_unicode"] }
4867
console = "0.16.3"
68+
exocrate = { path = "../exocrate" }
4969
sha2 = "0.10"
5070
fs2 = "0.4"
5171
toml = "0.8"
52-
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"] }
53-
dirs = "6.0"
5472
tempfile = "3.27.0"
55-
tar = "0.4"
56-
flate2 = "1.1"
5773

5874
[build-dependencies]
75+
sha2 = "0.10"
5976
toml = "0.8"
6077

6178
[dev-dependencies]
@@ -89,7 +106,7 @@ aeneas_rev = "42c0e90dacf486f7d3ed5b6cde3a9a81f04915a4"
89106

90107
# The Lean toolchain version to use. This must match the version of Lean used
91108
# by Aeneas in the `lean-toolchain` file in the commit above.
92-
lean_toolchain = "leanprover/lean4:v4.28.0-rc1"
109+
lean_toolchain = "leanprover/lean4:v4.30.0-rc2"
93110

94111
# The Rust toolchain required by Charon.
95112
#

anneal/Dockerfile

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.11-slim AS extractor
22
WORKDIR /app
3-
COPY Cargo.toml ./
3+
COPY anneal/Cargo.toml ./
44
# Remove the [workspace] section so it can be built as a standalone package.
55
# Also inject tree-sitter dependencies used by doc_gen to cache them too.
66
RUN sed '1,2d' Cargo.toml > Cargo.toml.no_workspace && \
@@ -51,23 +51,22 @@ ENV ANNEAL_INTEGRATION_TARGET_DIR=/cache/anneal_target
5151
# via a volume mount, causing dependencies to be rebuilt once in the volume.
5252
# This double-build is intentional to support both environments efficiently.
5353
ENV CARGO_TARGET_DIR=/opt/anneal_target
54-
WORKDIR /workspace
54+
WORKDIR /workspace/anneal
5555

5656
# Copy the workspace configuration (without workspace section) and lockfile.
5757
COPY --from=extractor --chown=anneal:anneal /app/Cargo.toml.no_workspace ./Cargo.toml
58-
COPY --chown=anneal:anneal Cargo.lock ./
58+
COPY --chown=anneal:anneal anneal/Cargo.lock ./
5959

60-
# Copy source files needed for setup.
61-
COPY --chown=anneal:anneal src/setup.rs src/util.rs ./src/
62-
COPY --chown=anneal:anneal build.rs ./
63-
COPY --chown=anneal:anneal tools/prune_mathlib.py ./tools/
60+
# Copy source files needed to compile dependency-heavy modules.
61+
COPY --chown=anneal:anneal anneal/src/setup.rs ./src/
62+
COPY --chown=anneal:anneal exocrate/Cargo.toml /workspace/exocrate/Cargo.toml
63+
COPY --chown=anneal:anneal exocrate/src /workspace/exocrate/src
64+
COPY --chown=anneal:anneal anneal/build.rs ./
6465

65-
# Create a minimal `main.rs` that runs setup, and a dummy test file.
66-
# This allows us to build all dependencies (including dev-dependencies)
67-
# in the image while still being able to run `setup`. It also avoids taking
68-
# a dependency on *most* source files, avoiding unnecessary container rebuilds
69-
# when source files change.
70-
RUN echo 'mod setup; mod util; fn main() -> anyhow::Result<()> { setup::run_setup() }' > src/main.rs && \
66+
# Create a minimal `main.rs` and a dummy test file.
67+
# This allows us to build dependencies in the image without copying the full
68+
# source tree, avoiding unnecessary container rebuilds when source files change.
69+
RUN echo 'mod setup; fn main() {}' > src/main.rs && \
7170
mkdir tests && echo 'fn main() {}' > tests/integration.rs
7271

7372
# Build dependencies to cache them in the image.
@@ -78,19 +77,9 @@ RUN echo 'mod setup; mod util; fn main() -> anyhow::Result<()> { setup::run_setu
7877
RUN cargo build && \
7978
cargo build --tests
8079

81-
# Pre-build Lean libraries using the minimal binary.
82-
#
83-
# We also generate the import graph and prune unused Mathlib artifacts here
84-
# to reduce the image size. This must be done after `cargo run` because that
85-
# command fetches and builds the dependencies.
80+
# Use a stable shared install root inside test containers. CI installs the
81+
# Nix-built archive here before invoking tests or examples.
8682
ENV ANNEAL_TOOLCHAIN_DIR=/opt/anneal_toolchain
87-
RUN cargo run && \
88-
chmod -R u+w /opt/anneal_toolchain && \
89-
LEAN_DIR=$(find /opt/anneal_toolchain/.anneal/toolchain/ -type d -path "*/backends/lean" | head -n 1) && \
90-
cd $LEAN_DIR && \
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
9483

9584
# Ensure the integration target directory exists.
9685
RUN mkdir -p /cache/anneal_target

anneal/build.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::fs;
22

3+
use sha2::{Digest as _, Sha256};
4+
35
/// This build script reads toolchain versioning metadata from `Cargo.toml` and
46
/// exposes it to the Rust compiler via environment variables.
57
///
@@ -17,6 +19,8 @@ fn main() {
1719
let cargo_toml: toml::Value =
1820
toml::from_str(&cargo_toml_content).expect("failed to parse Cargo.toml");
1921

22+
println!("cargo:rustc-env=ANNEAL_EXOCRATE_VERSION_SLUG={}", exocrate_version_slug());
23+
2024
// We expect the metadata to be under `[package.metadata.build_rs]`.
2125
let build_rs_metadata = cargo_toml
2226
.get("package")
@@ -72,4 +76,54 @@ fn main() {
7276
}
7377
}
7478
}
79+
80+
if let Some(exocrate_metadata) = cargo_toml
81+
.get("package")
82+
.and_then(|p| p.get("metadata"))
83+
.and_then(|m| m.get("exocrate"))
84+
.and_then(|e| e.as_table())
85+
{
86+
for (os, os_table) in exocrate_metadata {
87+
let Some(os_table) = os_table.as_table() else {
88+
continue;
89+
};
90+
for (arch, config) in os_table {
91+
let Some(config) = config.as_table() else {
92+
continue;
93+
};
94+
let env_platform = format!("{}_{}", os, arch).to_uppercase();
95+
96+
for key in ["sha256", "url"] {
97+
let value = config.get(key).and_then(|v| v.as_str()).unwrap_or_else(|| {
98+
panic!("package.metadata.exocrate.{os}.{arch}.{key} must be a string")
99+
});
100+
println!(
101+
"cargo:rustc-env=ANNEAL_EXOCRATE_{}_{}={}",
102+
env_platform,
103+
key.to_uppercase(),
104+
value
105+
);
106+
}
107+
}
108+
}
109+
}
110+
}
111+
112+
fn exocrate_version_slug() -> String {
113+
let mut hasher = Sha256::new();
114+
for path in ["Cargo.toml", "Cargo.lock"] {
115+
hasher.update(path.as_bytes());
116+
hasher.update(fs::read(path).unwrap_or_else(|err| panic!("failed to read {path}: {err}")));
117+
}
118+
hasher.update(
119+
std::env::var("CARGO_CFG_TARGET_OS")
120+
.expect("CARGO_CFG_TARGET_OS is set by Cargo")
121+
.as_bytes(),
122+
);
123+
hasher.update(
124+
std::env::var("CARGO_CFG_TARGET_ARCH")
125+
.expect("CARGO_CFG_TARGET_ARCH is set by Cargo")
126+
.as_bytes(),
127+
);
128+
format!("{:x}", hasher.finalize())
75129
}

anneal/docker.sh

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ if [ "${DOCKER_CMD[0]}" = "podman" ]; then
3434
ARG_GID=1001
3535
fi
3636

37-
# Resolve the directory paths required to mount the workspace volume into the
38-
# container. This assumes that the script is located in the root of the
39-
# `anneal` workspace.
40-
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
37+
# Resolve the directory paths required to build the image and mount the
38+
# workspace volume into the container. The Docker build context is the repo
39+
# worktree root so the Dockerfile can copy both `anneal/` and the root-level
40+
# `exocrate/` crate.
41+
ANNEAL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
42+
WORKTREE_DIR="$(cd "$ANNEAL_DIR/.." >/dev/null 2>&1 && pwd)"
4143
# To avoid pollution between different git worktrees, we generate a unique
4244
# worktree ID and store it in a file. This ensures each worktree gets its own
4345
# isolated cache volume and Docker image tag.
44-
WORKTREE_ID_FILE="$DIR/.docker-worktree-id"
46+
WORKTREE_ID_FILE="$ANNEAL_DIR/.docker-worktree-id"
4547
if [ ! -f "$WORKTREE_ID_FILE" ]; then
4648
if command -v uuidgen >/dev/null 2>&1; then
4749
uuidgen > "$WORKTREE_ID_FILE"
@@ -63,7 +65,7 @@ BUILD_CACHE=$(mktemp)
6365
# and capture its output to prevent terminal spam during cached runs. If the
6466
# build takes longer than 5 seconds, we assume a rebuild is occurring and
6567
# stream the output to the terminal so the developer knows why it is pausing.
66-
DOCKER_BUILDKIT=1 "${DOCKER_CMD[@]}" build --progress=plain --build-arg UID=$ARG_UID --build-arg GID=$ARG_GID -t "$IMAGE_NAME" -f "$DIR/Dockerfile" "$DIR" > "$BUILD_CACHE" 2>&1 &
68+
DOCKER_BUILDKIT=1 "${DOCKER_CMD[@]}" build --progress=plain --build-arg UID=$ARG_UID --build-arg GID=$ARG_GID -t "$IMAGE_NAME" -f "$ANNEAL_DIR/Dockerfile" "$WORKTREE_DIR" > "$BUILD_CACHE" 2>&1 &
6769
BUILD_PID=$!
6870

6971
# Wait up to 5 seconds for the build to finish silently.
@@ -185,10 +187,10 @@ done
185187
# Determine the user's current working directory relative to the repository.
186188
# This path is passed to Docker so that the container executes the requested
187189
# command in the same relative directory as the caller.
188-
REL_PATH=$(realpath --relative-to="$DIR" "$(pwd)" 2>/dev/null || echo ".")
190+
REL_PATH=$(realpath --relative-to="$WORKTREE_DIR" "$(pwd)" 2>/dev/null || echo "anneal")
189191
WORKDIR="/workspace/$REL_PATH"
190192

191193
exec "${DOCKER_CMD[@]}" run "${DOCKER_FLAGS[@]}" \
192-
-v "$DIR:/workspace" \
194+
-v "$WORKTREE_DIR:/workspace" \
193195
-w "$WORKDIR" \
194196
"$IMAGE_NAME" "$@"

0 commit comments

Comments
 (0)