Merge v3-t15: shared compile cache, precompile, /bin/*.wasm and /src/sausage copies (T15)

This commit is contained in:
2026-11-06 22:18:53 +00:00
94 changed files with 991 additions and 270 deletions
+8 -1
View File
@@ -5,6 +5,9 @@ use std::path::PathBuf;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
/// Shared host-user compile cache (otherwise SAUSAGE_CACHE or the XDG cache folder).
#[arg(long, global = true)]
cache: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
@@ -29,7 +32,11 @@ enum Command {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
match Cli::parse().command {
let cli = Cli::parse();
if let Some(cache) = cli.cache {
sausage::process::configure_cache(cache)?;
}
match cli.command {
Command::Run { world, userland } => {
let bundle =
userland.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from));
+236
View File
@@ -0,0 +1,236 @@
//! Shares compiled programs and single-flight compilation across worlds in one host process.
use super::{Compiled, State, adapter, bindings};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{Arc, Mutex, OnceLock},
};
use tokio::sync::watch;
type Compilation = watch::Receiver<Option<Result<Compiled, String>>>;
use wasmtime::{
Engine,
component::{Component, Linker},
};
use wasmtime_wasi::p2::bindings::CommandPre;
static DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
static SHARED: Mutex<Option<Arc<Compiler>>> = Mutex::new(None);
/// Select the host user's cache before opening a world.
pub fn configure(directory: PathBuf) -> anyhow::Result<()> {
DIRECTORY
.set(directory)
.map_err(|_| anyhow::anyhow!("cache directory already configured"))
}
fn directory() -> anyhow::Result<PathBuf> {
if let Some(dir) = DIRECTORY.get() {
return Ok(dir.clone());
}
if let Some(dir) = std::env::var_os("SAUSAGE_CACHE") {
return Ok(dir.into());
}
if let Some(dir) = std::env::var_os("XDG_CACHE_HOME") {
return Ok(PathBuf::from(dir).join("sausage"));
}
Ok(
PathBuf::from(std::env::var_os("HOME").ok_or_else(|| anyhow::anyhow!("HOME is not set"))?)
.join(".cache/sausage"),
)
}
pub(super) fn shared() -> anyhow::Result<Arc<Compiler>> {
let mut shared = SHARED.lock().unwrap();
if shared.is_none() {
*shared = Some(Compiler::new(&directory()?)?);
}
Ok(shared.as_ref().unwrap().clone())
}
pub(super) struct Compiler {
pub engine: Engine,
programs: Mutex<HashMap<Vec<u8>, Compilation>>,
#[cfg(test)]
before_compile: Mutex<Option<Box<dyn FnOnce() + Send>>>,
#[cfg(test)]
compilations: std::sync::atomic::AtomicUsize,
}
impl Compiler {
pub fn new(directory: &Path) -> anyhow::Result<Arc<Self>> {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let mut config = wasmtime::Config::new();
config.epoch_interruption(true);
// Keep Cranelift's default optimization: compiles are cached per content, and guests run
// many times. OptLevel::None saved only 12.7% of compile time (T15) but slows every run.
let mut cache = wasmtime::CacheConfig::new();
cache.with_directory(directory);
config.cache(Some(wasmtime::Cache::new(cache)?));
Ok(Arc::new(Self {
engine: Engine::new(&config)?,
programs: Mutex::new(HashMap::new()),
#[cfg(test)]
before_compile: Mutex::new(None),
#[cfg(test)]
compilations: std::sync::atomic::AtomicUsize::new(0),
}))
}
pub async fn get(self: &Arc<Self>, bytes: Vec<u8>) -> anyhow::Result<Compiled> {
let mut result = self
.programs
.lock()
.unwrap()
.entry(bytes.clone())
.or_insert_with(|| {
let (tx, rx) = watch::channel(None);
let compiler = self.clone();
// The job owns completion even if a command or a precompile waiter is cancelled.
tokio::spawn(async move {
let result =
tokio::task::spawn_blocking(move || compiler.compile(&bytes)).await;
tx.send_replace(Some(
result
.map_err(anyhow::Error::from)
.and_then(|r| r)
.map_err(|e| format!("{e:#}")),
));
});
rx
})
.clone();
loop {
if let Some(result) = result.borrow().clone() {
return result.map_err(|e| anyhow::anyhow!(e));
}
result.changed().await?;
}
}
fn compile(&self, bytes: &[u8]) -> anyhow::Result<Compiled> {
#[cfg(test)]
{
self.compilations
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if let Some(hook) = self.before_compile.lock().unwrap().take() {
hook();
}
}
if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
let module = wasmtime::Module::new(&self.engine, bytes)?;
let mut linker = wasmtime::Linker::new(&self.engine);
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
s.preview1.as_mut().unwrap()
})?;
Ok(Compiled::Module(linker.instantiate_pre(&module)?))
} else {
let component = Component::new(&self.engine, bytes)?;
let mut linker = Linker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
adapter::add_to_linker(&mut linker, |s: &mut State| adapter::View {
adapter: &mut s.fs,
table: &mut s.table,
})?;
bindings::sausage::process::host::add_to_linker::<_, wasmtime::component::HasSelf<_>>(
&mut linker,
|s| s,
)?;
Ok(Compiled::Component(CommandPre::new(
linker.instantiate_pre(&component)?,
)?))
}
}
}
/// Compile programs in both command folders without blocking world startup.
pub(super) fn precompile(
compiler: Arc<Compiler>,
root: Arc<dyn sausage_vfs::Layer>,
) -> tokio::task::JoinHandle<anyhow::Result<usize>> {
tokio::spawn(async move {
let mut tasks = tokio::task::JoinSet::new();
// Leave CPU and file bandwidth for commands during startup.
let slots = Arc::new(tokio::sync::Semaphore::new(2));
for folder in ["/bin", "/usr/local/bin"] {
let entries = match root.list(folder).await {
Ok(entries) => entries,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => return Err(e.into()),
};
for entry in entries.into_iter().filter(|entry| !entry.directory) {
let root = root.clone();
let compiler = compiler.clone();
let path = format!("{folder}/{}", entry.name);
let slots = slots.clone();
tasks.spawn(async move {
let _permit = slots.acquire().await?;
let file = root
.open(
&path,
sausage_vfs::Open {
follow: true,
..Default::default()
},
)
.await?;
let bytes = file.read(0, file.stat().await?.size as usize).await?;
if bytes.get(..4) != Some(b"\0asm") {
return Ok::<_, anyhow::Error>(false);
}
compiler.get(bytes).await?;
Ok(true)
});
}
}
let mut count = 0;
while let Some(result) = tasks.join_next().await {
if result?? {
count += 1;
}
}
Ok(count)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::{Command, Exec, Options};
use std::{future::Future, sync::atomic::Ordering};
#[tokio::test(flavor = "multi_thread")]
async fn command_joins_precompile_even_after_waiter_cancellation() {
let temp = tempfile::tempdir().unwrap();
let compiler = Compiler::new(&temp.path().join("cache")).unwrap();
let world = crate::world::World::open(&temp.path().join("world")).unwrap();
let bytes =
wat::parse_str(r#"(module (memory (export "memory") 1) (func (export "_start")))"#)
.unwrap();
std::fs::create_dir_all(temp.path().join("world/world/bin")).unwrap();
std::fs::write(temp.path().join("world/world/bin/program.wasm"), &bytes).unwrap();
let (started_tx, started_rx) = tokio::sync::oneshot::channel();
let (finish_tx, finish_rx) = std::sync::mpsc::channel();
*compiler.before_compile.lock().unwrap() = Some(Box::new(move || {
started_tx.send(()).unwrap();
finish_rx.recv().unwrap();
}));
// Startup returns while the compiler remains blocked behind the test gate.
let runtime = super::super::Runtime::with_compiler(world, compiler.clone()).unwrap();
started_rx.await.unwrap();
let background = compiler.clone();
let precompile = tokio::spawn(async move { background.get(bytes).await });
let mut output = |_: crate::exec::Stream, _: &[u8]| {};
let command = runtime.exec(
Command::Argv(vec!["/bin/program".into()]),
Options::default(),
&mut output,
);
tokio::pin!(command);
let pending = std::future::poll_fn(|cx| {
std::task::Poll::Ready(command.as_mut().poll(cx).is_pending())
})
.await;
assert!(pending);
assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1);
precompile.abort();
let _ = precompile.await;
finish_tx.send(()).unwrap();
assert_eq!(command.await.unwrap(), 0);
assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1);
assert!(!temp.path().join("world/host/compile-cache").exists());
}
}
+60 -47
View File
@@ -5,7 +5,7 @@ use crate::{
};
use sausage_vfs::{
Mounts,
adapter::{self, Adapter, View},
adapter::{self, Adapter},
};
use std::{
collections::HashMap,
@@ -22,7 +22,7 @@ use tokio::{
};
use wasmtime::{
Engine, Store,
component::{Component, Linker, Resource, ResourceTable},
component::{Resource, ResourceTable},
};
use wasmtime_wasi::{
WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView,
@@ -45,6 +45,8 @@ mod bindings {
});
}
use bindings::sausage::process::host::{Host, HostChild, Request, Stdio};
mod cache;
pub use cache::configure as configure_cache;
mod services;
mod stdio;
pub use services::supervise;
@@ -77,7 +79,8 @@ struct Entry {
pub struct Runtime {
world: World,
engine: Engine,
cache: Mutex<HashMap<Vec<u8>, Compiled>>,
compiler: Arc<cache::Compiler>,
cache: Mutex<std::collections::HashSet<Vec<u8>>>,
entries: Mutex<HashMap<u32, Entry>>,
next: AtomicU32,
alive: Arc<AtomicBool>,
@@ -138,14 +141,14 @@ impl Runtime {
Self::new(World::open(path)?)
}
pub fn new(world: World) -> anyhow::Result<Arc<Self>> {
// Deno and WASI HTTP enable different providers. Select one before HTTPS creates a client.
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let mut config = wasmtime::Config::new();
config.epoch_interruption(true);
let mut cache = wasmtime::CacheConfig::new();
cache.with_directory(world.host_dir.join("compile-cache"));
config.cache(Some(wasmtime::Cache::new(cache)?));
let engine = Engine::new(&config)?;
Self::with_compiler(world, cache::shared()?)
}
/// Open an independent engine using an explicit shared disk cache.
pub fn new_with_cache(world: World, directory: &Path) -> anyhow::Result<Arc<Self>> {
Self::with_compiler(world, cache::Compiler::new(directory)?)
}
fn with_compiler(world: World, compiler: Arc<cache::Compiler>) -> anyhow::Result<Arc<Self>> {
let engine = compiler.engine.clone();
let alive = Arc::new(AtomicBool::new(true));
let ticking = alive.clone();
let clock = engine.clone();
@@ -155,19 +158,57 @@ impl Runtime {
clock.increment_epoch();
}
});
Ok(Arc::new(Self {
let runtime = Arc::new(Self {
world,
engine,
alive,
cache: Mutex::new(HashMap::new()),
compiler,
cache: Mutex::new(Default::default()),
entries: Mutex::new(HashMap::new()),
next: AtomicU32::new(1),
}))
});
cache::precompile(runtime.compiler.clone(), runtime.world.root.clone());
Ok(runtime)
}
/// Number of distinct program contents compiled by this runtime.
pub fn compiled_programs(&self) -> usize {
self.cache.lock().unwrap().len()
}
async fn resolve_program(
&self,
cwd: &str,
name: &str,
env: &[(String, String)],
) -> anyhow::Result<String> {
let paths = if name.contains('/') {
vec![sausage_vfs::resolve(cwd, name)]
} else {
let path = env
.iter()
.find(|(key, _)| key == "PATH")
.map(|(_, value)| value.as_str())
.unwrap_or("/usr/local/bin:/bin");
path.split(':')
.map(|folder| {
sausage_vfs::resolve(
cwd,
&std::path::Path::new(folder).join(name).to_string_lossy(),
)
})
.collect()
};
for path in paths {
for candidate in [path.clone(), format!("{path}.wasm")] {
match self.world.root.stat(&candidate, true).await {
Ok(stat) if !stat.directory => return Ok(candidate),
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
}
}
}
anyhow::bail!("program not found: {name}")
}
fn kill_command(&self, command: u32) {
for entry in self.entries.lock().unwrap().values() {
if entry.command == command {
@@ -222,7 +263,9 @@ impl Runtime {
request.cwd = sausage_vfs::resolve("/", &request.cwd);
request.env.retain(|(name, _)| name != "PWD");
request.env.push(("PWD".into(), request.cwd.clone()));
let executable = sausage_vfs::resolve(&request.cwd, &request.program);
let executable = self
.resolve_program(&request.cwd, &request.program, &request.env)
.await?;
let file = self
.world
.root
@@ -237,38 +280,8 @@ impl Runtime {
let bytes = file
.read(0, usize::try_from(file.stat().await?.size)?)
.await?;
let pre = {
let mut cache = self.cache.lock().unwrap();
if let Some(pre) = cache.get(&bytes) {
pre.clone()
} else {
let pre = if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
let module = wasmtime::Module::new(&self.engine, &bytes)?;
let mut linker = wasmtime::Linker::new(&self.engine);
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
s.preview1.as_mut().unwrap()
})?;
Compiled::Module(linker.instantiate_pre(&module)?)
} else {
let component = Component::new(&self.engine, &bytes)?;
let mut linker = Linker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
// The library linker includes exit-with-code. Only Wasmtime's CLI gates it.
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
adapter::add_to_linker(&mut linker, |s: &mut State| View {
adapter: &mut s.fs,
table: &mut s.table,
})?;
bindings::sausage::process::host::add_to_linker::<
_,
wasmtime::component::HasSelf<_>,
>(&mut linker, |s| s)?;
Compiled::Component(CommandPre::new(linker.instantiate_pre(&component)?)?)
};
cache.insert(bytes, pre.clone());
pre
}
};
let pre = self.compiler.get(bytes.clone()).await?;
self.cache.lock().unwrap().insert(bytes);
let pid = self.next.fetch_add(1, Ordering::Relaxed);
let command = if command == 0 { pid } else { command };
let group = match request.group {
+23 -9
View File
@@ -19,17 +19,37 @@ pub struct World {
pub processes: crate::process::Processes,
}
fn copy_tree(source: &Path, destination: &Path) -> std::io::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(())
}
impl World {
/// Open (and create if missing) the world directory `dir`, including `/proc` and `/run` mounts.
pub fn open(dir: &Path) -> anyhow::Result<Self> {
Self::open_with_userland(dir, None)
}
/// Mount an optional host bundle read-only at `/bin` (D43).
/// Copy an optional bundle into a new world. Existing worlds retain their programs.
pub fn open_with_userland(dir: &Path, userland: Option<&Path>) -> anyhow::Result<Self> {
let root_dir = dir.join("world");
let host_dir = dir.join("host");
let fresh = !root_dir.exists();
std::fs::create_dir_all(root_dir.join("usr/local/bin"))?;
if fresh && let Some(bundle) = userland {
for folder in ["bin", "src"] {
copy_tree(&bundle.join(folder), &root_dir.join(folder))?;
}
}
std::fs::create_dir_all(&host_dir)?;
let mut mounts = Mounts::default();
mounts.mount(
@@ -55,17 +75,11 @@ impl World {
}
Ok(text.into_bytes())
});
if let Some(bundle) = userland {
mounts.mount(
"/bin",
Arc::new(Host::new(bundle, Process::default())?),
true,
);
}
mounts.mount("/proc", Arc::new(proc), true);
mounts.mount("/run", Arc::new(Memory::default()), false);
let root: Arc<dyn Layer> = Arc::new(mounts);
Ok(World {
root: Arc::new(mounts),
root,
host_dir,
processes,
})
+114 -8
View File
@@ -1,13 +1,13 @@
//! P1 integration: a read-only bundle and real shell calls from the durable harness.
//! P1 integration: independent bundle copies and real shell calls from the durable harness.
use sausage::{harness::run_module, world::World};
use sausage_vfs::Open;
use std::path::PathBuf;
#[tokio::test(flavor = "current_thread")]
async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
async fn bundle_copy_and_real_shell() -> anyhow::Result<()> {
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
let bundle = repo.join("target/userland/bin");
if !bundle.join("sh").exists() {
let bundle = repo.join("target/userland/bundle");
if !bundle.join("bin/sh.wasm").exists() {
assert!(
std::process::Command::new("nu")
.env_remove("CARGO_TARGET_DIR")
@@ -18,9 +18,16 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
}
let dir = tempfile::tempdir()?;
let world = World::open_with_userland(dir.path(), Some(&bundle))?;
assert!(world.root.stat("/bin/sh", true).await?.size > 0);
assert!(world.root.stat("/bin/sh.wasm", true).await?.size > 0);
use std::os::unix::fs::MetadataExt;
for entry in std::fs::read_dir(dir.path().join("world/bin"))? {
let entry = entry?;
assert_eq!(entry.path().extension().unwrap(), "wasm");
assert_eq!(entry.metadata()?.nlink(), 1);
assert!(!entry.file_type()?.is_symlink());
}
assert!(world.root.stat("/usr/local/bin", true).await?.directory);
let denied = world
let writable = world
.root
.open(
"/bin/forbidden",
@@ -31,17 +38,72 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
},
)
.await;
assert!(matches!(denied, Err(e) if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem));
assert!(writable.is_ok());
let exec = sausage::process::exec(World {
root: world.root.clone(),
host_dir: world.host_dir.clone(),
processes: world.processes.clone(),
})?;
use sausage::exec::{Command, Options, Stream};
for (script, expected) in [
("command -v ls", "/bin/ls.wasm\n"),
("ls", "bin"),
("git --version", "git version"),
("printf 'a\\n' | xargs echo", "a\n"),
("env FOO=1 printenv FOO", "1\n"),
] {
let mut out = Vec::new();
let code = exec
.exec(
Command::Shell(script.into()),
Options::default(),
&mut |stream, bytes| {
if matches!(stream, Stream::Stdout) {
out.extend_from_slice(bytes);
}
},
)
.await?;
assert_eq!(code, 0, "{script}");
assert!(String::from_utf8(out)?.contains(expected), "{script}");
}
let mut history = Vec::new();
assert_eq!(
exec.exec(
Command::Shell("cd /src/sausage && git log --oneline".into()),
Options::default(),
&mut |stream, bytes| {
if matches!(stream, Stream::Stdout) {
history.extend_from_slice(bytes);
}
},
)
.await?,
0,
);
let history = String::from_utf8(history)?;
assert_eq!(history.lines().count(), 1);
assert!(history.contains("Bundle source "));
let mut status = Vec::new();
assert_eq!(
exec.exec(
Command::Shell("cd /src/sausage && git status --short".into()),
Options::default(),
&mut |stream, bytes| {
if matches!(stream, Stream::Stdout) {
status.extend_from_slice(bytes);
}
},
)
.await?,
0,
);
assert!(status.is_empty(), "{}", String::from_utf8_lossy(&status));
let source = format!(
"globalThis.testP1 = true;\n{}",
include_str!("../js/test-bundle.js")
);
run_module(world, exec, &source).await?;
run_module(world, exec.clone(), &source).await?;
assert_eq!(
std::fs::read_to_string(dir.path().join("world/test.txt"))?,
"after\n"
@@ -59,6 +121,50 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> {
let actual = std::fs::read_to_string(spills[0].path())?;
let expected: String = (1..=10000).map(|n| format!("{n}\n")).collect();
assert_eq!(actual, expected);
let source_cat = std::fs::read(bundle.join("bin/cat.wasm"))?;
let head = std::fs::read(dir.path().join("world/bin/head.wasm"))?;
let mut out = Vec::new();
assert_eq!(
exec.exec(
Command::Shell(
"cp /bin/true.wasm /bin/cat.wasm; cat /test.txt; head -n 1 /test.txt".into()
),
Options::default(),
&mut |stream, bytes| {
if matches!(stream, Stream::Stdout) {
out.extend_from_slice(bytes);
}
}
)
.await?,
0
);
assert_eq!(out, b"after\n");
assert_eq!(std::fs::read(bundle.join("bin/cat.wasm"))?, source_cat);
assert_eq!(std::fs::read(dir.path().join("world/bin/head.wasm"))?, head);
assert_eq!(
exec.exec(
Command::Shell("cp /bin/true.wasm /usr/local/bin/head.wasm; head /test.txt".into()),
Options::default(),
&mut |_, bytes| assert!(bytes.is_empty())
)
.await?,
0
);
let changed = std::fs::read(dir.path().join("world/bin/cat.wasm"))?;
let reopened = World::open_with_userland(
dir.path(),
Some(std::path::Path::new("/nonexistent-bundle")),
)?;
assert_eq!(
reopened
.root
.open("/bin/cat.wasm", Open::default())
.await?
.read(0, changed.len())
.await?,
changed
);
// An unbundled world remains usable by process tests and file-only callers.
let unbundled = tempfile::tempdir()?;
let world = World::open(unbundled.path())?;
+56 -11
View File
@@ -9,6 +9,39 @@ use std::{
sync::{Arc, Once},
time::Duration,
};
#[test]
fn world_copies_programs_and_nested_source_only_once() -> anyhow::Result<()> {
let temp = tempfile::tempdir()?;
let bundle = temp.path().join("bundle");
std::fs::create_dir_all(bundle.join("bin"))?;
std::fs::create_dir_all(bundle.join("src/sausage/.git"))?;
std::fs::write(bundle.join("bin/probe.wasm"), b"program")?;
std::fs::write(
bundle.join("src/sausage/.git/HEAD"),
b"ref: refs/heads/main\n",
)?;
std::fs::write(bundle.join("src/sausage/README.md"), b"source")?;
let path = temp.path().join("world");
let _world = World::open_with_userland(&path, Some(&bundle))?;
assert_eq!(
std::fs::read(path.join("world/bin/probe.wasm"))?,
b"program"
);
assert_eq!(
std::fs::read(path.join("world/src/sausage/README.md"))?,
b"source"
);
assert!(path.join("world/src/sausage/.git/HEAD").is_file());
std::fs::write(path.join("world/src/sausage/README.md"), b"agent patch")?;
let _world = World::open_with_userland(&path, Some(Path::new("/missing-bundle")))?;
assert_eq!(
std::fs::read(path.join("world/src/sausage/README.md"))?,
b"agent patch"
);
assert!(!path.join("host/compile-cache").exists());
Ok(())
}
mod features {
//! Acceptance coverage for preview1, identity, inherited IO, services, HTTP, and bundle aliases.
use super::*;
@@ -260,28 +293,32 @@ mod features {
empty(&processes).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn relative_aliases_share_compilation() {
async fn independent_copies_share_compilation() {
let _serial = SERIAL.lock().await;
let (temp, runtime, _) = world();
let bin = temp.path().join("world/bin");
for name in ["true", "echo"] {
std::os::unix::fs::symlink("cat", bin.join(name)).unwrap();
for name in ["read-a.wasm", "read-b.wasm"] {
std::fs::copy(bin.join("cat.wasm"), bin.join(name)).unwrap();
}
assert_eq!(
run(&runtime, Command::Argv(vec!["/bin/true".into()]), "/")
.await
.0,
run(
&runtime,
Command::Argv(vec!["/bin/read-a".into(), "/f".into()]),
"/"
)
.await
.0,
0
);
assert_eq!(runtime.compiled_programs(), 1);
let (code, out, _) = run(
&runtime,
Command::Argv(vec!["/bin/echo".into(), "shared".into()]),
Command::Argv(vec!["/bin/read-b".into(), "/f".into()]),
"/",
)
.await;
assert_eq!(code, 0);
assert_eq!(out, b"shared\n");
assert_eq!(out, b"root\n");
assert_eq!(runtime.compiled_programs(), 1);
}
#[tokio::test(flavor = "multi_thread")]
@@ -464,13 +501,17 @@ fn world() -> (tempfile::TempDir, Arc<Runtime>, sausage::process::Processes) {
let world = World::open(temp.path()).unwrap();
let cache = Path::new(env!("CARGO_TARGET_TMPDIR")).join("process-host/compile-cache");
std::fs::create_dir_all(&cache).unwrap();
std::os::unix::fs::symlink(cache, world.host_dir.join("compile-cache")).unwrap();
let processes = world.processes.clone();
fixture(&temp.path().join("world"));
let artifacts = repo().join("userland/target/wasm32-wasip2/release");
let bin = temp.path().join("world/bin");
for tool in ["cat", "tr", "ls", "wc", "yes", "head", "sleep"] {
std::fs::copy(artifacts.join("sausage-coreutils.wasm"), bin.join(tool)).unwrap();
std::fs::copy(
artifacts.join(format!("{tool}.wasm")),
bin.join(format!("{tool}.wasm")),
)
.unwrap();
}
for tool in ["ps", "kill"] {
std::fs::copy(artifacts.join("sausage-procps.wasm"), bin.join(tool)).unwrap();
@@ -491,7 +532,11 @@ fn world() -> (tempfile::TempDir, Arc<Runtime>, sausage::process::Processes) {
bin.join("git"),
)
.unwrap();
(temp, Runtime::new(world).unwrap(), processes)
(
temp,
Runtime::new_with_cache(world, &cache).unwrap(),
processes,
)
}
async fn run(runtime: &Arc<Runtime>, command: Command, cwd: &str) -> (i32, Vec<u8>, Vec<u8>) {
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
+6
View File
@@ -18,6 +18,12 @@ def main [] {
if (^git -C $source rev-parse HEAD | str trim) != $revision { error make {msg: 'unexpected brush revision'} }
let core = ($source | path join brush-core)
copy-if-changed ($here | path join process.rs) ($core | path join src sys wasm process.rs)
copy-if-changed ($here | path join lookup.rs) ($core | path join src sys wasm sausage-lookup.rs)
let fs = ($core | path join src sys wasm fs.rs)
let fs_text = (open --raw $fs)
if not ($fs_text | str contains 'include!("sausage-lookup.rs")') {
$fs_text + "\ninclude!(\"sausage-lookup.rs\");\n" | save --force $fs
}
mkdir ($core | path join sausage-process)
let wit = ($here | path join .. .. host wit | path expand)
copy-if-changed ($wit | path join process.wit) ($core | path join sausage-process process.wit)
+28
View File
@@ -0,0 +1,28 @@
/// Resolve ordinary files before the WASI suffix in each PATH folder.
pub fn resolve_executable(path: std::path::PathBuf) -> Option<std::path::PathBuf> {
if path.is_file() {
return Some(path);
}
let mut name = path.into_os_string();
name.push(".wasm");
let path = std::path::PathBuf::from(name);
path.is_file().then_some(path)
}
pub(crate) fn resolve_executable_path(
path: std::path::PathBuf,
working_dir: &std::path::Path,
) -> std::path::PathBuf {
let absolute = working_dir.join(&path);
if absolute.is_file() {
return path;
}
let mut name = absolute.into_os_string();
name.push(".wasm");
if std::path::PathBuf::from(name).is_file() {
let mut name = path.into_os_string();
name.push(".wasm");
name.into()
} else {
path
}
}
+31 -7
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env nu
# Build every workspace CLI and assemble the read-only /bin bundle.
# Build independent commands and a committed source snapshot of their repository.
def main [destination?: path] {
let here = $env.FILE_PWD
let destination = ($destination | default ($here | path join .. target userland bin) | path expand)
let destination = ($destination | default ($here | path join .. target userland bundle) | path expand)
let bin = ($destination | path join bin)
let object = ($here | path join cwd-init init.o)
let sources = [($here | path join cwd-init init.rs) ($here | path join cwd-init exit.wit) ($here | path join cwd-init build.nu)]
if not ($object | path exists) or ($sources | any {|source| (ls $source | get modified.0) > (ls $object | get modified.0) }) {
@@ -14,23 +15,46 @@ def main [destination?: path] {
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'userland build failed'} }
^nu ($here | path join brush build.nu)
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush build failed'} }
mkdir $destination
mkdir $bin
for package in ($metadata.packages | where {|p| $p.id in $metadata.workspace_members }) {
let tools = ($package.manifest_path | path dirname | path join tools.txt)
for binary in ($package.targets | where {|t| 'bin' in $t.kind }) {
let source = ($metadata.target_directory | path join wasm32-wasip2 release $'($binary.name).wasm')
let names = if ($tools | path exists) { open --raw $tools | lines | where {|s| ($s | str trim) != '' } } else { [$binary.name] }
for name in $names {
^ln -f $source ($destination | path join $name)
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle link failed: ($name)'} }
^cp --remove-destination $source ($bin | path join $'($name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle copy failed: ($name)'} }
}
}
}
let brush_metadata = (^cargo metadata --manifest-path ($here | path join brush source Cargo.toml) --no-deps --format-version 1 | from json)
let brush = ($brush_metadata.target_directory | path join wasm32-wasip2 release brush.wasm)
for name in [sh bash] {
^ln -f $brush ($destination | path join $name)
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush link failed'} }
^cp --remove-destination $brush ($bin | path join $'($name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush copy failed'} }
}
let repo = ($here | path join .. | path expand)
let revision = (^git -C $repo rev-parse HEAD | str trim)
let snapshot = ($destination | path join src sausage)
if ($snapshot | path exists) { rm --recursive --force $snapshot }
mkdir $snapshot
let archive = ($destination | path join source.tar)
^git -C $repo archive --output $archive HEAD host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source archive failed'} }
^tar -xf $archive -C $snapshot
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source extraction failed'} }
rm $archive
^git -C $snapshot init --quiet --initial-branch=main
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git init failed'} }
^git -C $snapshot add host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git add failed'} }
with-env {
GIT_AUTHOR_NAME: 'Sausage', GIT_AUTHOR_EMAIL: 'sausage@localhost',
GIT_COMMITTER_NAME: 'Sausage', GIT_COMMITTER_EMAIL: 'sausage@localhost',
GIT_AUTHOR_DATE: '2000-01-01T00:00:00+0000', GIT_COMMITTER_DATE: '2000-01-01T00:00:00+0000'
} {
^git -C $snapshot commit --no-gpg-sign --quiet -m $'Bundle source ($revision)'
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git commit failed'} }
}
print $destination
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the arch command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("arch").expect("utility supplies localization");
std::process::exit(uu_arch::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the b2sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("b2sum").expect("utility supplies localization");
std::process::exit(uu_b2sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the base32 command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("base32").expect("utility supplies localization");
std::process::exit(uu_base32::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the base64 command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("base64").expect("utility supplies localization");
std::process::exit(uu_base64::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the basename command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("basename").expect("utility supplies localization");
std::process::exit(uu_basename::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the basenc command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("basenc").expect("utility supplies localization");
std::process::exit(uu_basenc::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the cat command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("cat").expect("utility supplies localization");
std::process::exit(uu_cat::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the cksum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("cksum").expect("utility supplies localization");
std::process::exit(uu_cksum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the comm command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("comm").expect("utility supplies localization");
std::process::exit(uu_comm::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the cp command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("cp").expect("utility supplies localization");
std::process::exit(uu_cp::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the csplit command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("csplit").expect("utility supplies localization");
std::process::exit(uu_csplit::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the cut command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("cut").expect("utility supplies localization");
std::process::exit(uu_cut::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the date command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("date").expect("utility supplies localization");
std::process::exit(uu_date::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the dd command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("dd").expect("utility supplies localization");
std::process::exit(uu_dd::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the dir command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("ls").expect("utility supplies localization");
std::process::exit(uu_dir::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the dircolors command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("dircolors").expect("utility supplies localization");
std::process::exit(uu_dircolors::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the dirname command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("dirname").expect("utility supplies localization");
std::process::exit(uu_dirname::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the echo command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("echo").expect("utility supplies localization");
std::process::exit(uu_echo::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the expand command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("expand").expect("utility supplies localization");
std::process::exit(uu_expand::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the expr command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("expr").expect("utility supplies localization");
std::process::exit(uu_expr::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the factor command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("factor").expect("utility supplies localization");
std::process::exit(uu_factor::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the false command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("false").expect("utility supplies localization");
std::process::exit(uu_false::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the fmt command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("fmt").expect("utility supplies localization");
std::process::exit(uu_fmt::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the fold command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("fold").expect("utility supplies localization");
std::process::exit(uu_fold::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the head command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("head").expect("utility supplies localization");
std::process::exit(uu_head::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the hostid command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("hostid").expect("utility supplies localization");
std::process::exit(uu_hostid::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the join command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("join").expect("utility supplies localization");
std::process::exit(uu_join::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the link command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("link").expect("utility supplies localization");
std::process::exit(uu_link::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the ln command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("ln").expect("utility supplies localization");
std::process::exit(uu_ln::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the ls command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("ls").expect("utility supplies localization");
std::process::exit(uu_ls::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the md5sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("md5sum").expect("utility supplies localization");
std::process::exit(uu_md5sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the mkdir command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("mkdir").expect("utility supplies localization");
std::process::exit(uu_mkdir::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the mktemp command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("mktemp").expect("utility supplies localization");
std::process::exit(uu_mktemp::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the mv command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("mv").expect("utility supplies localization");
std::process::exit(uu_mv::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the nice command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("nice").expect("utility supplies localization");
std::process::exit(uu_nice::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the nl command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("nl").expect("utility supplies localization");
std::process::exit(uu_nl::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the nproc command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("nproc").expect("utility supplies localization");
std::process::exit(uu_nproc::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the numfmt command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("numfmt").expect("utility supplies localization");
std::process::exit(uu_numfmt::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the od command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("od").expect("utility supplies localization");
std::process::exit(uu_od::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the paste command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("paste").expect("utility supplies localization");
std::process::exit(uu_paste::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the pathchk command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("pathchk").expect("utility supplies localization");
std::process::exit(uu_pathchk::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the pr command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("pr").expect("utility supplies localization");
std::process::exit(uu_pr::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the printenv command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("printenv").expect("utility supplies localization");
std::process::exit(uu_printenv::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the printf command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("printf").expect("utility supplies localization");
std::process::exit(uu_printf::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the ptx command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("ptx").expect("utility supplies localization");
std::process::exit(uu_ptx::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the pwd command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("pwd").expect("utility supplies localization");
std::process::exit(uu_pwd::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the readlink command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("readlink").expect("utility supplies localization");
std::process::exit(uu_readlink::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the realpath command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("realpath").expect("utility supplies localization");
std::process::exit(uu_realpath::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the rm command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("rm").expect("utility supplies localization");
std::process::exit(uu_rm::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the rmdir command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("rmdir").expect("utility supplies localization");
std::process::exit(uu_rmdir::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the seq command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("seq").expect("utility supplies localization");
std::process::exit(uu_seq::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sha1sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sha1sum").expect("utility supplies localization");
std::process::exit(uu_sha1sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sha224sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sha224sum").expect("utility supplies localization");
std::process::exit(uu_sha224sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sha256sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sha256sum").expect("utility supplies localization");
std::process::exit(uu_sha256sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sha384sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sha384sum").expect("utility supplies localization");
std::process::exit(uu_sha384sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sha512sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sha512sum").expect("utility supplies localization");
std::process::exit(uu_sha512sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the shred command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("shred").expect("utility supplies localization");
std::process::exit(uu_shred::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the shuf command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("shuf").expect("utility supplies localization");
std::process::exit(uu_shuf::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sleep command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sleep").expect("utility supplies localization");
std::process::exit(uu_sleep::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sort command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sort").expect("utility supplies localization");
std::process::exit(uu_sort::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the split command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("split").expect("utility supplies localization");
std::process::exit(uu_split::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the sum command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("sum").expect("utility supplies localization");
std::process::exit(uu_sum::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the tail command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("tail").expect("utility supplies localization");
std::process::exit(uu_tail::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the tee command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("tee").expect("utility supplies localization");
std::process::exit(uu_tee::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the test command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("test").expect("utility supplies localization");
std::process::exit(uu_test::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the touch command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("touch").expect("utility supplies localization");
std::process::exit(uu_touch::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the tr command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("tr").expect("utility supplies localization");
std::process::exit(uu_tr::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the true command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("true").expect("utility supplies localization");
std::process::exit(uu_true::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the truncate command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("truncate").expect("utility supplies localization");
std::process::exit(uu_truncate::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the tsort command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("tsort").expect("utility supplies localization");
std::process::exit(uu_tsort::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the tty command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("tty").expect("utility supplies localization");
std::process::exit(uu_tty::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the uname command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("uname").expect("utility supplies localization");
std::process::exit(uu_uname::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the unexpand command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("unexpand").expect("utility supplies localization");
std::process::exit(uu_unexpand::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the uniq command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("uniq").expect("utility supplies localization");
std::process::exit(uu_uniq::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the unlink command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("unlink").expect("utility supplies localization");
std::process::exit(uu_unlink::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the vdir command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("ls").expect("utility supplies localization");
std::process::exit(uu_vdir::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the wc command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("wc").expect("utility supplies localization");
std::process::exit(uu_wc::uumain(uucore::args_os()));
}
+5
View File
@@ -0,0 +1,5 @@
//! Implements the yes command as an independent WASI binary.
fn main() {
uucore::locale::setup_localization("yes").expect("utility supplies localization");
std::process::exit(uu_yes::uumain(uucore::args_os()));
}
-98
View File
@@ -1,98 +0,0 @@
//! Dispatches the portable uutils 0.12 multicall component by argv[0].
fn main() {
let argv0 = std::env::args().next().unwrap();
let name = std::path::Path::new(&argv0)
.file_name()
.unwrap()
.to_str()
.unwrap();
let name = if name == "[" { "test" } else { name };
let locale = match name {
"dir" | "vdir" => "ls",
_ => name,
};
uucore::locale::setup_localization(locale).expect("utility supplies localization");
let args = uucore::args_os();
let code = match name {
"arch" => uu_arch::uumain(args),
"base32" => uu_base32::uumain(args),
"base64" => uu_base64::uumain(args),
"basename" => uu_basename::uumain(args),
"basenc" => uu_basenc::uumain(args),
"cat" => uu_cat::uumain(args),
"cksum" => uu_cksum::uumain(args),
"b2sum" => uu_b2sum::uumain(args),
"md5sum" => uu_md5sum::uumain(args),
"nice" => uu_nice::uumain(args),
"sha1sum" => uu_sha1sum::uumain(args),
"sha224sum" => uu_sha224sum::uumain(args),
"sha256sum" => uu_sha256sum::uumain(args),
"sha384sum" => uu_sha384sum::uumain(args),
"sha512sum" => uu_sha512sum::uumain(args),
"comm" => uu_comm::uumain(args),
"cp" => uu_cp::uumain(args),
"csplit" => uu_csplit::uumain(args),
"cut" => uu_cut::uumain(args),
"date" => uu_date::uumain(args),
"dd" => uu_dd::uumain(args),
"dir" => uu_dir::uumain(args),
"dircolors" => uu_dircolors::uumain(args),
"dirname" => uu_dirname::uumain(args),
"echo" => uu_echo::uumain(args),
"expand" => uu_expand::uumain(args),
"expr" => uu_expr::uumain(args),
"factor" => uu_factor::uumain(args),
"false" => uu_false::uumain(args),
"fmt" => uu_fmt::uumain(args),
"fold" => uu_fold::uumain(args),
"head" => uu_head::uumain(args),
"hostid" => uu_hostid::uumain(args),
"join" => uu_join::uumain(args),
"link" => uu_link::uumain(args),
"ln" => uu_ln::uumain(args),
"ls" => uu_ls::uumain(args),
"mkdir" => uu_mkdir::uumain(args),
"mktemp" => uu_mktemp::uumain(args),
"mv" => uu_mv::uumain(args),
"nl" => uu_nl::uumain(args),
"numfmt" => uu_numfmt::uumain(args),
"od" => uu_od::uumain(args),
"paste" => uu_paste::uumain(args),
"pathchk" => uu_pathchk::uumain(args),
"pr" => uu_pr::uumain(args),
"printenv" => uu_printenv::uumain(args),
"printf" => uu_printf::uumain(args),
"ptx" => uu_ptx::uumain(args),
"pwd" => uu_pwd::uumain(args),
"readlink" => uu_readlink::uumain(args),
"realpath" => uu_realpath::uumain(args),
"rm" => uu_rm::uumain(args),
"rmdir" => uu_rmdir::uumain(args),
"seq" => uu_seq::uumain(args),
"shred" => uu_shred::uumain(args),
"shuf" => uu_shuf::uumain(args),
"sleep" => uu_sleep::uumain(args),
"sort" => uu_sort::uumain(args),
"split" => uu_split::uumain(args),
"sum" => uu_sum::uumain(args),
"tail" => uu_tail::uumain(args),
"tee" => uu_tee::uumain(args),
"test" => uu_test::uumain(args),
"touch" => uu_touch::uumain(args),
"tr" => uu_tr::uumain(args),
"true" => uu_true::uumain(args),
"truncate" => uu_truncate::uumain(args),
"tsort" => uu_tsort::uumain(args),
"tty" => uu_tty::uumain(args),
"uname" => uu_uname::uumain(args),
"unexpand" => uu_unexpand::uumain(args),
"uniq" => uu_uniq::uumain(args),
"unlink" => uu_unlink::uumain(args),
"vdir" => uu_vdir::uumain(args),
"wc" => uu_wc::uumain(args),
"yes" => uu_yes::uumain(args),
"nproc" => uu_nproc::uumain(args),
_ => 127,
};
std::process::exit(code);
}
-78
View File
@@ -1,78 +0,0 @@
arch
base32
base64
basename
basenc
cat
cksum
b2sum
md5sum
nice
sha1sum
sha224sum
sha256sum
sha384sum
sha512sum
comm
cp
csplit
cut
date
dd
dir
dircolors
dirname
echo
expand
expr
factor
false
fmt
fold
head
hostid
join
link
ln
ls
mkdir
mktemp
mv
nl
numfmt
od
paste
pathchk
pr
printenv
printf
ptx
pwd
readlink
realpath
rm
rmdir
seq
shred
shuf
sleep
sort
split
sum
tail
tee
test
touch
tr
true
truncate
tsort
tty
uname
unexpand
uniq
unlink
vdir
wc
yes
nproc
+17 -1
View File
@@ -238,6 +238,22 @@ pub fn status(repo: &gix::Repository) -> Result<Status> {
.map_err(|e| error("status", e))?;
for item in iter {
let item = item.map_err(|e| error("status", e))?;
#[cfg(target_os = "wasi")]
if matches!(
&item,
Item::Modification {
status: gix::status::plumbing::index_as_worktree::EntryStatus::Change(
gix::status::plumbing::index_as_worktree::Change::Modification {
executable_bit_changed: true,
content_change: None,
..
}
),
..
}
) {
continue;
}
let path = item.rela_path().to_str_lossy().into_owned();
let Some(summary) = item.summary() else {
continue;
@@ -332,7 +348,7 @@ fn worktree_file(
} else {
entry.mode
};
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "wasi")))]
let mode = if mode != 0o120000 && caps.executable_bit {
use std::os::unix::fs::PermissionsExt;
if meta.permissions().mode() & 0o111 != 0 {
+2 -2
View File
@@ -229,7 +229,7 @@ fn work_entry(
Mode::SYMLINK,
)
} else if m.is_file() {
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "wasi")))]
let mode = {
use std::os::unix::fs::PermissionsExt;
if m.permissions().mode() & 0o111 != 0 {
@@ -238,7 +238,7 @@ fn work_entry(
Mode::FILE
}
};
#[cfg(not(unix))]
#[cfg(any(not(unix), target_os = "wasi"))]
let mode = if previous == Some(Mode::FILE_EXECUTABLE) {
Mode::FILE_EXECUTABLE
} else {
+18
View File
@@ -268,6 +268,24 @@ def main [component: path] {
print $'($args | str join " "): (if $equal {"PASS documented usage"} else {"FAIL documented usage"}) wasi=($result.exit_code)'
if not $equal { $failures += 1; print ($result | to json) }
}
# WASI checkout and staging retain an existing executable file's indexed mode.
let executable = ($root | path join executable)
mkdir $executable
^git -C $executable init -q -b main
"#!/bin/sh\necho executable\n" | save ($executable | path join run.sh)
^chmod +x ($executable | path join run.sh)
^git -C $executable add run.sh
^git -C $executable commit --no-gpg-sign -q -m executable
rm ($executable | path join run.sh)
mut executable_ok = true
for args in [[checkout -q HEAD -- run.sh] [status --short] [diff] [add run.sh] [diff --cached] [status --short]] {
let result = (^wasmtime run -S http=y -S cli-exit-with-code=y --dir $'($executable)::/mode' $component -C /mode ...$args | complete)
if $result.exit_code != 0 or $result.stdout != '' { $executable_ok = false; print ($result | to json) }
}
let indexed = (^git -C $executable ls-files --stage run.sh | complete)
$executable_ok = ($executable_ok and $indexed.exit_code == 0 and ($indexed.stdout | str starts-with '100755 '))
print $'executable checkout stays clean: (if $executable_ok {"PASS"} else {"FAIL"})'
if not $executable_ok { $failures += 1 }
# A merge tree that differs from both parents requires a combined diff.
'combined resolution\n' | save --force ($root | path join native/repo/file.txt)
let native = ($root | path join native/repo)
+1 -1
View File
@@ -5,7 +5,7 @@ mod bindings {
fn main() {
let args: Vec<String> = std::env::args().collect();
match std::path::Path::new(&args[0])
.file_name()
.file_stem()
.unwrap()
.to_str()
.unwrap()
+1 -7
View File
@@ -41,13 +41,7 @@ impl Command {
pub fn stderr(&mut self, mode: Stdio) -> &mut Self { self.stderr = mode; self }
/// Resolve PATH and start the component.
pub fn spawn(&self) -> io::Result<Child> {
let program = if self.program.contains('/') { self.program.clone() } else {
self.env.get("PATH").map(String::as_str).unwrap_or("/usr/local/bin:/bin").split(':')
.map(|dir| self.cwd.join(dir).join(&self.program))
.find(|p| p.is_file()).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, &self.program[..]))?
.to_string_lossy().into_owned()
};
let child = host::spawn(&host::Request { program, argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(),
let child = host::spawn(&host::Request { program: self.program.clone(), argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(),
env: self.env.clone().into_iter().collect(), cwd: self.cwd.to_string_lossy().into_owned(), group: self.group,
stdin: self.stdin, stdout: self.stdout, stderr: self.stderr }).map_err(io::Error::other)?;
Ok(Child { stdin: matches!(self.stdin, Stdio::Pipe).then(|| child.stdin()),