builder: delegate run selection and preserve test working directories
This commit is contained in:
@@ -1,21 +1,33 @@
|
||||
# Rust builder
|
||||
|
||||
The world `cargo` sends `POST http://host.sausage/cargo` with `argv`, `cwd`, and optional `timeout_secs`.
|
||||
The world `cargo` calls `POST http://host.sausage/cargo` with `argv` and `cwd`.
|
||||
The request also accepts optional `timeout_secs`.
|
||||
The response contains newline-delimited JSON events.
|
||||
`stdout` and `stderr` events contain byte arrays. The final `exit` event contains a code and executable paths.
|
||||
The CLI forwards the bytes immediately. For `test` and `run`, it executes the artifacts through the process interface.
|
||||
`stdout` and `stderr` events contain byte arrays.
|
||||
The final `exit` event contains a code and component locations.
|
||||
Each location contains an executable path and working directory.
|
||||
|
||||
The CLI forwards the bytes immediately.
|
||||
For `test` and `run`, it executes the artifacts through the process interface.
|
||||
Cargo selects run binaries through a capture runner, so `--bin` and `default-run` retain their normal behavior.
|
||||
The test runner uses each package's root directory, even when the caller runs Cargo from a subdirectory.
|
||||
For `test` and `run`, arguments after `--` belong to the executable, not the host build.
|
||||
Other commands retain their `--` arguments, such as `fmt -- --check` and Clippy lint options.
|
||||
|
||||
The host finds the Cargo workspace above the requested directory and copies its sources through the shared VFS.
|
||||
It excludes `target` and `.git`.
|
||||
|
||||
Each workspace owns a source copy, target directory, registry, and kache cache.
|
||||
Each workspace owns a source copy and target directory.
|
||||
It also owns a registry and kache cache.
|
||||
These directories live under `<dir>/host/builds/`.
|
||||
|
||||
Cargo fetches dependencies with network access. The command then runs offline inside bubblewrap namespaces.
|
||||
Only the source copy, target, and private kache cache permit writes.
|
||||
The registry and Nix store remain read-only. HOME and the host network are absent.
|
||||
Cargo fetches dependencies with network access.
|
||||
The command then runs offline inside bubblewrap namespaces.
|
||||
|
||||
The source copy and target directory permit writes.
|
||||
The private kache cache also permits writes.
|
||||
The registry and Nix store remain read-only.
|
||||
Host home directories and the host network are absent.
|
||||
The PID namespace removes compiler descendants and the private kache daemon after Cargo exits.
|
||||
|
||||
The builder copies changed and new source files back through the VFS.
|
||||
|
||||
+55
-15
@@ -24,12 +24,24 @@ pub struct Request {
|
||||
pub cwd: String,
|
||||
pub timeout_secs: Option<u64>,
|
||||
}
|
||||
#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Executable {
|
||||
pub path: String,
|
||||
pub cwd: String,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "event", rename_all = "snake_case")]
|
||||
pub enum Event {
|
||||
Stdout { bytes: Vec<u8> },
|
||||
Stderr { bytes: Vec<u8> },
|
||||
Exit { code: i32, executables: Vec<String> },
|
||||
Stdout {
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Stderr {
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Exit {
|
||||
code: i32,
|
||||
executables: Vec<Executable>,
|
||||
},
|
||||
}
|
||||
pub struct Builder {
|
||||
root: Arc<dyn Layer>,
|
||||
@@ -150,6 +162,7 @@ impl Builder {
|
||||
"link.nu",
|
||||
include_bytes!("../../../userland/cwd-init/link.nu").as_slice(),
|
||||
),
|
||||
("run.nu", include_bytes!("run.nu").as_slice()),
|
||||
] {
|
||||
std::fs::write(tools.join(name), bytes)?;
|
||||
}
|
||||
@@ -178,6 +191,10 @@ impl Builder {
|
||||
return Ok(());
|
||||
}
|
||||
let kache = executable("kache")?;
|
||||
let run_path = target_mount.join("run-path");
|
||||
if action == "run" && run_path.exists() {
|
||||
std::fs::remove_file(&run_path)?;
|
||||
}
|
||||
let mut cmd = Command::new("bwrap");
|
||||
cmd.args([
|
||||
"--unshare-user",
|
||||
@@ -220,6 +237,8 @@ impl Builder {
|
||||
.arg(&cache)
|
||||
.args(["--setenv", "SAUSAGE_CWD_INIT"])
|
||||
.arg(tools.join("init.o"))
|
||||
.args(["--setenv", "SAUSAGE_RUN_PATH"])
|
||||
.arg(&run_path)
|
||||
.args(["--setenv", "CARGO_BUILD_JOBS", "2"]);
|
||||
for name in [
|
||||
"CC_wasm32_wasip2",
|
||||
@@ -242,9 +261,6 @@ impl Builder {
|
||||
"target.wasm32-wasip2.rustflags=['-C','link-arg=sausage-cwd-init']",
|
||||
]);
|
||||
let mut args = request.argv.clone();
|
||||
if action == "run" {
|
||||
args[0] = "build".into();
|
||||
}
|
||||
if matches!(action, "run" | "test")
|
||||
&& let Some(index) = args.iter().position(|a| a == "--")
|
||||
{
|
||||
@@ -259,6 +275,13 @@ impl Builder {
|
||||
}
|
||||
let split = args.iter().position(|a| a == "--").unwrap_or(args.len());
|
||||
cmd.args(&args[..split]);
|
||||
if action == "run" {
|
||||
cmd.arg("--config").arg(format!(
|
||||
"target.wasm32-wasip2.runner=[{:?},{:?}]",
|
||||
executable("nu")?,
|
||||
tools.join("run.nu")
|
||||
));
|
||||
}
|
||||
if compiles {
|
||||
cmd.args([
|
||||
"--target",
|
||||
@@ -282,23 +305,40 @@ impl Builder {
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if code == 0 && action == "run" && run_path.exists() {
|
||||
let path = std::fs::read_to_string(&run_path)?;
|
||||
let relative = Path::new(&path).strip_prefix(&target)?;
|
||||
executables.push(Executable {
|
||||
path: format!("{world_target}/{}", relative.display()),
|
||||
cwd: world_cwd.clone(),
|
||||
});
|
||||
}
|
||||
if code == 0
|
||||
&& matches!(action, "test" | "run")
|
||||
&& !(action == "test"
|
||||
&& request
|
||||
.argv
|
||||
.iter()
|
||||
.take_while(|a| a.as_str() != "--")
|
||||
.any(|a| a == "--no-run"))
|
||||
&& action == "test"
|
||||
&& !request
|
||||
.argv
|
||||
.iter()
|
||||
.take_while(|a| a.as_str() != "--")
|
||||
.any(|a| a == "--no-run")
|
||||
{
|
||||
for line in stdout.split(|b| *b == b'\n') {
|
||||
if let Ok(value) = serde_json::from_slice::<serde_json::Value>(line)
|
||||
&& value["reason"] == "compiler-artifact"
|
||||
&& (action == "run" || value["profile"]["test"] == true)
|
||||
&& value["profile"]["test"] == true
|
||||
&& let Some(path) = value["executable"].as_str()
|
||||
&& let Ok(relative) = Path::new(path).strip_prefix(&target)
|
||||
{
|
||||
executables.push(format!("{world_target}/{}", relative.display()));
|
||||
let manifest = Path::new(
|
||||
value["manifest_path"]
|
||||
.as_str()
|
||||
.context("missing test manifest")?,
|
||||
);
|
||||
let package = manifest.parent().unwrap().strip_prefix(&source)?;
|
||||
let cwd = sausage_vfs::resolve(&workspace, &package.to_string_lossy());
|
||||
executables.push(Executable {
|
||||
path: format!("{world_target}/{}", relative.display()),
|
||||
cwd,
|
||||
});
|
||||
}
|
||||
}
|
||||
executables.sort();
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Record Cargo's selected component without executing it on the host.
|
||||
def main [executable: string] {
|
||||
$executable | save --raw --force $env.SAUSAGE_RUN_PATH
|
||||
}
|
||||
Reference in New Issue
Block a user