221 lines
11 KiB
Nix
221 lines
11 KiB
Nix
{
|
|
description = "Sausage: a world of WASI programs and durable agents";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
flake-utils.url = "github:numtide/flake-utils";
|
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
|
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
|
crane.url = "github:ipetkov/crane";
|
|
kache = {
|
|
url = "github:kunobi-ninja/kache/stable";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
|
|
outputs = { self, nixpkgs, flake-utils, rust-overlay, crane, kache, ... }:
|
|
flake-utils.lib.eachSystem flake-utils.lib.allSystems (system:
|
|
let
|
|
pkgs = import nixpkgs {
|
|
inherit system;
|
|
overlays = [ rust-overlay.overlays.default ];
|
|
};
|
|
rust = pkgs.rust-bin.stable.latest.default.override {
|
|
targets = [ "wasm32-wasip2" ];
|
|
extensions = [ "rust-src" ];
|
|
};
|
|
mold = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.mold ];
|
|
hostTarget = pkgs.lib.toUpper (builtins.replaceStrings [ "-" ] [ "_" ]
|
|
pkgs.stdenv.hostPlatform.rust.rustcTarget);
|
|
moldVar = "CARGO_TARGET_${hostTarget}_RUSTFLAGS";
|
|
moldEnv = pkgs.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);
|
|
# A WASI clang with wasi-libc, so that build scripts (the `cc` crate)
|
|
# can compile C for wasm32-wasip2. Its headers are WASI 0.1: C code
|
|
# that needs sockets does not build.
|
|
wasiCc = pkgs.pkgsCross.wasi32.stdenv.cc;
|
|
wasiCcEnv = {
|
|
CC_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}cc";
|
|
AR_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}ar";
|
|
};
|
|
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
|
|
# Preserve the embedded JavaScript and WIT alongside Cargo's sources.
|
|
hostSrc = pkgs.lib.cleanSourceWith {
|
|
src = ./.;
|
|
filter = path: type:
|
|
let relative = pkgs.lib.removePrefix (toString ./. + "/") path;
|
|
in !((pkgs.lib.hasPrefix "userland/" relative && !(pkgs.lib.hasPrefix "userland/cwd-init" relative))
|
|
|| pkgs.lib.hasPrefix "spikes/" relative)
|
|
&& (craneLib.filterCargoSources path type
|
|
|| pkgs.lib.hasSuffix ".wit" path || pkgs.lib.hasSuffix ".witx" path
|
|
|| pkgs.lib.hasSuffix ".js" path || pkgs.lib.hasSuffix ".html" path
|
|
|| pkgs.lib.hasSuffix ".wasm" path || pkgs.lib.hasSuffix ".nu" path
|
|
|| pkgs.lib.hasSuffix ".c" path || pkgs.lib.hasSuffix ".patch" path);
|
|
};
|
|
v8Archive = pkgs.fetchurl {
|
|
# deno_core enables rusty_v8's simdutf feature.
|
|
url = "https://github.com/denoland/rusty_v8/releases/download/v150.4.0/librusty_v8_simdutf_release_${pkgs.stdenv.hostPlatform.rust.rustcTarget}.a.gz";
|
|
hash = {
|
|
x86_64-linux = "sha256-9IdiyhDR8fxgWkQcWuQw7Izh6egPFNePvELLh4wwtHY=";
|
|
aarch64-linux = "sha256-U54oOBWjlqV5bzKFi0LlF7hY66rqqtBdAykO6MhkpSc=";
|
|
x86_64-darwin = "sha256-p1AnH+xrIRRX7Qpc99LqsZJLJlYhqC2oarlZ1v8II+Q=";
|
|
aarch64-darwin = "sha256-Wu/9jVoMG3msHXCvg9WxkJllX9nGRaeU3EPxAfd5g4w=";
|
|
}.${system};
|
|
};
|
|
common = {
|
|
pname = "sausage";
|
|
version = "0.3.0";
|
|
src = hostSrc;
|
|
strictDeps = true;
|
|
RUSTY_V8_ARCHIVE = v8Archive;
|
|
buildInputs = [ pkgs.openssl ];
|
|
nativeBuildInputs = [ pkgs.pkg-config pkgs.cmake pkgs.python3 pkgs.perl ] ++ mold;
|
|
} // moldEnv;
|
|
cargoArtifacts = craneLib.buildDepsOnly (common // {
|
|
doCheck = false;
|
|
});
|
|
brushSource = pkgs.fetchFromGitHub {
|
|
owner = "reubeno";
|
|
repo = "brush";
|
|
rev = "943553433ae04f5b3bd55d2cf207e39eb9ef2af7";
|
|
hash = "sha256-a1eMkqx4nBqJ8R+h+rxtU35pAungcEnXoxwZnb1poH8=";
|
|
};
|
|
# bundle.nu archives these sources, not the sandbox's development patches.
|
|
userlandSrc = pkgs.lib.cleanSource ./.;
|
|
revision = self.rev or self.dirtyRev or "unknown";
|
|
userlandCommon = {
|
|
pname = "sausage-userland";
|
|
version = "0.3.0";
|
|
src = userlandSrc;
|
|
strictDeps = true;
|
|
cargoVendorDir = craneLib.vendorMultipleCargoDeps {
|
|
cargoLockList = [ ./userland/Cargo.lock ./userland/brush/Cargo.lock ];
|
|
};
|
|
nativeBuildInputs = [ pkgs.nushell pkgs.wasm-tools pkgs.git pkgs.pkg-config pkgs.go ] ++ mold;
|
|
GOTOOLCHAIN = "local";
|
|
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";
|
|
postPatch = ''
|
|
git init --quiet --initial-branch=main
|
|
git add host vfs userland agents skills extensions Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore
|
|
git commit --no-gpg-sign --quiet -m 'Bundle source ${revision}'
|
|
cp -r ${brushSource} userland/brush/source
|
|
chmod -R u+w userland/brush/source
|
|
# The fixed-output source fetch replaces the development checkout's Git check.
|
|
substituteInPlace userland/brush/build.nu --replace-fail \
|
|
${pkgs.lib.escapeShellArg "if (^git -C $source rev-parse HEAD | str trim) != $revision { error make {msg: 'unexpected brush revision'} }"} \
|
|
'# Revision verified by the fixed-output Nix fetch.'
|
|
patchShebangs userland/cwd-init/link.nu
|
|
'';
|
|
preBuild = ''
|
|
export CARGO_TARGET_DIR="$PWD/target"
|
|
export GOCACHE="$TMPDIR/go-cache"
|
|
'';
|
|
buildPhaseCargoCommand = ''
|
|
nu userland/bundle.nu
|
|
# bundle.nu sees the reconstructed repository's HEAD. Restore the flake revision.
|
|
git -C target/userland/bundle/src/sausage commit --amend --no-gpg-sign --quiet \
|
|
-m 'Bundle source ${revision}'
|
|
'';
|
|
doCheck = false;
|
|
} // wasiCcEnv // moldEnv;
|
|
# Keep the real local patch crates: registry dependencies use their full APIs.
|
|
userlandArtifacts = craneLib.buildDepsOnly (userlandCommon // {
|
|
dummySrc = userlandSrc;
|
|
});
|
|
userland = craneLib.mkCargoDerivation (userlandCommon // {
|
|
cargoArtifacts = userlandArtifacts;
|
|
installPhaseCommand = ''
|
|
mkdir -p $out
|
|
cp -a target/userland/bundle/. $out/
|
|
test -f $out/bin/chat.wasm
|
|
test -f $out/etc/systemd/system/chat.service
|
|
test -f $out/etc/systemd/system/multi-user.target.wants/chat.service
|
|
'';
|
|
doInstallCargoArtifacts = false;
|
|
# The upstream browser fixture links to an optional, unbuilt artifact.
|
|
dontCheckForBrokenSymlinks = true;
|
|
});
|
|
sausage = craneLib.buildPackage (common // {
|
|
inherit cargoArtifacts;
|
|
# Deno loads extension TypeScript from the vendored sources at runtime.
|
|
doNotRemoveReferencesToVendorDir = true;
|
|
cargoExtraArgs = "--locked -p sausage";
|
|
doCheck = false;
|
|
nativeBuildInputs = common.nativeBuildInputs ++ [ pkgs.makeWrapper ];
|
|
postInstall = ''
|
|
wrapProgram $out/bin/sausage --set-default SAUSAGE_USERLAND ${userland} \
|
|
--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'"}
|
|
'';
|
|
meta.mainProgram = "sausage";
|
|
});
|
|
in {
|
|
packages.default = sausage;
|
|
checks.package = sausage;
|
|
checks.tests = craneLib.mkCargoDerivation (common // wasiCcEnv // {
|
|
inherit cargoArtifacts;
|
|
pnameSuffix = "-test";
|
|
nativeBuildInputs = common.nativeBuildInputs ++ [ pkgs.git ];
|
|
doCheck = true;
|
|
buildPhaseCargoCommand = "";
|
|
# Keep the same sandbox-compatible modules in the shared host test executable.
|
|
checkPhaseCargoCommand = ''
|
|
cargoWithProfile test --locked --workspace --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
|
|
'';
|
|
preCheck = ''
|
|
export SAUSAGE_CACHE="$TMPDIR/sausage-cache"
|
|
export CARGO_TARGET_DIR="$PWD/target"
|
|
export RUST_BACKTRACE=1
|
|
# Flake sources omit Git history. Map the recorded revision to independently
|
|
# staged source trees for P1's directory-hash comparison.
|
|
cp -r ${userlandSrc}/agents ${userlandSrc}/skills ${userlandSrc}/extensions .
|
|
git init --quiet
|
|
git add agents skills extensions
|
|
git -c user.name=Sausage -c user.email=sausage@localhost \
|
|
commit --no-gpg-sign --quiet -m 'Flake source trees'
|
|
git update-ref refs/replace/${revision} HEAD
|
|
mkdir -p target/userland
|
|
ln -s ${userland} target/bundle
|
|
ln -s ${userland} target/userland/bundle
|
|
'';
|
|
doInstallCargoArtifacts = false;
|
|
});
|
|
checks.shell = pkgs.runCommand "sausage-shell" { } ''
|
|
export SAUSAGE_CACHE="$TMPDIR/sausage-cache"
|
|
${sausage}/bin/sausage sh "$TMPDIR/world" -c \
|
|
'ls /bin | wc -l; echo hi | tr a-z A-Z; git --version; rg --version' > output
|
|
cat output
|
|
grep -qx HI output
|
|
grep -q 'git version' output
|
|
grep -q ripgrep output
|
|
${sausage}/bin/sausage sh "$TMPDIR/world" -c \
|
|
'set -e; awk "BEGIN {print 42}"; echo payload > /payload; tar -cf /test.tar /payload; tar -tf /test.tar; qjs -e "console.log(42)"; box --help; cargo --help; undo list' > tools
|
|
cat tools
|
|
grep -qx 42 tools
|
|
grep -q payload tools
|
|
test -d "$TMPDIR/world/world/src/sausage/extensions"
|
|
test -d "$TMPDIR/world/world/src/sausage/agents"
|
|
test -d "$TMPDIR/world/world/src/sausage/skills"
|
|
touch $out
|
|
'';
|
|
apps.default = { type = "app"; program = "${sausage}/bin/sausage"; };
|
|
devShells.default = pkgs.mkShell (wasiCcEnv // moldEnv // {
|
|
packages = [ rust pkgs.wasmtime pkgs.openssl pkgs.pkg-config ] ++ builderTools ++ mold;
|
|
});
|
|
});
|
|
}
|