cargo.build: write the SDK copy in the cache only when a build needs it
An unwritable cache makes only a build fail, with the folder in data.output. The host still starts.
This commit is contained in:
+22
-17
@@ -61,6 +61,7 @@ pub struct Cargo {
|
|||||||
runner: PathBuf,
|
runner: PathBuf,
|
||||||
target: PathBuf,
|
target: PathBuf,
|
||||||
sdk: PathBuf,
|
sdk: PathBuf,
|
||||||
|
sdk_files: Arc<std::collections::BTreeMap<PathBuf, Vec<u8>>>,
|
||||||
}
|
}
|
||||||
impl Cargo {
|
impl Cargo {
|
||||||
/// Installs changed SDK files and selects the current sausage executable as runner.
|
/// Installs changed SDK files and selects the current sausage executable as runner.
|
||||||
@@ -126,25 +127,9 @@ impl Cargo {
|
|||||||
let cached = std::path::absolute(cache)?
|
let cached = std::path::absolute(cache)?
|
||||||
.join("sdk")
|
.join("sdk")
|
||||||
.join(format!("{:x}", hash.finalize()));
|
.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 {
|
Ok(Self {
|
||||||
sdk: cached,
|
sdk: cached,
|
||||||
|
sdk_files: Arc::new(files),
|
||||||
target: std::path::absolute(cache)?.join("cargo-target"),
|
target: std::path::absolute(cache)?.join("cargo-target"),
|
||||||
world,
|
world,
|
||||||
runner,
|
runner,
|
||||||
@@ -177,7 +162,27 @@ impl Cargo {
|
|||||||
let source = source.to_path_buf();
|
let source = source.to_path_buf();
|
||||||
let copy = work.clone();
|
let copy = work.clone();
|
||||||
let sdk = self.sdk.clone();
|
let sdk = self.sdk.clone();
|
||||||
|
let sdk_files = self.sdk_files.clone();
|
||||||
let build_guard = tokio::task::spawn_blocking(move || -> Result<_> {
|
let build_guard = tokio::task::spawn_blocking(move || -> Result<_> {
|
||||||
|
// Host startup installs only the readable world copy. Builds populate the cache.
|
||||||
|
if !sdk.is_dir() {
|
||||||
|
let parent = sdk.parent().unwrap();
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("create {}", parent.display()))?;
|
||||||
|
let staging = tempfile::Builder::new()
|
||||||
|
.prefix("install-")
|
||||||
|
.tempdir_in(parent)
|
||||||
|
.with_context(|| format!("stage SDK in {}", parent.display()))?;
|
||||||
|
for (path, bytes) in sdk_files.iter() {
|
||||||
|
write_changed(&staging.path().join(path), bytes)?;
|
||||||
|
}
|
||||||
|
// Another builder can publish the same complete SDK first.
|
||||||
|
if let Err(error) = std::fs::rename(staging.path(), &sdk)
|
||||||
|
&& !sdk.is_dir()
|
||||||
|
{
|
||||||
|
return Err(error).with_context(|| format!("install {}", sdk.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
if cleanup.exists() {
|
if cleanup.exists() {
|
||||||
std::fs::remove_dir_all(&cleanup)
|
std::fs::remove_dir_all(&cleanup)
|
||||||
.with_context(|| format!("remove {}", cleanup.display()))?;
|
.with_context(|| format!("remove {}", cleanup.display()))?;
|
||||||
|
|||||||
@@ -268,6 +268,42 @@ async fn native_discovery_and_error_paths() {
|
|||||||
);
|
);
|
||||||
host.stop().await;
|
host.stop().await;
|
||||||
}
|
}
|
||||||
|
/// Rework 3: an unavailable cache prevents builds, not host startup or SDK installation.
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn cache_failure_is_deferred_until_build() {
|
||||||
|
let world = TestWorld::new();
|
||||||
|
world.source("simple", &fixture("cargo-test-fail"));
|
||||||
|
// A file blocks directory creation even when the test runs as root.
|
||||||
|
let cache = world.0.join("blocked-cache");
|
||||||
|
std::fs::write(&cache, "not a directory").unwrap();
|
||||||
|
let host = Host::start_with_cache(&world.0, &cache).await.unwrap();
|
||||||
|
assert!(world.0.join("plugins/cargo/sdk/src/lib.rs").is_file());
|
||||||
|
let error = host
|
||||||
|
.call("cargo.build", json!({"plugin":"simple"}))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error["code"], -32004);
|
||||||
|
let output = error["data"]["output"].as_str().unwrap();
|
||||||
|
assert!(output.contains(cache.to_str().unwrap()), "{output}");
|
||||||
|
assert!(output.contains("create"), "{output}");
|
||||||
|
host.stop().await;
|
||||||
|
|
||||||
|
let standalone = TestWorld::new();
|
||||||
|
standalone.source("simple", &fixture("cargo-test-fail"));
|
||||||
|
let builder = sausage::cargo::Cargo::with_runner(
|
||||||
|
World::open(&standalone.0).unwrap(),
|
||||||
|
PathBuf::from(env!("CARGO_BIN_EXE_sausage")),
|
||||||
|
&cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(standalone.0.join("plugins/cargo/sdk/src/lib.rs").is_file());
|
||||||
|
let error = builder
|
||||||
|
.build(&standalone.0.join("plugins/simple/source"))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(error.output.contains(cache.to_str().unwrap()), "{error}");
|
||||||
|
assert!(cache.is_file());
|
||||||
|
}
|
||||||
/// T23: release-only failures use data.output and retain successful WASI totals.
|
/// T23: release-only failures use data.output and retain successful WASI totals.
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn release_only_failure() {
|
async fn release_only_failure() {
|
||||||
|
|||||||
Reference in New Issue
Block a user