186 lines
5.7 KiB
Rust
186 lines
5.7 KiB
Rust
//! Shared fixture copies and builds. A mutex protects cwd-init and the shell build.
|
|
use std::{
|
|
hash::{Hash, Hasher},
|
|
os::unix::{ffi::OsStrExt, fs::PermissionsExt},
|
|
path::{Path, PathBuf},
|
|
process::{Command, ExitStatus},
|
|
sync::{Mutex, OnceLock},
|
|
};
|
|
pub static BUILD: Mutex<()> = Mutex::new(());
|
|
|
|
pub fn runtime_options() -> sausage::process::RuntimeOptions {
|
|
sausage::process::RuntimeOptions {
|
|
no_builder_sandbox: std::env::var("SAUSAGE_TEST_NO_BUILDER_SANDBOX").as_deref() == Ok("1"),
|
|
}
|
|
}
|
|
|
|
/// Keep the real shared prompt in every roster fixture, with an ephemeral approval port.
|
|
pub fn write_config(dir: &Path, standing: &str, roster: serde_json::Value) -> anyhow::Result<()> {
|
|
let mut config: serde_json::Value =
|
|
serde_yaml_ng::from_str(include_str!("../../../config/config.yaml"))?;
|
|
config["standing"] = serde_json::json!(standing);
|
|
config["roster"] = roster;
|
|
let folder = dir.join("world/etc/sausage");
|
|
std::fs::create_dir_all(&folder)?;
|
|
std::fs::write(
|
|
folder.join("config.yaml"),
|
|
serde_yaml_ng::to_string(&config)?,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn copy_tree(source: &Path, destination: &Path) -> anyhow::Result<()> {
|
|
std::fs::create_dir_all(destination)?;
|
|
for entry in std::fs::read_dir(source)? {
|
|
let entry = entry?;
|
|
let target = destination.join(entry.file_name());
|
|
if entry.file_type()?.is_dir() {
|
|
copy_tree(&entry.path(), &target)?;
|
|
} else {
|
|
std::fs::copy(entry.path(), target)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn target_dir() -> PathBuf {
|
|
std::env::var_os("CARGO_TARGET_DIR")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|| {
|
|
Path::new(env!("CARGO_TARGET_TMPDIR"))
|
|
.parent()
|
|
.unwrap()
|
|
.to_owned()
|
|
})
|
|
}
|
|
|
|
/// Fingerprint the snapshot inputs and their current working-tree contents.
|
|
pub fn bundle_key(repo: &Path) -> anyhow::Result<String> {
|
|
const INPUTS: &[&str] = &[
|
|
"host",
|
|
"vfs",
|
|
"userland",
|
|
"config",
|
|
"skills",
|
|
"extensions",
|
|
"Cargo.toml",
|
|
"Cargo.lock",
|
|
"flake.nix",
|
|
"flake.lock",
|
|
"README.md",
|
|
"AGENTS.md",
|
|
".gitignore",
|
|
];
|
|
let tree = Command::new("git")
|
|
.current_dir(repo)
|
|
.args(["ls-tree", "HEAD", "--"])
|
|
.args(INPUTS)
|
|
.output()?;
|
|
anyhow::ensure!(
|
|
tree.status.success(),
|
|
"git ls-tree: {}",
|
|
String::from_utf8_lossy(&tree.stderr)
|
|
);
|
|
let files = Command::new("git")
|
|
.current_dir(repo)
|
|
.args([
|
|
"ls-files",
|
|
"-z",
|
|
"--cached",
|
|
"--others",
|
|
"--exclude-standard",
|
|
"--",
|
|
])
|
|
.args(INPUTS)
|
|
.output()?;
|
|
anyhow::ensure!(
|
|
files.status.success(),
|
|
"git ls-files: {}",
|
|
String::from_utf8_lossy(&files.stderr)
|
|
);
|
|
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
|
tree.stdout.hash(&mut hash);
|
|
let mut names: Vec<_> = files
|
|
.stdout
|
|
.split(|byte| *byte == 0)
|
|
.filter(|name| !name.is_empty())
|
|
.collect();
|
|
names.sort_unstable();
|
|
for name in names {
|
|
name.hash(&mut hash);
|
|
let path = repo.join(std::ffi::OsStr::from_bytes(name));
|
|
match std::fs::symlink_metadata(&path) {
|
|
Ok(metadata) => {
|
|
metadata.permissions().mode().hash(&mut hash);
|
|
if metadata.file_type().is_symlink() {
|
|
std::fs::read_link(path)?.hash(&mut hash);
|
|
} else {
|
|
std::fs::read(path)?.hash(&mut hash);
|
|
}
|
|
}
|
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
|
"deleted".hash(&mut hash);
|
|
}
|
|
Err(error) => return Err(error.into()),
|
|
}
|
|
}
|
|
Ok(format!("{:016x}\n", hash.finish()))
|
|
}
|
|
|
|
pub fn cwd_init() {
|
|
static INIT: OnceLock<()> = OnceLock::new();
|
|
INIT.get_or_init(|| {
|
|
let _guard = BUILD.lock().unwrap();
|
|
let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland");
|
|
if !userland.join("cwd-init/init.o").is_file() {
|
|
assert!(
|
|
Command::new("nu")
|
|
.args(["--no-config-file", "cwd-init/build.nu"])
|
|
.current_dir(userland)
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn build_brush() -> std::io::Result<ExitStatus> {
|
|
let _guard = BUILD.lock().unwrap();
|
|
Command::new("nu")
|
|
.args(["--no-config-file", "brush/build.nu"])
|
|
.env("CARGO_TARGET_DIR", target_dir())
|
|
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"))
|
|
.status()
|
|
}
|
|
|
|
pub fn build_git() -> &'static Path {
|
|
static GIT: OnceLock<PathBuf> = OnceLock::new();
|
|
GIT.get_or_init(|| {
|
|
let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland");
|
|
let target = target_dir();
|
|
cwd_init();
|
|
assert!(
|
|
Command::new("cargo")
|
|
.args([
|
|
"build",
|
|
"--release",
|
|
"-p",
|
|
"git",
|
|
"-p",
|
|
"sausage-archive-tools",
|
|
"--manifest-path"
|
|
])
|
|
.arg(userland.join("../Cargo.toml"))
|
|
.args(["--target", "wasm32-wasip2", "--target-dir"])
|
|
.arg(&target)
|
|
.args(["-j", "2", "--locked"])
|
|
.current_dir(&userland)
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
);
|
|
target
|
|
})
|
|
}
|