{ 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); }; 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 extensions agents skills Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore # This browser fixture links to an unbuilt upstream artifact, not a WASI tool. git rm --cached userland/esbuild/source/scripts/browser/esbuild.wasm.bagel 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.' substituteInPlace userland/bundle.nu --replace-fail \ 'userland extensions Cargo.toml' 'userland extensions agents skills Cargo.toml' 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/ ''; doInstallCargoArtifacts = false; }); sausage = craneLib.buildPackage (common // { inherit cargoArtifacts; 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.cargoTest (common // wasiCcEnv // { inherit cargoArtifacts; cargoExtraArgs = "--workspace --lib --test harness --test p1 --test durable --test 'cred*' --test layers --test regressions --test streams --test wasi"; preCheck = '' export SAUSAGE_CACHE="$TMPDIR/sausage-cache" export CARGO_TARGET_DIR="$PWD/target" 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; }); }); }