Merge branch 'v3' into v3-t98

This commit is contained in:
2026-11-12 14:47:38 +00:00
335 changed files with 134295 additions and 516 deletions
+8 -2
View File
@@ -37,7 +37,6 @@ including its own. Keep it that way.
about Sausage. Host code reads and writes the world only through it.
- `userland/`: the default CLIs of a world, built for `wasm32-wasip2` in the root Cargo workspace.
The patched Brush crates live in `userland/brush/source/` and share the root lock file.
- `spikes/`: accepted prototypes. Read them for evidence, copy from them, do not build on them.
## Programs in the world
@@ -55,7 +54,14 @@ nix develop -c nu userland/bundle.nu
nix develop -c cargo build -p git --target wasm32-wasip2 --release
```
`nix flake check` checks the packaged host, its bundle, and sandbox-compatible tests.
`nix build` uses separate Crane pipelines for the native host and WASI userland.
Go tools and QuickJS build separately. Nix assembles the bundle without `bundle.nu`.
Both Nix and the incremental development bundle read the command names from `userland/tools.json`.
Coreutils, diffutils, procps, and brush use relative symlinks to one binary each.
`nix flake check` checks the packaged host, its bundle, and the whole host/VFS test suite.
Sandbox-incompatible tests print an explicit skip reason instead of a flake-level filter.
The check sets `SAUSAGE_NIX_CHECK=1` for tests that need network access or unvendored fixture dependencies.
## Tests
+15 -4
View File
@@ -9,7 +9,14 @@ Keep the complete world folder between starts.
## Build and start
Run host commands from a checkout of the repository.
Use Nix with flakes. The package supplies the native Rust builder and its tools.
Use Nix with flakes and the `pipe-operators` experimental feature.
The package supplies the native Rust builder and its tools.
`nix build` builds the host and userland through separate Crane pipelines.
The userland pipeline targets `wasm32-wasip2`.
Separate Go derivations build awk and esbuild for WASI preview1.
QuickJS builds from its vendored C sources with WASI clang.
Nix assembles the final bundle without the development bundle script.
Use `sausage run --no-builder-sandbox WORLD` only when the host must run the builder without namespace isolation.
The package entry point is:
@@ -36,11 +43,15 @@ The source-tree bundle builder is:
nix develop -c nu userland/bundle.nu
```
It builds the world commands and creates `target/userland/bundle` by default.
It snapshots committed source, not uncommitted edits.
It builds the world commands incrementally with the configured Cargo cache.
Pass a destination to place the bundle under your shared executor target directory.
The script snapshots the current source files, including uncommitted edits.
The source archive includes the complete `config/`, `skills/`, and `extensions/` directories.
The bundle contains plain source files. `sausage init` copies them without a Git repository or first commit.
The world copier preserves symlinks, including dangling links in the source tree.
Both bundle builders read `userland/tools.json` for the package and command names.
Coreutils, diffutils, procps, and brush each occupy one real binary in `bin/`.
Their command names are relative symlinks to those binaries.
The world copier preserves these symlinks and dangling links in the source tree.
A fresh development world needs no manual source copy.
The bundle includes `/bin/web.wasm` and an enabled `/etc/systemd/system/web.service`.
World init starts that service on `0.0.0.0:8080`.
+100 -64
View File
@@ -15,18 +15,22 @@
flake-utils.lib.eachSystem flake-utils.lib.allSystems (system:
let
pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; };
inherit (pkgs) lib;
rust = pkgs.rust-bin.stable.latest.default.override {
targets = [ "wasm32-wasip2" ];
extensions = [ "rust-src" ];
};
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
src = pkgs.nix-gitignore.gitignoreSource [] ./.;
mold = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.mold ];
hostTarget = pkgs.lib.toUpper (builtins.replaceStrings [ "-" ] [ "_" ] pkgs.stdenv.hostPlatform.rust.rustcTarget);
src = ./. |> pkgs.nix-gitignore.gitignoreSource [];
tools = ./userland/tools.json |> builtins.readFile |> builtins.fromJSON;
commandArgs = tools |> builtins.filter (t: t.package != null)
|> map (t: t.package) |> lib.unique |> map (p: "-p ${p}") |> lib.concatStringsSep " ";
mold = lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.mold ];
hostTarget = pkgs.stdenv.hostPlatform.rust.rustcTarget |> builtins.replaceStrings [ "-" ] [ "_" ] |> lib.toUpper;
moldVar = "CARGO_TARGET_${hostTarget}_RUSTFLAGS";
moldEnv = pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { ${moldVar} = "-C link-arg=-fuse-ld=mold"; };
moldEnv = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { ${moldVar} = "-C link-arg=-fuse-ld=mold"; };
builderTools = [ kache.packages.${system}.default pkgs.bubblewrap pkgs.nushell pkgs.wasm-tools pkgs.bash pkgs.coreutils ];
runtimePath = pkgs.lib.makeBinPath ([ rust pkgs.stdenv.cc ] ++ builderTools ++ mold);
runtimePath = [ rust pkgs.stdenv.cc ] ++ builderTools ++ mold |> lib.makeBinPath;
wasiCc = pkgs.pkgsCross.wasi32.stdenv.cc;
wasiCcEnv = {
CC_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}cc";
@@ -41,7 +45,7 @@
aarch64-darwin = "sha256-Wu/9jVoMG3msHXCvg9WxkJllX9nGRaeU3EPxAfd5g4w=";
}.${system};
};
# rusty_v8's documented archive hook prevents downloads inside the Nix sandbox.
# rusty_v8's archive hook prevents downloads inside the Nix sandbox.
v8Env = { RUSTY_V8_ARCHIVE = v8Archive; };
cwdInit = pkgs.runCommand "sausage-cwd-init" { nativeBuildInputs = [ rust pkgs.nushell pkgs.wasm-tools ]; } ''
cp -r ${./userland/cwd-init} cwd-init
@@ -50,60 +54,94 @@
mkdir $out
cp cwd-init/init.o $out/
'';
linker = pkgs.writeScript "sausage-wasi-link.nu" ("#!${pkgs.nushell}/bin/nu\n" + builtins.readFile ./userland/cwd-init/link.nu);
members = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.members;
commands = builtins.filter (p: pkgs.lib.hasPrefix "userland/" p && !(pkgs.lib.hasPrefix "userland/brush/" p)) members;
commandArgs = pkgs.lib.concatMapStringsSep " " (p: "-p " + (builtins.fromTOML (builtins.readFile (./. + "/${p}/Cargo.toml"))).package.name) commands;
linker = pkgs.runCommand "sausage-wasi-link" { nativeBuildInputs = [ pkgs.nushell ]; } ''
cp ${./userland/cwd-init/link.nu} $out
chmod +x $out
patchShebangs $out
'';
common = {
pname = "sausage";
version = "0.3.0";
inherit src;
strictDeps = true;
buildInputs = [ pkgs.openssl ];
nativeBuildInputs = [ pkgs.pkg-config pkgs.cmake pkgs.python3 pkgs.perl pkgs.nushell pkgs.wasm-tools pkgs.go ] ++ mold;
nativeBuildInputs = [ pkgs.pkg-config pkgs.cmake pkgs.python3 pkgs.perl pkgs.nushell pkgs.wasm-tools ];
# The WASI linker initializes each guest's cwd before main.
SAUSAGE_CWD_INIT = "${cwdInit}/init.o";
CARGO_TARGET_WASM32_WASIP2_LINKER = linker;
CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS = "-C link-arg=sausage-cwd-init";
GOTOOLCHAIN = "local";
} // v8Env // wasiCcEnv // moldEnv;
cargoArtifacts = craneLib.buildDepsOnly (common // {
# Registry gix crates need the real APIs of the local WASI patches.
extraDummyScript = ''
chmod -R u+w $out/userland/git/vendor
cp -r ${./userland/git/vendor}/. $out/userland/git/vendor/
'';
buildPhaseCargoCommand = ''
cargoWithProfile build --locked -p sausage -p sausage-vfs
cargoWithProfile build --locked --target wasm32-wasip2 ${commandArgs}
cargoWithProfile build --locked --target wasm32-wasip2 -p brush-shell --no-default-features --features minimal,tokio/time
'';
doCheck = false;
});
userland = craneLib.mkCargoDerivation (common // {
inherit cargoArtifacts;
pname = "sausage-userland";
preBuild = ''
export CARGO_TARGET_DIR="$PWD/target"
export GOCACHE="$TMPDIR/go-cache"
'';
buildPhaseCargoCommand = "nu userland/bundle.nu";
doCheck = false;
installPhaseCommand = ''
mkdir -p $out
cp -a target/userland/bundle/. $out/
test -f $out/bin/web.wasm
test -f $out/srv/www/index.html
test -f $out/etc/systemd/system/web.service
test -f $out/etc/systemd/system/multi-user.target.wants/web.service
'';
doInstallCargoArtifacts = false;
dontCheckForBrokenSymlinks = true;
});
sausageUnwrapped = craneLib.buildPackage (common // {
inherit cargoArtifacts;
doNotRemoveReferencesToVendorDir = true;
} // wasiCcEnv;
hostArgs = common // v8Env // moldEnv // {
cargoExtraArgs = "--locked -p sausage";
buildInputs = [ pkgs.openssl ];
nativeBuildInputs = common.nativeBuildInputs ++ mold;
doCheck = false;
};
hostArtifacts = craneLib.buildDepsOnly hostArgs;
userlandArgs = common // {
pname = "sausage-userland";
CARGO_BUILD_TARGET = "wasm32-wasip2";
cargoExtraArgs = "--locked ${commandArgs}";
doCheck = false;
};
userlandArtifacts = craneLib.buildDepsOnly userlandArgs;
userland = craneLib.buildPackage (userlandArgs // { cargoArtifacts = userlandArtifacts; });
goTool = name: source: subPackages:
(pkgs.buildGoModule {
pname = "sausage-${name}";
version = "vendored";
src = source;
inherit subPackages;
vendorHash = null;
env.CGO_ENABLED = "0";
ldflags = [ "-s" "-w" ];
doCheck = false;
dontStrip = true;
postInstall = ''
mv $out/bin/wasip1_wasm/* $out/bin/${name}.wasm
rmdir $out/bin/wasip1_wasm
'';
}).overrideAttrs (old: { env = old.env // { GOOS = "wasip1"; GOARCH = "wasm"; }; });
awk = goTool "awk" ./userland/goawk/source [ "." ];
esbuild = goTool "esbuild" ./userland/esbuild/source [ "cmd/esbuild" ];
qjs = pkgs.stdenv.mkDerivation {
pname = "sausage-qjs";
version = "0.18.0";
src = ./userland/qjs/source;
nativeBuildInputs = [ pkgs.cmake pkgs.llvmPackages_21.lld ];
cmakeFlags = [ "-DCMAKE_SYSTEM_NAME=WASI" "-DCMAKE_C_COMPILER=${wasiCcEnv.CC_wasm32_wasip2}" "-DCMAKE_AR=${wasiCcEnv.AR_wasm32_wasip2}" "-DCMAKE_RANLIB=${wasiCc}/bin/${wasiCc.targetPrefix}ranlib" ];
buildFlags = [ "qjs_exe" ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp qjs $out/bin/qjs.wasm
runHook postInstall
'';
dontStrip = true;
};
bundle = pkgs.runCommand "sausage-bundle" {} ''
mkdir -p $out/bin $out/src/sausage $out/etc/sausage $out/etc/systemd/system/multi-user.target.wants $out/srv/www
${tools |> map (t: ''
cp ${(if t.package != null then userland else { inherit awk esbuild qjs; }.${t.binary})}/bin/${t.binary}.wasm $out/bin/
${t.names |> map (name: ''
${lib.optionalString (name != t.binary) "ln -s ${t.binary}.wasm $out/bin/${name}.wasm"}
ln -s ${t.binary}.wasm $out/bin/${name}
'') |> lib.concatStrings}
'') |> lib.concatStrings}
cp -R --no-preserve=mode ${src}/. $out/src/sausage/
cp ${./config/config.yaml} $out/etc/sausage/config.yaml
cp -R ${./config/roster} $out/etc/sausage/roster
cp ${./userland/web/web.service} $out/etc/systemd/system/web.service
touch $out/etc/systemd/system/multi-user.target.wants/web.service
cp ${./userland/web/index.html} ${./userland/web/credentials.js} ${./userland/web/credentials-view.js} $out/srv/www/
'';
sausageUnwrapped = craneLib.buildPackage (hostArgs // {
cargoArtifacts = hostArtifacts;
doNotRemoveReferencesToVendorDir = true;
});
# Keep the wrapper separate from crane's removal of toolchain references.
sausage = pkgs.symlinkJoin {
@@ -111,40 +149,37 @@
paths = [ sausageUnwrapped ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/sausage --set-default SAUSAGE_USERLAND ${userland} \
wrapProgram $out/bin/sausage --set-default SAUSAGE_USERLAND ${bundle} \
--prefix PATH : ${runtimePath} \
--set-default CC_wasm32_wasip2 ${wasiCcEnv.CC_wasm32_wasip2} \
--set-default AR_wasm32_wasip2 ${wasiCcEnv.AR_wasm32_wasip2} \
${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux "--set-default ${moldVar} '-C link-arg=-fuse-ld=mold'"}
${lib.optionalString pkgs.stdenv.hostPlatform.isLinux "--set-default ${moldVar} '-C link-arg=-fuse-ld=mold'"}
'';
meta.mainProgram = "sausage";
};
in {
packages.default = sausage;
packages = { default = sausage; inherit bundle userland awk esbuild qjs; };
checks.package = sausage;
checks.tests = craneLib.mkCargoDerivation (common // {
inherit cargoArtifacts;
pnameSuffix = "-test";
nativeBuildInputs = common.nativeBuildInputs ++ [ pkgs.git ];
checks.tests = craneLib.cargoTest (hostArgs // {
cargoArtifacts = hostArtifacts;
cargoExtraArgs = "--locked -p sausage -p sausage-vfs";
# Guest builds and cold compilations share the check's CPU and memory.
cargoTestExtraArgs = "-- --nocapture --test-threads=1";
doCheck = true;
buildPhaseCargoCommand = "";
checkPhaseCargoCommand = ''
cargoWithProfile test --locked -p sausage -p sausage-vfs --lib
cargoWithProfile test --locked -p sausage --test it -- harness:: p1:: durable:: cred:: cred_ --skip undo_harness::
cargoWithProfile test --locked -p sausage-vfs --test layers --test regressions --test streams --test wasi
'';
nativeBuildInputs = hostArgs.nativeBuildInputs ++ builderTools ++ [ pkgs.git pkgs.nodejs ];
SAUSAGE_TEST_NO_BUILDER_SANDBOX = "1";
SAUSAGE_NIX_CHECK = "1";
SAUSAGE_USERLAND = bundle;
preCheck = ''
export SAUSAGE_USERLAND="${userland}"
export SAUSAGE_CACHE="$TMPDIR/sausage-cache"
export CARGO_TARGET_DIR="$PWD/target"
export RUST_BACKTRACE=1
mkdir -p target/userland
ln -s ${userland} target/bundle
ln -s ${userland} target/userland/bundle
ln -s ${bundle} target/bundle
ln -s ${bundle} target/userland/bundle
'';
doInstallCargoArtifacts = false;
});
checks.shell = pkgs.runCommand "sausage-shell" { } ''
checks.shell = pkgs.runCommand "sausage-shell" {} ''
export SAUSAGE_CACHE="$TMPDIR/sausage-cache"
${sausage}/bin/sausage sh "$TMPDIR/world" -c \
'set -e; echo hi | tr a-z A-Z; git --version; rg --version; awk "BEGIN {print 42}"; echo payload > /payload; tar -cf /test.tar /payload; tar -tf /test.tar; qjs -e "console.log(42)"; cargo --help; undo list' > output
@@ -155,11 +190,12 @@
grep -qx 42 output
grep -q payload output
test -d "$TMPDIR/world/world/src/sausage/extensions"
test -f "$TMPDIR/world/world/etc/sausage/roster/sausage.md"
touch $out
'';
apps.default = { type = "app"; program = "${sausage}/bin/sausage"; };
devShells.default = pkgs.mkShell (v8Env // wasiCcEnv // moldEnv // {
packages = [ rust pkgs.wasmtime pkgs.openssl pkgs.pkg-config ] ++ builderTools ++ mold;
packages = [ rust pkgs.wasmtime pkgs.openssl pkgs.pkg-config pkgs.go pkgs.cmake pkgs.llvmPackages_21.lld ] ++ builderTools ++ mold;
});
});
}
+6
View File
@@ -170,6 +170,12 @@ fn cache_hits(base: &Path) -> anyhow::Result<u64> {
}
#[tokio::test(flavor = "multi_thread")]
async fn cargo_build_test_and_source_changes() -> anyhow::Result<()> {
if std::env::var("SAUSAGE_NIX_CHECK").as_deref() == Ok("1") {
println!(
"SKIP builder::cargo_build_test_and_source_changes: the isolated builder fixture requires crates.io access"
);
return Ok(());
}
let binaries = programs();
let temp = tempfile::tempdir()?;
let world = World::open(temp.path())?;
+9 -1
View File
@@ -10,7 +10,15 @@ async fn default_identity_and_which() -> anyhow::Result<()> {
let target = crate::common::build_git();
assert!(
std::process::Command::new("cargo")
.args(["build", "--release", "-p", "sausage-exec-tools", "--locked"])
.args([
"build",
"--release",
"--target",
"wasm32-wasip2",
"-p",
"sausage-exec-tools",
"--locked",
])
.current_dir(&userland)
.status()?
.success()
+2 -2
View File
@@ -60,7 +60,7 @@ fn sigterm_cancels_tool_and_resumes() -> anyhow::Result<()> {
"-p",
"sausage-coreutils",
"--bin",
"sleep",
"coreutils",
"--manifest-path",
])
.arg(source.join("Cargo.toml"))
@@ -74,7 +74,7 @@ fn sigterm_cancels_tool_and_resumes() -> anyhow::Result<()> {
World::open(dir.path())?;
std::fs::create_dir_all(dir.path().join("world/bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/sleep.wasm"),
target.join("wasm32-wasip2/release/coreutils.wasm"),
dir.path().join("world/bin/sleep.wasm"),
)?;
let spawn = || {
+12
View File
@@ -52,6 +52,12 @@ async fn run(runtime: &Arc<Runtime>, url: String, chain: Vec<String>) -> String
}
#[tokio::test]
async fn identity_injection_denial_and_ambiguity() {
if std::env::var("SAUSAGE_NIX_CHECK").as_deref() == Ok("1") {
println!(
"SKIP hook::identity_injection_denial_and_ambiguity: the wstd fixture dependency is not vendored"
);
return;
}
let guest = hook_guest::component();
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
@@ -325,6 +331,12 @@ async fn upstream(
#[tokio::test]
async fn streamed_upload_redirect_and_http_admin_denial() {
if std::env::var("SAUSAGE_NIX_CHECK").as_deref() == Ok("1") {
println!(
"SKIP hook::streamed_upload_redirect_and_http_admin_denial: the wstd fixture dependency is not vendored"
);
return;
}
let guest = hook_guest::component();
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
+6
View File
@@ -16,6 +16,12 @@ use super::hook_guest;
#[tokio::test]
async fn one_api_for_pages_and_guest_requests() -> anyhow::Result<()> {
if std::env::var("SAUSAGE_NIX_CHECK").as_deref() == Ok("1") {
println!(
"SKIP hook_api::one_api_for_pages_and_guest_requests: the wstd fixture dependency is not vendored"
);
return Ok(());
}
use webauthn_authenticator_rs::{WebauthnAuthenticator, softpasskey::SoftPasskey};
let dir = tempfile::tempdir()?;
let world = World::open(dir.path())?;
+6
View File
@@ -51,6 +51,12 @@ async fn run(runtime: &Arc<Runtime>, mode: &str, address: String) -> String {
}
#[tokio::test]
async fn guest_tcp_and_host_denial() {
if std::env::var_os("SAUSAGE_NIX_CHECK").is_some() {
eprintln!(
"SKIP net::guest_tcp_and_host_denial: the wstd fixture dependency is not vendored"
);
return;
}
let guest = hook_guest::component();
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
+19 -6
View File
@@ -66,15 +66,24 @@ async fn bundle_copy_and_real_shell() -> anyhow::Result<()> {
for entry in std::fs::read_dir(dir.path().join("world/bin"))? {
let entry = entry?;
if entry.file_type()?.is_symlink() {
assert_eq!(
std::fs::read_link(entry.path())?,
PathBuf::from(format!("{}.wasm", entry.file_name().to_string_lossy()))
);
let target = std::fs::read_link(entry.path())?;
assert_eq!(target.components().count(), 1, "{target:?}");
assert_eq!(target.extension().unwrap(), "wasm");
assert!(std::fs::symlink_metadata(entry.path().parent().unwrap().join(target))?.is_file());
} else {
assert_eq!(entry.path().extension().unwrap(), "wasm");
assert_eq!(entry.metadata()?.nlink(), 1);
}
}
let tools: Vec<serde_json::Value> = serde_json::from_str(include_str!("../../../userland/tools.json"))?;
for tool in tools {
let binary = format!("{}.wasm", tool["binary"].as_str().unwrap());
let bin = dir.path().join("world/bin");
assert!(std::fs::symlink_metadata(bin.join(&binary))?.is_file());
for name in tool["names"].as_array().unwrap() {
assert_eq!(std::fs::read_link(bin.join(name.as_str().unwrap()))?, PathBuf::from(&binary));
}
}
assert!(world.root.stat("/usr/local/bin", true).await?.directory);
let writable = world
.root
@@ -200,12 +209,16 @@ async fn bundle_copy_and_real_shell() -> anyhow::Result<()> {
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"))?;
std::fs::write(
dir.path().join("world/bin/replacement.wasm"),
wat::parse_str(r#"(module (memory (export "memory") 1) (func (export "_start")))"#)?,
)?;
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()
"rm /bin/cat /bin/cat.wasm; cp /bin/replacement.wasm /bin/cat.wasm; ln -s cat.wasm /bin/cat; cat /test.txt; head -n 1 /test.txt".into()
),
Options::default(),
&mut |stream, bytes| {
@@ -222,7 +235,7 @@ async fn bundle_copy_and_real_shell() -> anyhow::Result<()> {
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()),
Command::Shell("cp /bin/replacement.wasm /usr/local/bin/head.wasm; head /test.txt".into()),
Options::default(),
&mut |_, bytes| assert!(bytes.is_empty())
)
+4
View File
@@ -206,6 +206,10 @@ fn no_secrets(path: &Path, secrets: &[&[u8]]) -> Result<()> {
#[tokio::test(flavor = "current_thread")]
async fn request_approve_fetch_and_ssh_push() -> Result<()> {
if std::env::var_os("SAUSAGE_NIX_CHECK").is_some() {
eprintln!("SKIP p2::request_approve_fetch_and_ssh_push: the SSH fixture requires a host sshd service");
return Ok(());
}
let components = components()?;
let dir = tempfile::tempdir()?;
let world = World::open(dir.path())?;
+41
View File
@@ -107,6 +107,9 @@ mod features {
}
#[tokio::test(flavor = "multi_thread")]
async fn core_module_exit_and_cancellation() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (temp, runtime, processes) = world();
let bin = temp.path().join("world/bin");
@@ -140,6 +143,9 @@ mod features {
}
#[tokio::test(flavor = "multi_thread")]
async fn preview1_pipeline_and_esbuild() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (temp, runtime, _) = world();
warm(&runtime).await;
@@ -223,6 +229,9 @@ mod features {
}
#[tokio::test(flavor = "multi_thread")]
async fn inherited_stdio_identity_and_conversation() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (_temp, runtime, processes) = world();
warm(&runtime).await;
@@ -320,6 +329,9 @@ mod features {
}
#[tokio::test(flavor = "multi_thread")]
async fn independent_copies_share_compilation() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (temp, runtime, _) = world();
let bin = temp.path().join("world/bin");
@@ -349,6 +361,9 @@ mod features {
}
#[tokio::test(flavor = "multi_thread")]
async fn git_local_and_anonymous_https_clone() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (temp, runtime, _) = world();
warm(&runtime).await;
@@ -393,6 +408,17 @@ fn target() -> PathBuf {
.to_owned()
})
}
fn skip_unvendored_probe() -> bool {
if std::env::var_os("SAUSAGE_NIX_CHECK").is_none() {
return false;
}
eprintln!(
"SKIP {}: the isolated brush probe lacks a vendored lock file",
std::thread::current().name().unwrap()
);
true
}
fn build() {
BUILD.call_once(|| {
let root = repo();
@@ -529,6 +555,9 @@ async fn warm(runtime: &Arc<Runtime>) {
}
#[tokio::test(flavor = "multi_thread")]
async fn cwd_without_program_workaround() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (_temp, runtime, _) = world();
let (code, output, _) = run(&runtime, Command::Argv(vec!["/bin/probe".into()]), "/work").await;
@@ -561,6 +590,9 @@ async fn cwd_without_program_workaround() {
}
#[tokio::test(flavor = "multi_thread")]
async fn corpus_matches_bash() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (temp, runtime, _) = world();
warm(&runtime).await;
@@ -617,6 +649,9 @@ async fn empty(processes: &sausage::process::Processes) {
}
#[tokio::test(flavor = "multi_thread")]
async fn timeout_and_drop_kill_descendants() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (_temp, runtime, processes) = world();
warm(&runtime).await;
@@ -655,6 +690,9 @@ async fn timeout_and_drop_kill_descendants() {
}
#[tokio::test(flavor = "multi_thread")]
async fn ps_and_kill_pipeline() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (_temp, runtime, processes) = world();
warm(&runtime).await;
@@ -701,6 +739,9 @@ async fn ps_and_kill_pipeline() {
}
#[tokio::test(flavor = "multi_thread")]
async fn output_keeps_stream_order() {
if skip_unvendored_probe() {
return;
}
let _serial = SERIAL.lock().await;
let (_temp, runtime, _) = world();
warm(&runtime).await;
+5 -4
View File
@@ -35,8 +35,7 @@ async fn tool_call_commit_and_independent_change() {
);
let guest_dir = target.join("wasm32-wasip2/release");
if !guest_dir.join("git.wasm").is_file()
|| !guest_dir.join("cp.wasm").is_file()
|| !guest_dir.join("touch.wasm").is_file()
|| !guest_dir.join("coreutils.wasm").is_file()
|| !guest_dir.join("undo.wasm").is_file()
{
crate::common::cwd_init();
@@ -45,6 +44,8 @@ async fn tool_call_commit_and_independent_change() {
.args([
"build",
"--release",
"--target",
"wasm32-wasip2",
"-p",
"git",
"-p",
@@ -65,8 +66,8 @@ async fn tool_call_commit_and_independent_change() {
std::fs::create_dir_all(root.join("repo")).unwrap();
for (binary, guest) in [
("git", "git"),
("cp", "cp"),
("touch", "touch"),
("coreutils", "cp"),
("coreutils", "touch"),
("undo", "undo"),
] {
std::fs::copy(
+22 -4
View File
@@ -10,21 +10,39 @@ def main [host: path = 'result/bin/sausage'] {
print --stderr --no-newline $initialized.stderr
if $initialized.exit_code != 0 { error make {msg: 'packaged host init failed'} }
let script = r#'set -e
/bin/printf 'coreutils OK\n'
printf 'same\n' > /same
cmp /same /same
diff /same /same
ps | grep -q PID
sh -c 'echo sh OK'
bash -c 'echo bash OK'
qjs -e 'console.log("qjs source OK")'
mkdir -p /src/projects
cd /src/projects
cargo new packaged-probe --vcs none
printf 'fn main() { println!("packaged builder OK"); }\n' > packaged-probe/src/main.rs
cd packaged-probe
cargo build --release
./target/wasm32-wasip2/release/packaged-probe.wasm
cargo run --release
'#
let result = (^$host sh $world -c $script | complete)
print --no-newline $result.stdout
print --stderr --no-newline $result.stderr
if $result.exit_code != 0 { error make {msg: 'packaged builder failed'} }
if not ($result.stdout | lines | any {|line| $line == 'packaged builder OK' }) {
error make {msg: 'compiled program did not produce its expected output'}
for expected in ['coreutils OK' 'sh OK' 'bash OK' 'qjs source OK' 'packaged builder OK'] {
if not ($result.stdout | lines | any {|line| $line == $expected }) {
error make {msg: $'packaged check did not print: ($expected)'}
}
}
# Native run starts init. A bare command must retain argv[0] through its PATH symlink.
"[Service]\nType=oneshot\nExecStart=touch /init-PATH-ok\n" | save --force ($world | path join world etc systemd system web.service)
let supervised = (^timeout --signal INT 15s $host run $world | complete)
print --no-newline $supervised.stdout
print --stderr --no-newline $supervised.stderr
if $supervised.exit_code not-in [0 124] or not ($world | path join world init-PATH-ok | path exists) {
error make {msg: 'init did not resolve the bare command through its PATH symlink'}
}
print 'init PATH OK'
rm --recursive --force $temporary
print 'Packaged builder check passed.'
}
+1 -1
View File
@@ -67,7 +67,7 @@ harness = false
[features]
# Input backends. Each can be enabled for any target, and is offered where it
# works (see `build.rs`), so the defaults suit every target.
default = ["basic", "reedline", "minimal", "vt"]
default = ["minimal", "tokio/time"]
basic = ["brush-interactive/basic"]
minimal = ["brush-interactive/minimal"]
reedline = ["brush-interactive/reedline"]
+19 -22
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env nu
# Build independent commands and plain source files. World creation initializes their repository.
# Build the shared tool list incrementally and copy plain sources for world initialization.
def main [destination?: path] {
let here = ($env.FILE_PWD | path expand)
let destination = ($destination | default ($here | path join .. target userland bundle) | path expand)
@@ -12,32 +12,29 @@ def main [destination?: path] {
}
let manifest = ($here | path join .. Cargo.toml)
let metadata = (^cargo metadata --manifest-path $manifest --no-deps --format-version 1 | from json)
let packages = ($metadata.packages | where {|p|
($p.id in $metadata.workspace_members) and ($p.manifest_path | str starts-with $here) and not ($p.manifest_path | str starts-with ($here | path join brush source))
})
let arguments = ($packages | where {|p| $p.targets | any {|t| 'bin' in $t.kind } } | each {|p| ['-p' $p.name] } | flatten)
let tools = (open ($here | path join tools.json))
let packages = ($tools | where package != null | get package | uniq)
let arguments = ($packages | each {|p| ['-p' $p] } | flatten)
do { cd $here; ^cargo build --manifest-path $manifest --release --locked --target wasm32-wasip2 ...$arguments }
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'} }
if ($bin | path exists) { rm --recursive --force $bin }
mkdir $bin
^nu ($here | path join preview1.nu) $bin
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'preview1 build failed'} }
for package in $packages {
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 {
^cp --remove-destination $source ($bin | path join $'($name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle copy failed: ($name)'} }
}
}
for tool in ($tools | where package != null) {
let source = ($metadata.target_directory | path join wasm32-wasip2 release $'($tool.binary).wasm')
^cp --remove-destination $source ($bin | path join $'($tool.binary).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle copy failed: ($tool.binary)'} }
}
let brush = ($metadata.target_directory | path join wasm32-wasip2 release brush.wasm)
for name in [sh bash] {
^cp --remove-destination $brush ($bin | path join $'($name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush copy failed'} }
for tool in $tools {
for name in $tool.names {
if $name != $tool.binary {
^ln -s $'($tool.binary).wasm' ($bin | path join $'($name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'tool alias failed: ($name).wasm'} }
}
^ln -s $'($tool.binary).wasm' ($bin | path join $name)
if $env.LAST_EXIT_CODE != 0 { error make {msg: $'tool alias failed: ($name)'} }
}
}
let units = ($destination | path join etc systemd system)
mkdir ($units | path join multi-user.target.wants)
@@ -66,7 +63,7 @@ def main [destination?: path] {
mkdir $settings
^cp --remove-destination ($snapshot | path join config config.yaml) ($settings | path join config.yaml)
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'default config copy failed'} }
^cp -r ($snapshot | path join config roster) ($settings | path join roster)
^cp -r ($snapshot | path join config roster) $settings
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'default roster copy failed'} }
print $destination
}
+5
View File
@@ -2,6 +2,11 @@
name = "sausage-coreutils"
version = "0.1.0"
edition = "2024"
autobins = false
[[bin]]
name = "coreutils"
path = "src/main.rs"
[dependencies]
rustix = { version = "1.1.6", features = ["std"] }
-5
View File
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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
@@ -1,5 +0,0 @@
//! 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()));
}
+106
View File
@@ -0,0 +1,106 @@
//! Dispatch the bundled uutils commands by argv[0], or as coreutils <command>.
fn main() {
let argv: Vec<_> = std::env::args_os().collect();
let invoked = std::path::Path::new(&argv[0]).file_stem().unwrap();
let second = invoked == "coreutils";
if second {
if argv.len() < 2 {
eprintln!("Usage: coreutils <utility> [arguments...]");
std::process::exit(2);
}
uucore::set_utility_is_second_arg();
}
let name = uucore::util_name();
let name = name.strip_suffix(".wasm").unwrap_or(name);
let locale = match name {
"dir" | "vdir" => "ls",
_ => name,
};
uucore::locale::setup_localization(locale).expect("utility supplies localization");
let args = uucore::args_os().skip(usize::from(second));
let code = match name {
"arch" => uu_arch::uumain(args),
"b2sum" => uu_b2sum::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),
"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),
"md5sum" => uu_md5sum::uumain(args),
"mkdir" => uu_mkdir::uumain(args),
"mktemp" => uu_mktemp::uumain(args),
"mv" => uu_mv::uumain(args),
"nice" => uu_nice::uumain(args),
"nl" => uu_nl::uumain(args),
"nproc" => uu_nproc::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),
"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),
"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),
_ => {
eprintln!("coreutils: unknown utility: {name}");
127
}
};
std::process::exit(code);
}
+8 -8
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env nu
# Build pinned Go commands offline and copy the pinned QuickJS release.
# Build the pinned Go tools and QuickJS C sources for WASI preview1 during development.
def main [bin: path] {
let here = $env.FILE_PWD
let target = $env.CARGO_TARGET_DIR
let target = ($env.CARGO_TARGET_DIR | path expand)
mkdir $target $bin
for tool in [
{name: awk, source: goawk, package: '.'}
@@ -19,11 +19,11 @@ def main [bin: path] {
^cp --remove-destination $output ($bin | path join $'($tool.name).wasm')
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'preview1 bundle copy failed'} }
}
let qjs = ($here | path join qjs qjs.wasm)
let hash = (open --raw $qjs | hash sha256)
if $hash != '2c218412cb8b17e46ee91bea7556ec38f5b9b6bd6312fddd3ceeed14aa71eaaa' {
error make {msg: 'QuickJS release hash mismatch'}
}
^cp --remove-destination $qjs ($bin | path join qjs.wasm)
let build = ($target | path join qjs)
^cmake -S ($here | path join qjs source) -B $build -DCMAKE_SYSTEM_NAME=WASI $'-DCMAKE_C_COMPILER=($env.CC_wasm32_wasip2)' $'-DCMAKE_AR=($env.AR_wasm32_wasip2)' $'-DCMAKE_RANLIB=($env.AR_wasm32_wasip2 | str replace "-ar" "-ranlib")' -DCMAKE_BUILD_TYPE=Release
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'QuickJS configure failed'} }
^cmake --build $build --target qjs_exe --parallel
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'QuickJS build failed'} }
^cp --remove-destination ($build | path join qjs) ($bin | path join qjs.wasm)
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'QuickJS bundle copy failed'} }
}
+7 -7
View File
@@ -1,13 +1,13 @@
# QuickJS-ng
The bundle includes the unmodified WASI preview1 command from QuickJS-ng v0.18.0.
Sausage builds QuickJS-ng v0.18.0 from the unmodified C sources in `source/`.
The Nix package uses the existing WASI clang toolchain and upstream's CMake `qjs_exe` target.
The development bundle uses the same sources and target through `userland/preview1.nu`.
Neither path downloads a prebuilt executable.
- URL: https://github.com/quickjs-ng/quickjs/releases/download/v0.18.0/qjs-wasi.wasm
- SHA-256: `2c218412cb8b17e46ee91bea7556ec38f5b9b6bd6312fddd3ceeed14aa71eaaa`
- License: MIT. https://github.com/quickjs-ng/quickjs/blob/v0.18.0/LICENSE
`userland/preview1.nu` checks the hash before it copies the binary.
The binary is committed so bundle builds do not download it.
- Source: https://github.com/quickjs-ng/quickjs/tree/v0.18.0
- License: MIT. See `source/LICENSE`.
- Upstream WASI build: https://github.com/quickjs-ng/quickjs/blob/v0.18.0/.github/workflows/ci.yml
```sh
qjs -e 'console.log(6 * 7)'
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
# Don't show changes in generated files when doing git diff
gen/** -diff
builtin-*.h -diff

Some files were not shown because too many files have changed in this diff Show More