Use the world SDK for cargo builds and seed it only when missing

This commit is contained in:
Sausage Worker
2026-10-28 08:39:36 +00:00
parent dc81f78c20
commit 9ef73e6bc7
4 changed files with 138 additions and 54 deletions
+33 -43
View File
@@ -3,7 +3,6 @@ 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::time::Duration;
use std::{
path::{Path, PathBuf},
@@ -169,10 +168,9 @@ pub struct Cargo {
runner: PathBuf,
target: PathBuf,
sdk: PathBuf,
sdk_files: Arc<std::collections::BTreeMap<PathBuf, Vec<u8>>>,
}
impl Cargo {
/// Installs changed SDK files and selects the current sausage executable as runner.
/// Seeds a missing world SDK and selects the current sausage executable as runner.
pub fn new(world: World, cache: &Path) -> Result<Self> {
let mut runner = std::env::current_exe()?;
if runner
@@ -183,9 +181,23 @@ impl Cargo {
}
Self::with_runner(world, runner, cache)
}
/// Installs the SDK and accepts an explicit sausage binary, including for init integrations.
/// Seeds a missing world SDK and accepts an explicit sausage binary, including for init integrations.
pub fn with_runner(world: World, runner: PathBuf, cache: &Path) -> Result<Self> {
let sdk = world.plugin_dir(ID).join("sdk");
Self::seed_sdk(&sdk)?;
Ok(Self {
sdk,
target: std::path::absolute(cache)?.join("cargo-target"),
world,
runner,
builds: Arc::new(tokio::sync::Mutex::new(())),
})
}
/// Seeds only an absent SDK; an existing world owns all of its SDK files.
fn seed_sdk(sdk: &Path) -> Result<()> {
if sdk.try_exists()? {
return Ok(());
}
let mut files = std::collections::BTreeMap::new();
collect_files(&SDK, Path::new("src"), &mut files);
collect_files(&MACROS, Path::new("macros/src"), &mut files);
@@ -223,26 +235,23 @@ impl Cargo {
}
files.insert(destination, document.to_string().into_bytes());
}
let mut hash = Sha256::new();
let parent = sdk.parent().context("SDK has no parent")?;
std::fs::create_dir_all(parent).with_context(|| format!("create {}", parent.display()))?;
let staging = tempfile::Builder::new()
.prefix("seed-sdk-")
.tempdir_in(parent)
.with_context(|| format!("stage SDK in {}", parent.display()))?;
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)?;
write_changed(&staging.path().join(path), bytes)?;
}
let cached = std::path::absolute(cache)?
.join("sdk")
.join(format!("{:x}", hash.finalize()));
Ok(Self {
sdk: cached,
sdk_files: Arc::new(files),
target: std::path::absolute(cache)?.join("cargo-target"),
world,
runner,
builds: Arc::new(tokio::sync::Mutex::new(())),
})
// Another builder may have seeded the world while we prepared the files.
if !sdk.try_exists()?
&& let Err(error) = std::fs::rename(staging.path(), sdk)
&& !sdk.try_exists()?
{
return Err(error).with_context(|| format!("seed {}", sdk.display()));
}
Ok(())
}
/// Registers cargo.build as a cancellable native process.
pub fn register(self, host: &Host) {
@@ -310,27 +319,8 @@ impl Cargo {
let source = source.to_path_buf();
let copy = work.clone();
let sdk = self.sdk.clone();
let sdk_files = self.sdk_files.clone();
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()));
}
}
Self::seed_sdk(&sdk)?;
if cleanup.exists() {
std::fs::remove_dir_all(&cleanup)
.with_context(|| format!("remove {}", cleanup.display()))?;
@@ -659,7 +649,7 @@ fn write_changed(path: &Path, bytes: &[u8]) -> Result<()> {
}
Ok(())
}
/// Collects embedded source files for deterministic hashing and installation.
/// Collects embedded source files for seeding a missing world SDK.
fn collect_files(
directory: &Dir<'_>,
destination: &Path,