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:
+56
-18
@@ -3,6 +3,7 @@ use crate::{Host, world::World};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use include_dir::{Dir, include_dir};
|
||||
use serde_json::{Value, json};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
@@ -58,10 +59,12 @@ pub struct Cargo {
|
||||
world: World,
|
||||
builds: Arc<tokio::sync::Mutex<()>>,
|
||||
runner: PathBuf,
|
||||
target: PathBuf,
|
||||
sdk: PathBuf,
|
||||
}
|
||||
impl Cargo {
|
||||
/// Installs changed SDK files and selects the current sausage executable as runner.
|
||||
pub fn new(world: World) -> Result<Self> {
|
||||
pub fn new(world: World, cache: &Path) -> Result<Self> {
|
||||
let mut runner = std::env::current_exe()?;
|
||||
if runner
|
||||
.parent()
|
||||
@@ -69,28 +72,29 @@ impl Cargo {
|
||||
{
|
||||
runner = runner.parent().unwrap().parent().unwrap().join("sausage");
|
||||
}
|
||||
Self::with_runner(world, runner)
|
||||
Self::with_runner(world, runner, cache)
|
||||
}
|
||||
/// Installs the SDK and accepts an explicit sausage binary, including for init integrations.
|
||||
pub fn with_runner(world: World, runner: PathBuf) -> Result<Self> {
|
||||
pub fn with_runner(world: World, runner: PathBuf, cache: &Path) -> Result<Self> {
|
||||
let sdk = world.plugin_dir(ID).join("sdk");
|
||||
write_dir(&SDK, &sdk.join("src"))?;
|
||||
write_dir(&MACROS, &sdk.join("macros/src"))?;
|
||||
write_dir(&INTERFACE, &sdk.join("interface/src"))?;
|
||||
let mut files = std::collections::BTreeMap::new();
|
||||
collect_files(&SDK, Path::new("src"), &mut files);
|
||||
collect_files(&MACROS, Path::new("macros/src"), &mut files);
|
||||
collect_files(&INTERFACE, Path::new("interface/src"), &mut files);
|
||||
for (manifest, destination, interface_path) in [
|
||||
(
|
||||
include_str!("../../sdk/Cargo.toml"),
|
||||
sdk.join("Cargo.toml"),
|
||||
PathBuf::from("Cargo.toml"),
|
||||
Some("interface"),
|
||||
),
|
||||
(
|
||||
include_str!("../../sdk/macros/Cargo.toml"),
|
||||
sdk.join("macros/Cargo.toml"),
|
||||
PathBuf::from("macros/Cargo.toml"),
|
||||
Some("../interface"),
|
||||
),
|
||||
(
|
||||
include_str!("../../interface/Cargo.toml"),
|
||||
sdk.join("interface/Cargo.toml"),
|
||||
PathBuf::from("interface/Cargo.toml"),
|
||||
None,
|
||||
),
|
||||
] {
|
||||
@@ -108,9 +112,40 @@ impl Cargo {
|
||||
}
|
||||
}
|
||||
}
|
||||
write_changed(&destination, document.to_string().as_bytes())?;
|
||||
files.insert(destination, document.to_string().into_bytes());
|
||||
}
|
||||
let mut hash = Sha256::new();
|
||||
for (path, bytes) in &files {
|
||||
let name = path.to_str().unwrap().as_bytes();
|
||||
hash.update((name.len() as u64).to_le_bytes());
|
||||
hash.update(name);
|
||||
hash.update((bytes.len() as u64).to_le_bytes());
|
||||
hash.update(bytes);
|
||||
write_changed(&sdk.join(path), bytes)?;
|
||||
}
|
||||
let cached = std::path::absolute(cache)?
|
||||
.join("sdk")
|
||||
.join(format!("{:x}", hash.finalize()));
|
||||
if !cached.is_dir() {
|
||||
let parent = cached.parent().unwrap();
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| format!("create {}", parent.display()))?;
|
||||
let staging = tempfile::Builder::new()
|
||||
.prefix("install-")
|
||||
.tempdir_in(parent)?;
|
||||
for (path, bytes) in &files {
|
||||
write_changed(&staging.path().join(path), bytes)?;
|
||||
}
|
||||
// Another builder can publish the same complete, content-addressed SDK first.
|
||||
if let Err(error) = std::fs::rename(staging.path(), &cached)
|
||||
&& !cached.is_dir()
|
||||
{
|
||||
return Err(error).with_context(|| format!("install {}", cached.display()));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
sdk: cached,
|
||||
target: std::path::absolute(cache)?.join("cargo-target"),
|
||||
world,
|
||||
runner,
|
||||
builds: Arc::new(tokio::sync::Mutex::new(())),
|
||||
@@ -136,12 +171,12 @@ impl Cargo {
|
||||
pub async fn build(&self, source: &Path) -> std::result::Result<BuildOutput, BuildFailure> {
|
||||
let build_guard = self.builds.clone().lock_owned().await;
|
||||
let work = self.world.state_dir(ID).join("work");
|
||||
let target = self.world.state_dir(ID).join("target");
|
||||
let target = self.target.clone();
|
||||
let temporary = self.world.state_dir(ID).join("tmp");
|
||||
let cleanup = temporary.clone();
|
||||
let source = source.to_path_buf();
|
||||
let copy = work.clone();
|
||||
let sdk = self.world.plugin_dir(ID).join("sdk");
|
||||
let sdk = self.sdk.clone();
|
||||
let build_guard = tokio::task::spawn_blocking(move || -> Result<_> {
|
||||
if cleanup.exists() {
|
||||
std::fs::remove_dir_all(&cleanup)
|
||||
@@ -168,7 +203,7 @@ impl Cargo {
|
||||
.await
|
||||
.map_err(anyhow::Error::from)??;
|
||||
let mut test = Command::new("cargo");
|
||||
test.args(["test", "--target", TARGET, "--target-dir"])
|
||||
test.args(["test", "--release", "--target", TARGET, "--target-dir"])
|
||||
.arg(&target)
|
||||
.current_dir(&work)
|
||||
.env("TMPDIR", &temporary)
|
||||
@@ -372,15 +407,18 @@ fn write_changed(path: &Path, bytes: &[u8]) -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Installs one embedded source tree without touching unchanged files.
|
||||
fn write_dir(directory: &Dir<'_>, destination: &Path) -> Result<()> {
|
||||
/// Collects embedded source files for deterministic hashing and installation.
|
||||
fn collect_files(
|
||||
directory: &Dir<'_>,
|
||||
destination: &Path,
|
||||
files: &mut std::collections::BTreeMap<PathBuf, Vec<u8>>,
|
||||
) {
|
||||
for file in directory.files() {
|
||||
write_changed(&destination.join(file.path()), file.contents())?;
|
||||
files.insert(destination.join(file.path()), file.contents().to_vec());
|
||||
}
|
||||
for directory in directory.dirs() {
|
||||
write_dir(directory, destination)?;
|
||||
collect_files(directory, destination, files);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Counts only final libtest summaries, never individual status lines.
|
||||
fn test_count(output: &str, field: &str) -> u64 {
|
||||
|
||||
Reference in New Issue
Block a user