cargo.build: a persistent build cache shared by all worlds

- The cargo target folder is <cache>/cargo-target, next to Wasmtime's
  cache, which moves to <cache>/wasmtime: Wasmtime's cleanup worker
  deletes unknown entries in its folder, also Cargo's artifacts.
- Builds use the SDK at <cache>/sdk/<content hash>/, so cargo reuses
  the compiled SDK in every world. /plugins/cargo/sdk/ stays for agents.
- Tests run in the release profile and share compiled crates with the
  release build.
- The cargo and cli tests use a persistent cache in CARGO_TARGET_TMPDIR:
  warm, the cargo suite takes 11 s (was 235 s) and the cli suite 6 s
  (was 149 s).
- nix flake check runs ignored stress tests. AGENTS.md: rules for fast
  tests.
This commit is contained in:
2026-10-23 08:05:15 +01:00
parent a090e1fd57
commit 93f092e02b
11 changed files with 155 additions and 48 deletions
+34 -8
View File
@@ -3,6 +3,7 @@ use sausage::{Host, world::World};
use serde_json::{Value, json};
use std::path::{Path, PathBuf};
use tokio::sync::OnceCell;
const CACHE: &str = concat!(env!("CARGO_TARGET_TMPDIR"), "/cache");
struct TestWorld(PathBuf);
impl TestWorld {
/// Creates a fresh native build world.
@@ -45,10 +46,12 @@ fn fixture(name: &str) -> PathBuf {
}
/// Builds through the real registered native plugin.
async fn build(world: &TestWorld, name: &str) -> Result<Value, Value> {
let host = Host::start(&world.0).await.unwrap();
let host = Host::start_with_cache(&world.0, Path::new(CACHE))
.await
.unwrap();
let runner = world.0.join("state/runner with spaces");
std::os::unix::fs::symlink(env!("CARGO_BIN_EXE_sausage"), &runner).unwrap();
sausage::cargo::Cargo::with_runner(World::open(&world.0).unwrap(), runner)
sausage::cargo::Cargo::with_runner(World::open(&world.0).unwrap(), runner, Path::new(CACHE))
.unwrap()
.register(&host);
let result = host.call("cargo.build", json!({"plugin":name})).await;
@@ -93,6 +96,7 @@ async fn valid() -> &'static Valid {
.args([
"test",
"--no-run",
"--release",
"--message-format=json",
"--bin",
"cargo-valid",
@@ -100,7 +104,7 @@ async fn valid() -> &'static Valid {
"wasm32-wasip2",
"--target-dir",
])
.arg(world.0.join("state/cargo/target"))
.arg(Path::new(CACHE).join("cargo-target"))
.current_dir(world.0.join("state/cargo/work"))
.stdin(std::process::Stdio::null())
.output()
@@ -221,7 +225,16 @@ async fn manifest_and_nested_sources() {
let manifest = std::fs::read_to_string(root.join("Cargo.toml")).unwrap();
assert!(manifest.contains("workspace.package"));
assert!(manifest.contains("optional = true"));
assert!(manifest.contains("/plugins/cargo/sdk"));
let document: toml_edit::DocumentMut = manifest.parse().unwrap();
let sdk = Path::new(document["target"]["cfg(target_arch = \"wasm32\")"]["dependencies"]["sausage_plugin"]["path"].as_str().unwrap());
assert_eq!(sdk.parent().unwrap(), Path::new(CACHE).join("sdk"));
let hash = sdk.file_name().unwrap().to_str().unwrap();
assert_eq!(hash.len(), 64);
assert!(hash.bytes().all(|byte| byte.is_ascii_hexdigit()));
assert_eq!(
std::fs::read(sdk.join("src/lib.rs")).unwrap(),
std::fs::read(valid.world.0.join("plugins/cargo/sdk/src/lib.rs")).unwrap()
);
assert_eq!(
std::fs::read_to_string(root.join("src/target/data")).unwrap(),
"nested target"
@@ -235,7 +248,9 @@ async fn manifest_and_nested_sources() {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn native_discovery_and_error_paths() {
let world = TestWorld::new();
let host = Host::start(&world.0).await.unwrap();
let host = Host::start_with_cache(&world.0, Path::new(CACHE))
.await
.unwrap();
assert_eq!(
host.call("cargo.rpc.discover", json!({})).await.unwrap()["methods"][0]["name"],
"build"
@@ -264,7 +279,7 @@ async fn release_only_failure() {
"[package]\nname='release-only'\nversion='0.1.0'\nedition='2024'\n",
)
.unwrap();
std::fs::write(root.join("main.rs"), "#[cfg(not(debug_assertions))] compile_error!(\"release-only error\"); fn main() {} #[test] fn passes() {}\n").unwrap();
std::fs::write(root.join("main.rs"), "#[cfg(not(test))] compile_error!(\"release-only error\"); fn main() {} #[test] fn passes() {}\n").unwrap();
let error = build(&world, "release").await.unwrap_err();
assert!(
error["data"]["output"]
@@ -296,6 +311,7 @@ async fn cancelled_preparation_is_serialized() {
sausage::cargo::Cargo::with_runner(
World::open(&world.0).unwrap(),
PathBuf::from(env!("CARGO_BIN_EXE_sausage")),
Path::new(CACHE),
)
.unwrap(),
);
@@ -358,6 +374,12 @@ async fn valid_build() {
assert_eq!(valid.result["tests"]["passed"], 12);
assert_eq!(valid.result["tests"]["failed"], 0);
assert!(!valid.world.0.join("plugins/valid/plugin.wasm").exists());
assert!(!valid.world.0.join("state/cargo/target").exists());
assert!(
Path::new(CACHE)
.join("cargo-target/wasm32-wasip2/release")
.is_dir()
);
}
/// PROTOCOL 11: tests and compilation share one source copy without target metadata.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -373,7 +395,7 @@ async fn source_copy() {
assert!(
std::fs::read_to_string(valid.world.0.join("state/cargo/work/Cargo.toml"))
.unwrap()
.contains("/plugins/cargo/sdk")
.contains(Path::new(CACHE).join("sdk").to_str().unwrap())
);
}
/// PROTOCOL 11: compiler failures return compiler diagnostics.
@@ -514,6 +536,7 @@ async fn unchanged_sdk() {
sausage::cargo::Cargo::with_runner(
World::open(&world.0).unwrap(),
PathBuf::from(env!("CARGO_BIN_EXE_sausage")),
Path::new(CACHE),
)
.unwrap()
};
@@ -534,6 +557,7 @@ async fn standalone_builder() {
let builder = sausage::cargo::Cargo::with_runner(
World::open(&world.0).unwrap(),
PathBuf::from(env!("CARGO_BIN_EXE_sausage")),
Path::new(CACHE),
)
.unwrap();
assert_eq!(
@@ -555,7 +579,9 @@ async fn kill_process_group() {
);
let world = TestWorld::new();
world.source("hang", &fixture("cargo-hang"));
let host = Host::start(&world.0).await.unwrap();
let host = Host::start_with_cache(&world.0, Path::new(CACHE))
.await
.unwrap();
let building = host.clone();
let task =
tokio::spawn(async move { building.call("cargo.build", json!({"plugin":"hang"})).await });
+16 -7
View File
@@ -12,9 +12,15 @@ use tokio::{
process::{Child, Command},
};
const CACHE: &str = concat!(env!("CARGO_TARGET_TMPDIR"), "/cache");
fn binary() -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_sausage"));
command.env_remove("SAUSAGE_HEADLESS").kill_on_drop(true);
command
.arg("--cache-dir")
.arg(CACHE)
.env_remove("SAUSAGE_HEADLESS")
.kill_on_drop(true);
command
}
fn world() -> (tempfile::TempDir, PathBuf) {
@@ -133,12 +139,15 @@ async fn init_builds_agent() {
std::fs::read(world.join("plugins/agent/source/Cargo.lock")).unwrap(),
include_bytes!("../../Cargo.lock")
);
let host = Host::start_with_cache(
&world,
&Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/core/cli-cache"),
)
.await
.unwrap();
assert!(!world.join("state/cargo/target").exists());
assert!(
Path::new(CACHE)
.join("cargo-target/wasm32-wasip2/release")
.is_dir()
);
let host = Host::start_with_cache(&world, Path::new(CACHE))
.await
.unwrap();
let doc = host.call("agent.rpc.discover", json!({})).await.unwrap();
assert_eq!(doc["x-sausage-grace"], 600);
assert!(