From 1cd9e4032fe9b004e09a66e380217403cf30894b Mon Sep 17 00:00:00 2001 From: kilyanni Date: Thu, 23 Jul 2026 09:57:08 +0200 Subject: [PATCH] nix: init --- WASIX-TODO.md | 12 +- pkgs/overlay/packages/boost.nix | 45 +++ pkgs/overlay/packages/libarchive.nix | 43 +++ pkgs/overlay/packages/libgit2.nix | 32 +++ pkgs/overlay/packages/llhttp/package.nix | 9 + .../wasi-is-not-the-js-wasm-build.patch | 30 ++ pkgs/overlay/packages/nix/package.nix | 92 ++++++ .../boost-modules-available-on-wasi.patch | 29 ++ .../no-boost-iostreams-mmap-on-wasi.patch | 41 +++ .../nix/patches/no-prelink-on-wasi.patch | 252 ++++++++++++++++ .../patches/portability-32-bit-libcxx.patch | 140 +++++++++ ...-handoff-instead-of-boost-coroutines.patch | 271 ++++++++++++++++++ .../unsupported-posix-apis-on-wasi.patch | 198 +++++++++++++ pkgs/overlay/packages/nix/tests/eval.nix | 69 +++++ pkgs/overlay/trivial.nix | 4 + ...wasixcc-relocatable-link-passthrough.patch | 166 +++++++++++ pkgs/toolchain/wasixcc.nix | 7 + 17 files changed, 1439 insertions(+), 1 deletion(-) create mode 100644 pkgs/overlay/packages/boost.nix create mode 100644 pkgs/overlay/packages/libarchive.nix create mode 100644 pkgs/overlay/packages/libgit2.nix create mode 100644 pkgs/overlay/packages/llhttp/package.nix create mode 100644 pkgs/overlay/packages/llhttp/patches/wasi-is-not-the-js-wasm-build.patch create mode 100644 pkgs/overlay/packages/nix/package.nix create mode 100644 pkgs/overlay/packages/nix/patches/boost-modules-available-on-wasi.patch create mode 100644 pkgs/overlay/packages/nix/patches/no-boost-iostreams-mmap-on-wasi.patch create mode 100644 pkgs/overlay/packages/nix/patches/no-prelink-on-wasi.patch create mode 100644 pkgs/overlay/packages/nix/patches/portability-32-bit-libcxx.patch create mode 100644 pkgs/overlay/packages/nix/patches/thread-handoff-instead-of-boost-coroutines.patch create mode 100644 pkgs/overlay/packages/nix/patches/unsupported-posix-apis-on-wasi.patch create mode 100644 pkgs/overlay/packages/nix/tests/eval.nix create mode 100644 pkgs/toolchain/wasixcc-relocatable-link-passthrough.patch diff --git a/WASIX-TODO.md b/WASIX-TODO.md index 876e4f5d..a9b3050a 100644 --- a/WASIX-TODO.md +++ b/WASIX-TODO.md @@ -133,7 +133,8 @@ Status: 🔴 needs upstream fix · 🟡 workaround in place · 🟢 fixed. with undefined symbols at link or dylib import. `if_indextoname` and `if_nametoindex` are declared in `net/if.h` but not defined at all. - Workaround: libuv's `libuv-0013-wasix-ifaddrs-names-no-if_index.patch` maps - the names and stubs the `if_*` lookups. + the names and stubs the `if_*` lookups. nix short-circuits its "are we + online" probe instead (`unsupported-posix-apis-on-wasi.patch`). - Fix: define the standard names in wasix-libc (alias or rename), and implement/stub `if_indextoname`/`if_nametoindex`. @@ -247,6 +248,15 @@ Status: 🔴 needs upstream fix · 🟡 workaround in place · 🟢 fixed. `wasix-libc-stubs.c`. That unblocks most util-linux programs; the rest need `fork` (`off` only), `sys/ipc.h`, or `PRIO_*`/`get,setpriority`. +### `statvfs` fails on a `--mapdir` directory 🟡 + +- `statvfs()` on a host directory mounted with `--mapdir`/`--volume` returns + ENOTSUP, so free-space checks fail. nix's local store calls it on every + operation that could trigger GC, which makes `nix --store /some/dir` unusable + (`nix eval --store dummy://` is unaffected: no filesystem, no statvfs). +- Fix: report the backing filesystem's numbers (or plausible ones) for mounted + host directories. + ## Toolchain ### wasixcc rejects `-fno-exceptions` under forced EH; stripped in the shim 🟡 diff --git a/pkgs/overlay/packages/boost.nix b/pkgs/overlay/packages/boost.nix new file mode 100644 index 00000000..3fcf519c --- /dev/null +++ b/pkgs/overlay/packages/boost.nix @@ -0,0 +1,45 @@ +# Boost for wasix: headers plus the one compiled library nix actually reaches. +# +# nixpkgs' boost can't cross here at all: b2 rejects `architecture=wasm`, and +# Boost.Context (which Boost.Coroutine2 rides on) has no wasm32 backend -- +# fcontext needs per-arch assembly, ucontext and WinFiber need APIs wasi lacks. +# So nix's use of boost::coroutines2 is patched out (see packages/nix) and what +# remains is header-only except Boost.URL, whose sources are compiled here +# directly (1.89 retired its single-TU src.hpp). +# +# Layout is a single prefix with include/ + lib/ because that is what meson's +# `boost_root` machine-file property expects (see packages/nix/package.nix). +{ + final, + prev, + helpers, + ... +}: +final.stdenv.mkDerivation { + pname = "boost"; + inherit (prev.boost) version src; + + dontConfigure = true; + + buildPhase = '' + runHook preBuild + find libs/url/src -name '*.cpp' -print0 \ + | xargs -0 -P "''${NIX_BUILD_CORES:-1}" -I {} \ + sh -c '$CXX -std=c++17 -I. -O2 -c "$1" -o "$1.o"' _ {} + find libs/url/src -name '*.o' -print0 | xargs -0 $AR rcs libboost_url.a + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/include $out/lib + cp -r boost $out/include/ + cp libboost_url.a $out/lib/ + runHook postInstall + ''; + + # Boost.URL is C++ with exceptions, so the no-EH profile is out. + passthru.wasix.supportedProfiles = helpers.profiles.withEh; + + meta = prev.boost.meta or {}; +} diff --git a/pkgs/overlay/packages/libarchive.nix b/pkgs/overlay/packages/libarchive.nix new file mode 100644 index 00000000..0542bf69 --- /dev/null +++ b/pkgs/overlay/packages/libarchive.nix @@ -0,0 +1,43 @@ +# libarchive for wasix (nix-util links it for tarball extraction). +# archive_read_disk_posix.c hard-requires fchdir (#error without HAVE_FCHDIR), +# which wasix-libc lacks (WASIX-TODO.md); configure's link test passes anyway +# because wasm-ld tolerates the undefined symbol. A declaration plus an ENOSYS +# stub keeps the archive_read_disk API linkable; only directory-tree reading +# uses it, and nix only reads archives from memory/fds. +{ + prev, + helpers, + ... +}: +helpers.libTweaks { + postPatch = '' + cat > wasix-fchdir.h <<'EOF' + #ifndef _WASIX_FCHDIR_H + #define _WASIX_FCHDIR_H + int fchdir(int); + #endif + EOF + cat > wasix-fchdir.c <<'EOF' + #include + int fchdir(int fd) { (void)fd; errno = ENOSYS; return -1; } + EOF + ''; + preConfigure = '' + $CC -c wasix-fchdir.c -o wasix-fchdir.o + $AR rcs libwasix-fchdir.a wasix-fchdir.o + export NIX_LDFLAGS="''${NIX_LDFLAGS-} -L$PWD -lwasix-fchdir" + # AC_CHECK_FUNCS' `char fchdir();` probe clashes with any real declaration, + # so the header only goes in for the build proper (below). + export ac_cv_func_fchdir=yes + ''; + preBuild = '' + export NIX_CFLAGS_COMPILE="''${NIX_CFLAGS_COMPILE-} -include $PWD/wasix-fchdir.h" + ''; + # The stub has to sit inside libarchive.a itself: a separate archive only + # satisfies libarchive's own link, leaving the symbol undefined for whoever + # links the static library later. + postInstall = '' + $AR r "''${lib-$out}/lib/libarchive.a" wasix-fchdir.o + ''; +} +prev.libarchive diff --git a/pkgs/overlay/packages/libgit2.nix b/pkgs/overlay/packages/libgit2.nix new file mode 100644 index 00000000..265d956c --- /dev/null +++ b/pkgs/overlay/packages/libgit2.nix @@ -0,0 +1,32 @@ +# libgit2 for wasix (nix-fetchers links it for builtins.fetchGit). +# libssh2 doesn't cross-build (inet_addr is absent from wasix-libc), and the +# ssh transport is unreachable from wasm anyway; HTTPS via openssl stays on. +{ + final, + prev, + helpers, + ... +}: let + dropSsh = builtins.filter (d: (d.pname or d.name or "") != "libssh2"); + # The git2 CLI links util/process.c, which needs fork; Wasm-EH hides fork + # (WASIX-TODO.md), so build the CLI only in the off profile, as upstream does. + offProfile = (final.stdenv.hostPlatform.wasmExceptions or "yes") == "no"; +in + helpers.libTweaks { + # appended after nixpkgs' flags; for duplicated -D options the last wins + cmakeFlags = [ + "-DUSE_SSH=OFF" + # `all` links the test binary, which needs mkfifo; cross can't run it anyway + "-DBUILD_TESTS=OFF" + # off only (see offProfile); nix links libgit2.a, never the CLI. + "-DBUILD_CLI=${ + if offProfile + then "ON" + else "OFF" + }" + ]; + # the static stdenv mirrors buildInputs into propagatedBuildInputs + buildInputs = dropSsh; + propagatedBuildInputs = dropSsh; + } + prev.libgit2 diff --git a/pkgs/overlay/packages/llhttp/package.nix b/pkgs/overlay/packages/llhttp/package.nix new file mode 100644 index 00000000..7b769a52 --- /dev/null +++ b/pkgs/overlay/packages/llhttp/package.nix @@ -0,0 +1,9 @@ +{ + prev, + helpers, + ... +}: +helpers.libTweaks { + patches = [./patches/wasi-is-not-the-js-wasm-build.patch]; +} +prev.llhttp diff --git a/pkgs/overlay/packages/llhttp/patches/wasi-is-not-the-js-wasm-build.patch b/pkgs/overlay/packages/llhttp/patches/wasi-is-not-the-js-wasm-build.patch new file mode 100644 index 00000000..aedd82c8 --- /dev/null +++ b/pkgs/overlay/packages/llhttp/patches/wasi-is-not-the-js-wasm-build.patch @@ -0,0 +1,30 @@ +Gate the JS-hosted wasm binding on "not WASI". + +The `__wasm__` block declares wasm_on_* imports supplied by llhttp's +JavaScript wrapper (undici's llhttp.wasm), and makes llhttp_alloc/llhttp_free +bind to them. A wasm32-wasi/wasix target is a standalone C target with no such +host, so every consumer that links libllhttp.a fails at link with undefined +wasm_on_* symbols. WASI builds want the ordinary C API. + +Upstream: report against nodejs/llhttp; the guard should exclude __wasi__. + +--- a/src/api.c ++++ b/src/api.c +@@ -39,7 +39,7 @@ + } + + +-#if defined(__wasm__) ++#if defined(__wasm__) && !defined(__wasi__) + + extern int wasm_on_message_begin(llhttp_t * p); + extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); +@@ -78,7 +78,7 @@ void llhttp_free(llhttp_t* parser) { + free(parser); + } + +-#endif // defined(__wasm__) ++#endif // defined(__wasm__) && !defined(__wasi__) + + /* Some getters required to get stuff from the parser */ + diff --git a/pkgs/overlay/packages/nix/package.nix b/pkgs/overlay/packages/nix/package.nix new file mode 100644 index 00000000..8aece459 --- /dev/null +++ b/pkgs/overlay/packages/nix/package.nix @@ -0,0 +1,92 @@ +# The nix evaluator for wasix: the `nix` CLI only, no build/sandbox support. +# +# nixpkgs builds nix as `nix-everything`, a merge of every component plus the +# manual, the C APIs and the test suites; we take its `nix-cli` passthru, which +# is the binary and the libraries it links (nix-util/store/fetchers/expr/ +# flake/main/cmd). Building derivations never happens here: seccomp sandboxing +# and the sandbox shell are already off for a non-Linux host, and the evaluator +# reaches the store only to read and to add paths. +# +# Deviations from a native build, all forced by the target: +# - GC off. Boehm GC finds roots by scanning the machine stack and registers, +# neither of which a wasm host exposes. libexpr's own switch leaks instead, +# which is what upstream already does on Windows and is fine for a +# short-lived evaluator. +# - Boost: see packages/boost.nix and the patches. +# - Markdown help off: lowdown doesn't cross-build. +{ + final, + prev, + helpers, + ... +}: let + # Meson's boost lookup ignores pkg-config and the BOOST_* env vars when + # cross-compiling; a machine-file property is the only channel. Merged after + # nixpkgs' own --cross-file. + boostRootFile = final.buildPackages.writeText "boost-root-cross-file.ini" '' + [properties] + boost_root = '${final.boost}' + ''; + + # overrideScope has to come before appendPatches: the other order dies in this + # spliced cross scope with "expected a set but found a function", though the + # same chain is fine in a plain nixpkgs cross set. + configured = prev.nix.overrideScope (_: prevScope: { + nix-expr = prevScope.nix-expr.override {enableGC = false;}; + nix-store = prevScope.nix-store.override { + withAWS = false; + # defaults on for a static host, but there is nothing to sandbox here and + # it wants a busybox built for the target + embeddedSandboxShell = false; + }; + # the repl keeps its default editline; readline's .pc wants a termcap we + # don't have + nix-cmd = prevScope.nix-cmd.override {enableMarkdown = false;}; + }); + + patched = configured.appendPatches [ + ./patches/thread-handoff-instead-of-boost-coroutines.patch + ./patches/no-boost-iostreams-mmap-on-wasi.patch + ./patches/boost-modules-available-on-wasi.patch + ./patches/portability-32-bit-libcxx.patch + ./patches/unsupported-posix-apis-on-wasi.patch + ./patches/no-prelink-on-wasi.patch + ]; + + components = patched.overrideAllMesonComponents (_: prevAttrs: { + mesonFlags = (prevAttrs.mesonFlags or []) ++ ["--cross-file=${boostRootFile}"]; + # Standing in for the prelink the patch above drops: every consumer of a + # nix library must take all of its members, or the translation units that + # only run a static registrar (primops, store implementations) are dropped + # and the feature silently disappears. The .pc files are how the components + # find each other, so the bracket goes there. + # postFixup, not postInstall: multiple-outputs moves the .pc files from + # $out to $dev during fixup. + postFixup = + (prevAttrs.postFixup or "") + + '' + for pc in "''${dev-$out}"/lib/pkgconfig/nix-*.pc; do + [ -e "$pc" ] || continue + sed -i -E 's|(-lnix[a-z0-9-]*)|-Wl,--whole-archive \1 -Wl,--no-whole-archive|g' "$pc" + done + ''; + }); +in + helpers.wasmRename {wasmName = "nix";} (helpers.libTweaks { + passthru.wasix.shipped = true; + # nixpkgs appends "+" for the patches we add, which is not semver. + # The patch count is a rebuild of the same upstream release, so it belongs + # in the rel, not the version. + passthru.wasmer.version = v: final.lib.head (final.lib.splitString "+" v); + # C++ exceptions rule out the no-EH profile; PIC is untested. + passthru.wasix.supportedProfiles = + builtins.filter (p: builtins.elem p helpers.profiles.withoutPic) helpers.profiles.withEh; + # Everything else installed here is a symlink to bin/nix, which would + # dangle once that is renamed for webc packaging: the nix-* compatibility + # commands (the old CLI) and libexec/nix/build-remote (a build feature). + postInstall = '' + rm -f "$out"/bin/nix-* + rm -rf "$out/libexec" + ''; + } + components.nix-cli) diff --git a/pkgs/overlay/packages/nix/patches/boost-modules-available-on-wasi.patch b/pkgs/overlay/packages/nix/patches/boost-modules-available-on-wasi.patch new file mode 100644 index 00000000..c665fcf0 --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/boost-modules-available-on-wasi.patch @@ -0,0 +1,29 @@ +Ask Meson only for Boost modules that exist on wasi. + +Meson resolves every listed module to a library file. On wasi only Boost.URL +is compiled (see pkgs/overlay/packages/boost.nix); Boost.Container is used +header-only here, and Boost.Context has no wasm32 build at all. + +--- a/src/libstore/meson.build 2026-07-22 21:35:15.763105114 +0200 ++++ b/src/libstore/meson.build 2026-07-22 21:37:16.935755244 +0200 +@@ -108,7 +108,7 @@ + + boost = dependency( + 'boost', +- modules : [ ++ modules : host_machine.system() == 'wasi' ? ['url'] : [ + 'container', + # Shouldn't list, because can header-only, and Meson currently looks for libs + #'regex', +--- a/src/libexpr/meson.build 2026-07-22 21:35:15.774105081 +0200 ++++ b/src/libexpr/meson.build 2026-07-22 21:37:23.162737264 +0200 +@@ -40,7 +40,8 @@ + + boost = dependency( + 'boost', +- modules : [ ++ # only header-only pieces are reached on wasi, where neither has a wasm32 build ++ modules : host_machine.system() == 'wasi' ? [] : [ + 'container', + 'context', + ], diff --git a/pkgs/overlay/packages/nix/patches/no-boost-iostreams-mmap-on-wasi.patch b/pkgs/overlay/packages/nix/patches/no-boost-iostreams-mmap-on-wasi.patch new file mode 100644 index 00000000..b62c43e0 --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/no-boost-iostreams-mmap-on-wasi.patch @@ -0,0 +1,41 @@ +Skip the Boost.Iostreams memory-mapped read path on wasi. + +readFile()'s fast path maps the file with boost::iostreams::mapped_file_source, +a compiled Boost library with no wasm32 build. The streaming fallback right +below it is already the general path. + +--- a/src/libutil/file-system.cc 2026-07-22 21:35:15.740105180 +0200 ++++ b/src/libutil/file-system.cc 2026-07-22 21:36:19.881919980 +0200 +@@ -21,8 +21,10 @@ + #include + #include + +-#include +-#include ++#ifndef __wasi__ ++# include ++# include ++#endif + + #ifdef __FreeBSD__ + # include +@@ -272,6 +274,9 @@ + void readFile(const std::filesystem::path & path, Sink & sink, bool memory_map) + { + // Memory-map the file for faster processing where possible. ++ // Boost.Iostreams has no wasm32 build (see the include above), so wasi ++ // always takes the streaming path. ++#ifndef __wasi__ + if (memory_map) { + try { + /* mapped_file_source can't be constructed from a std::filesystem::path. */ +@@ -284,6 +289,9 @@ + } + debug("memory-mapping failed for path: %s", PathFmt(path)); + } ++#else ++ (void) memory_map; ++#endif + + // Stream the file instead if memory-mapping fails or is disabled. + auto fd = openFileReadonly(std::filesystem::path(path)); diff --git a/pkgs/overlay/packages/nix/patches/no-prelink-on-wasi.patch b/pkgs/overlay/packages/nix/patches/no-prelink-on-wasi.patch new file mode 100644 index 00000000..c850190a --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/no-prelink-on-wasi.patch @@ -0,0 +1,252 @@ +Skip the prelink step on wasi. + +Meson prelinks each static library (a `wasm-ld -r` merge) so that C++ static +initializers survive archive-member selection at the final link. wasm-ld can't +do that here: the local-exec TLS relocation behind every `errno` reference has +no encoding against an undefined symbol, and libc's `errno` stays undefined +until the final link. + +The packaging compensates by whole-archiving these libraries in the consumers +(see pkgs/overlay/packages/nix/package.nix), which pulls in every member and so +keeps the same initializers. + +--- a/src/libcmd/meson.build 2026-07-22 22:22:27.176911160 +0200 ++++ b/src/libcmd/meson.build 2026-07-22 22:22:50.255996697 +0200 +@@ -110,7 +110,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) +--- a/src/libexpr-c/meson.build 2026-07-22 22:22:27.065911482 +0200 ++++ b/src/libexpr-c/meson.build 2026-07-22 22:22:50.254978897 +0200 +@@ -54,7 +54,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libexpr/meson.build 2026-07-22 21:35:15.774105081 +0200 ++++ b/src/libexpr/meson.build 2026-07-22 22:22:50.254366789 +0200 +@@ -240,7 +240,11 @@ + include_directories : include_dirs, + link_args : linker_export_flags, + link_whole : [ parser_library ], +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) +--- a/src/libexpr-test-support/meson.build 2026-07-22 22:22:27.030911584 +0200 ++++ b/src/libexpr-test-support/meson.build 2026-07-22 22:22:50.254603338 +0200 +@@ -50,7 +50,11 @@ + # TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326 + # is available. See also ../libutil/build.meson + link_args : linker_export_flags + [ '-lrapidcheck' ], +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libfetchers-c/meson.build 2026-07-22 22:22:27.041911553 +0200 ++++ b/src/libfetchers-c/meson.build 2026-07-22 22:22:50.254741126 +0200 +@@ -57,7 +57,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libfetchers/meson.build 2026-07-22 22:22:27.054911514 +0200 ++++ b/src/libfetchers/meson.build 2026-07-22 22:22:50.254862212 +0200 +@@ -64,7 +64,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) +--- a/src/libflake-c/meson.build 2026-07-22 22:22:27.111911349 +0200 ++++ b/src/libflake-c/meson.build 2026-07-22 22:22:50.255337972 +0200 +@@ -57,7 +57,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libflake/meson.build 2026-07-22 22:22:27.124911311 +0200 ++++ b/src/libflake/meson.build 2026-07-22 22:22:50.255475051 +0200 +@@ -62,7 +62,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libmain-c/meson.build 2026-07-22 22:22:27.137911274 +0200 ++++ b/src/libmain-c/meson.build 2026-07-22 22:22:50.255600263 +0200 +@@ -49,7 +49,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libmain/meson.build 2026-07-22 22:22:27.087911419 +0200 ++++ b/src/libmain/meson.build 2026-07-22 22:22:50.255099587 +0200 +@@ -81,7 +81,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libstore-c/meson.build 2026-07-22 22:22:27.152911230 +0200 ++++ b/src/libstore-c/meson.build 2026-07-22 22:22:50.255728871 +0200 +@@ -52,7 +52,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libstore/meson.build 2026-07-22 21:35:15.763105114 +0200 ++++ b/src/libstore/meson.build 2026-07-22 22:22:50.254114500 +0200 +@@ -381,7 +381,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) +--- a/src/libstore-test-support/meson.build 2026-07-22 22:22:27.211911058 +0200 ++++ b/src/libstore-test-support/meson.build 2026-07-22 22:22:50.256123598 +0200 +@@ -56,7 +56,11 @@ + # TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326 + # is available. See also ../libutil/build.meson + link_args : linker_export_flags + [ '-lrapidcheck' ], +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libutil-c/meson.build 2026-07-22 22:22:27.099911384 +0200 ++++ b/src/libutil-c/meson.build 2026-07-22 22:22:50.255221347 +0200 +@@ -57,7 +57,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + +--- a/src/libutil/meson.build 2026-07-22 21:35:15.751105148 +0200 ++++ b/src/libutil/meson.build 2026-07-22 22:22:50.253847595 +0200 +@@ -215,7 +215,11 @@ + dependencies : deps_public + deps_private + deps_other, + include_directories : include_dirs, + link_args : linker_export_flags, +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) +--- a/src/libutil-test-support/meson.build 2026-07-22 22:22:27.163911198 +0200 ++++ b/src/libutil-test-support/meson.build 2026-07-22 22:22:50.255869941 +0200 +@@ -47,7 +47,11 @@ + # TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326 + # is available. See also ../libutil/build.meson + link_args : linker_export_flags + [ '-lrapidcheck' ], +- prelink : true, # For C++ static initializers ++ # For C++ static initializers. wasm-ld can't do the relocatable link this ++ # needs: local-exec TLS relocations (every errno reference) have no ++ # encoding against an undefined symbol, and libc's errno is undefined ++ # until the final link. Consumers whole-archive these libraries instead. ++ prelink : host_machine.system() != 'wasi', + install : true, + ) + diff --git a/pkgs/overlay/packages/nix/patches/portability-32-bit-libcxx.patch b/pkgs/overlay/packages/nix/patches/portability-32-bit-libcxx.patch new file mode 100644 index 00000000..ee0f6006 --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/portability-32-bit-libcxx.patch @@ -0,0 +1,140 @@ +Portability fixes for a 32-bit target with libc++. + + - `std::string(sv.begin(), n)`, `std::string_view{it, n}` and passing + string_view iterators to only compile where string_view's iterator + is a raw `const char *`, as in libstdc++. + - `constexpr std::string` holds only for a literal short enough for the + small-string buffer. That is 11 bytes on a 32-bit target, so both of these + heap-allocate, and heap allocation is not a constant expression. + - `UploadData::sizeHint` is a file size: every caller passes a uint64_t and + curl takes a curl_off_t, so size_t only narrows (on a 32-bit target, it + fails the build outright). + +Upstream: report against NixOS/nix; none of these are wasi-specific. + +--- a/src/libutil/args.cc 2026-07-22 21:47:04.808057740 +0200 ++++ b/src/libutil/args.cc 2026-07-22 21:47:09.718043563 +0200 +@@ -77,7 +77,7 @@ + return {}; + auto i = s.find(completionMarker); + if (i != std::string::npos) +- return std::string(s.begin(), i); ++ return std::string(s.substr(0, i)); + return {}; + } + +--- a/src/libstore/names.cc 2026-07-22 22:29:31.706463927 +0200 ++++ b/src/libstore/names.cc 2026-07-22 22:29:31.759232621 +0200 +@@ -71,7 +71,9 @@ + while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) + p++; + +- return {s, size_t(p - s)}; ++ /* Iterator pair, not (pointer, length): string_view::const_iterator is ++ only a raw pointer in libstdc++. */ ++ return {s, p}; + } + + static bool componentsLT(const std::string_view c1, const std::string_view c2) +--- a/src/libstore/include/nix/store/binary-cache-store.hh 2026-07-22 22:27:13.060080362 +0200 ++++ b/src/libstore/include/nix/store/binary-cache-store.hh 2026-07-22 22:27:19.905060471 +0200 +@@ -92,9 +92,13 @@ + /** + * The prefix under which realisation infos will be stored + */ +- constexpr const static std::string realisationsPrefix = "realisations"; ++ /* Not `constexpr`: that only holds for a literal short enough for the ++ small-string buffer, which is 11 bytes on a 32-bit target -- both of ++ these heap-allocate there, and heap allocation is not a constant ++ expression. */ ++ inline const static std::string realisationsPrefix = "realisations"; + +- constexpr const static std::string cacheInfoFile = "nix-cache-info"; ++ inline const static std::string cacheInfoFile = "nix-cache-info"; + + BinaryCacheStore(Config &); + +--- a/src/libstore/include/nix/store/filetransfer.hh 2026-07-22 22:27:13.075080318 +0200 ++++ b/src/libstore/include/nix/store/filetransfer.hh 2026-07-22 22:27:29.752031858 +0200 +@@ -204,13 +204,15 @@ + { + } + +- UploadData(std::size_t sizeHint, RestartableSource & source) ++ UploadData(uint64_t sizeHint, RestartableSource & source) + : sizeHint(sizeHint) + , source(&source) + { + } + +- std::size_t sizeHint = 0; ++ /* uint64_t, not size_t: every caller passes one, and it reaches curl ++ as a curl_off_t. size_t narrows on a 32-bit target. */ ++ uint64_t sizeHint = 0; + RestartableSource * source = nullptr; + }; + +--- a/src/nix/diff-closures.cc 2026-07-22 22:44:19.908096723 +0200 ++++ b/src/nix/diff-closures.cc 2026-07-22 22:44:19.949220715 +0200 +@@ -39,7 +39,7 @@ + std::string_view const origName = path.name(); + std::string outputName; + +- if (std::regex_match(origName.begin(), origName.end(), match, regex)) { ++ if (std::regex_match(origName.data(), origName.data() + origName.size(), match, regex)) { + name = match[1]; + outputName = match[2]; + } +--- a/src/nix/nix-build/nix-build.cc 2026-07-22 22:50:26.493031854 +0200 ++++ b/src/nix/nix-build/nix-build.cc 2026-07-22 22:50:26.524042935 +0200 +@@ -42,7 +42,11 @@ + static std::vector shellwords(std::string_view s) + { + std::regex whitespace("^\\s+"); +- auto begin = s.cbegin(); ++ /* Raw pointers, not string_view iterators: std::cmatch is an iterator ++ range over `const char *`, and string_view::const_iterator is only that ++ in libstdc++. */ ++ const char * begin = s.data(); ++ const char * end = s.data() + s.size(); + std::vector res; + std::string cur; + +@@ -50,14 +54,14 @@ + + state st = sBegin; + auto it = begin; +- for (; it != s.cend(); ++it) { ++ for (; it != end; ++it) { + if (st == sBegin) { + std::cmatch match; +- if (regex_search(it, s.cend(), match, whitespace)) { ++ if (regex_search(it, end, match, whitespace)) { + cur.append(begin, it); + res.push_back(cur); + it = match[0].second; +- if (it == s.cend()) ++ if (it == end) + return res; + begin = it; + cur.clear(); +--- a/src/libexpr/primops.cc 2026-07-22 22:33:47.230934927 +0200 ++++ b/src/libexpr/primops.cc 2026-07-22 23:24:51.014770058 +0200 +@@ -4759,7 +4759,7 @@ + state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match"); + + std::cmatch match; +- if (!std::regex_match(str.begin(), str.end(), match, *regex)) { ++ if (!std::regex_match(str.data(), str.data() + str.size(), match, *regex)) { + v.mkNull(); + return; + } +@@ -4832,7 +4832,7 @@ + const auto str = + state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split"); + +- auto begin = std::cregex_iterator(str.begin(), str.end(), *regex); ++ auto begin = std::cregex_iterator(str.data(), str.data() + str.size(), *regex); + auto end = std::cregex_iterator(); + + // Any matches results are surrounded by non-matching results. diff --git a/pkgs/overlay/packages/nix/patches/thread-handoff-instead-of-boost-coroutines.patch b/pkgs/overlay/packages/nix/patches/thread-handoff-instead-of-boost-coroutines.patch new file mode 100644 index 00000000..f4972228 --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/thread-handoff-instead-of-boost-coroutines.patch @@ -0,0 +1,271 @@ +Replace boost::coroutines2 with a worker-thread handoff on wasi. + +Boost.Context, which Boost.Coroutine2 is built on, has no wasm32 backend: +fcontext needs per-arch assembly, and ucontext/WinFiber need APIs wasi does +not have. sinkToSource/sourceToSink only need a strict ping-pong between two +stacks, which a thread pair provides: exactly one side runs at a time, so a +string_view handed across stays valid while the other side is parked. + +Upstream: worth offering as the portable fallback for any target without a +Boost.Context backend. + +--- a/src/libutil/serialise.cc 2026-07-22 21:35:15.730105209 +0200 ++++ b/src/libutil/serialise.cc 2026-07-22 21:36:02.663969694 +0200 +@@ -9,8 +9,15 @@ + #include + #include + +-#include +-#include ++#ifdef __wasi__ ++# include ++# include ++# include ++# include ++#else ++# include ++# include ++#endif + + #ifdef _WIN32 + # include +@@ -305,6 +312,206 @@ + { + } + ++#ifdef __wasi__ ++ ++/* Boost.Context, which Boost.Coroutine2 is built on, has no wasm32 backend: ++ fcontext needs per-arch assembly, ucontext and WinFiber need APIs wasi ++ lacks. Stand in for it with a worker thread and a strict ping-pong handoff, ++ so exactly one side runs at a time and a std::string_view handed across ++ stays valid while the other side is parked. */ ++namespace { ++ ++struct Handoff ++{ ++ /* Thrown into a parked worker whose consumer has gone away, to unwind it ++ the way destroying a suspended coroutine would. */ ++ struct Abandoned ++ {}; ++ ++ std::mutex mutex; ++ std::condition_variable cv; ++ bool workerTurn = false; ++ bool workerDone = false; ++ bool abandoned = false; ++ std::exception_ptr ex; ++ std::thread thread; ++ ++ ~Handoff() ++ { ++ if (!thread.joinable()) ++ return; ++ { ++ std::lock_guard lock(mutex); ++ abandoned = true; ++ } ++ resume(); ++ thread.join(); ++ } ++ ++ void start(std::function body) ++ { ++ thread = std::thread([this, body{std::move(body)}]() { ++ bool cancelled; ++ { ++ std::unique_lock lock(mutex); ++ cv.wait(lock, [&] { return workerTurn; }); ++ cancelled = abandoned; ++ } ++ try { ++ if (!cancelled) ++ body(); ++ } catch (Abandoned &) { ++ } catch (...) { ++ std::lock_guard lock(mutex); ++ ex = std::current_exception(); ++ } ++ std::lock_guard lock(mutex); ++ workerDone = true; ++ workerTurn = false; ++ cv.notify_all(); ++ }); ++ } ++ ++ /* Consumer side: run the worker until it parks itself or returns. */ ++ void resume() ++ { ++ std::unique_lock lock(mutex); ++ if (workerDone) ++ return; ++ workerTurn = true; ++ cv.notify_all(); ++ cv.wait(lock, [&] { return !workerTurn; }); ++ if (ex && !abandoned) ++ std::rethrow_exception(std::exchange(ex, {})); ++ } ++ ++ /* Worker side: hand control back and park until resumed. */ ++ void yield() ++ { ++ std::unique_lock lock(mutex); ++ workerTurn = false; ++ cv.notify_all(); ++ cv.wait(lock, [&] { return workerTurn; }); ++ if (abandoned) ++ throw Abandoned{}; ++ } ++ ++ bool done() ++ { ++ std::lock_guard lock(mutex); ++ return workerDone; ++ } ++}; ++ ++} // namespace ++ ++std::unique_ptr sourceToSink(fun reader) ++{ ++ struct SourceToSink : FinishSink ++ { ++ fun reader; ++ std::string_view cur; ++ bool atEof = false; ++ bool started = false; ++ /* Last member: its destructor must join the worker while everything ++ the worker touches is still alive. */ ++ Handoff handoff; ++ ++ SourceToSink(fun reader) ++ : reader(reader) ++ { ++ } ++ ++ void operator()(std::string_view in) override ++ { ++ if (in.empty()) ++ return; ++ cur = in; ++ ++ if (!started) { ++ started = true; ++ handoff.start([&]() { ++ LambdaSource source([&](char * out, size_t out_len) { ++ while (cur.empty()) { ++ handoff.yield(); ++ if (atEof) ++ throw EndOfFile("coroutine has finished"); ++ } ++ size_t n = cur.copy(out, out_len); ++ cur.remove_prefix(n); ++ return n; ++ }); ++ reader(source); ++ }); ++ } ++ ++ while (!cur.empty() && !handoff.done()) ++ handoff.resume(); ++ } ++ ++ void finish() override ++ { ++ if (!started) ++ return; ++ atEof = true; ++ handoff.resume(); ++ } ++ }; ++ ++ return std::make_unique(reader); ++} ++ ++std::unique_ptr sinkToSource(fun writer, fun eof) ++{ ++ struct SinkToSource : Source ++ { ++ fun writer; ++ fun eof; ++ std::string_view cur; ++ bool started = false; ++ /* See SourceToSink. */ ++ Handoff handoff; ++ ++ SinkToSource(fun writer, fun eof) ++ : writer(writer) ++ , eof(eof) ++ { ++ } ++ ++ size_t read(char * data, size_t len) override ++ { ++ if (!started) { ++ started = true; ++ handoff.start([&]() { ++ LambdaSink sink([&](std::string_view data) { ++ if (!data.empty()) { ++ cur = data; ++ handoff.yield(); ++ } ++ }); ++ writer(sink); ++ }); ++ } ++ ++ while (cur.empty()) { ++ if (handoff.done()) { ++ eof(); ++ unreachable(); ++ } ++ handoff.resume(); ++ } ++ ++ size_t n = cur.copy(data, len); ++ cur.remove_prefix(n); ++ return n; ++ } ++ }; ++ ++ return std::make_unique(writer, eof); ++} ++ ++#else ++ + /* 512KiB is a conservative estimate for deeply nested NARs, which are limited + to 64 levels. We also tend to allocate rather large buffers on the stack, so + we should leave plenty of headroom. Note that no evaluation is supposed to +@@ -439,6 +646,8 @@ + return std::make_unique(writer, eof); + } + ++#endif ++ + void writePadding(size_t len, Sink & sink) + { + if (len % 8) { +--- a/src/libutil/meson.build 2026-07-22 21:35:15.751105148 +0200 ++++ b/src/libutil/meson.build 2026-07-22 21:37:10.750773102 +0200 +@@ -62,14 +62,17 @@ + ) + deps_private += blake3 + +-boost = dependency( +- 'boost', +- modules : [ ++# Boost.Context (which Boost.Coroutine2 rides on) and Boost.Iostreams have no ++# wasm32 build; serialise.cc and file-system.cc take wasi paths that avoid them. ++boost_modules = host_machine.system() == 'wasi' ? ['url'] : [ + 'context', + 'coroutine', + 'iostreams', + 'url', +- ], ++ ] ++boost = dependency( ++ 'boost', ++ modules : boost_modules, + include_type : 'system', + version : '>=1.87.0', + ) diff --git a/pkgs/overlay/packages/nix/patches/unsupported-posix-apis-on-wasi.patch b/pkgs/overlay/packages/nix/patches/unsupported-posix-apis-on-wasi.patch new file mode 100644 index 00000000..2e4b3223 --- /dev/null +++ b/pkgs/overlay/packages/nix/patches/unsupported-posix-apis-on-wasi.patch @@ -0,0 +1,198 @@ +Report the POSIX APIs wasi does not have as unimplemented. + + - pseudoterminals: wasi has none and wasix-libc has no ptsname_r/unlockpt. + Only the derivation builder opens one, and that never runs here. + - fork: wasix has it only in its asyncify build, which the wasm + exception-handling ABI this target uses rules out. Failing in doFork turns + every spawn into the existing "unable to fork" error. + - sync(): wasi can only flush a file descriptor it holds. + - flock() and POSIX record locks: neither exists, and nothing here shares a + store with another process, so every lock is reported as taken. + - getifaddrs: declared by wasix-libc but defined under another name, so it + does not link; assume we are online, as on Windows. + - dlopen: no dlfcn.h, so builtins.importNative, builtins.exec and plugin + loading drop out the same way they do on Windows. The first two are gated + on the native-code setting; loading a plugin now reports the same error + Windows gives. Boost.Stacktrace has no backend either, so the crash handler + prints an empty trace. + +--- a/src/libutil/terminal.cc 2026-07-22 21:47:04.812057728 +0200 ++++ b/src/libutil/terminal.cc 2026-07-22 21:47:30.892982423 +0200 +@@ -194,7 +194,12 @@ + #ifndef _WIN32 + std::string getPtsName(int fd) + { +-# ifdef __APPLE__ ++# ifdef __wasi__ ++ // wasi has no pseudoterminals at all. Only the derivation builder calls ++ // this, and that never runs here. ++ (void) fd; ++ throw UnimplementedError("pseudoterminals are not available on this platform"); ++# elif defined(__APPLE__) + static std::mutex ptsnameMutex; + // macOS doesn't have ptsname_r, use mutex-protected ptsname + std::lock_guard lock(ptsnameMutex); +--- a/src/libutil/unix/processes.cc 2026-07-22 21:50:29.028465871 +0200 ++++ b/src/libutil/unix/processes.cc 2026-07-22 21:52:14.191160864 +0200 +@@ -190,15 +190,25 @@ + + static pid_t doFork(bool allowVfork, ChildWrapperFunction & fun) + { +-#ifdef __linux__ +- pid_t pid = allowVfork ? vfork() : fork(); ++#ifdef __wasi__ ++ /* wasix exposes fork() only in its asyncify build, which the wasm ++ exception-handling ABI used here rules out. Nothing on the eval path ++ spawns a process; the caller reports this as "unable to fork". */ ++ (void) allowVfork; ++ (void) fun; ++ errno = ENOSYS; ++ return -1; + #else ++# ifdef __linux__ ++ pid_t pid = allowVfork ? vfork() : fork(); ++# else + pid_t pid = fork(); +-#endif ++# endif + if (pid != 0) + return pid; + fun(); + unreachable(); ++#endif + } + + #ifdef __linux__ +--- a/src/libstore/local-store.cc 2026-07-22 22:29:31.724984821 +0200 ++++ b/src/libstore/local-store.cc 2026-07-22 22:29:31.759912297 +0200 +@@ -895,7 +895,8 @@ + + void LocalStore::registerValidPaths(const ValidPathInfos & infos) + { +-#ifndef _WIN32 ++/* wasi has no sync(): it can only flush a file descriptor it holds. */ ++#if !defined(_WIN32) && !defined(__wasi__) + /* SQLite will fsync by default, but the new valid paths may not + be fsync-ed. So some may want to fsync them before registering + the validity, at the expense of some speed of the path +--- a/src/libstore/unix/build/derivation-builder.cc 2026-07-22 22:31:26.001345319 +0200 ++++ b/src/libstore/unix/build/derivation-builder.cc 2026-07-22 22:58:04.495928654 +0200 +@@ -849,6 +849,10 @@ + miscMethods->openLogFile(); + + /* Create a pseudoterminal to get the output of the builder. */ ++#ifdef __wasi__ ++ /* wasi has no pseudoterminals, and no fork to run a builder in either. */ ++ throw UnimplementedError("building derivations is not supported on this platform"); ++#else + builderOut = posix_openpt(O_RDWR | O_NOCTTY); + if (!builderOut) + throw SysError("opening pseudoterminal master"); +@@ -870,6 +874,7 @@ + + if (unlockpt(builderOut.get())) + throw SysError("unlocking pseudoterminal"); ++#endif + + buildResult.startTime = time(nullptr); + +--- a/src/libmain/plugin.cc 2026-07-22 22:38:57.299033871 +0200 ++++ b/src/libmain/plugin.cc 2026-07-22 22:39:11.766689016 +0200 +@@ -1,4 +1,4 @@ +-#ifndef _WIN32 ++#if !defined(_WIN32) && !defined(__wasi__) + # include + #endif + +@@ -92,7 +92,7 @@ + checkInterrupt(); + /* handle is purposefully leaked as there may be state in the + DSO needed by the action of the plugin. */ +-#ifndef _WIN32 // TODO implement via DLL loading on Windows ++#if !defined(_WIN32) && !defined(__wasi__) // TODO implement via DLL loading on Windows + void * handle = dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); + if (!handle) + throw Error("could not dynamically open plugin file %s: %s", PathFmt(file), dlerror()); +--- a/src/nix/crash-handler.cc 2026-07-22 22:44:19.923096679 +0200 ++++ b/src/nix/crash-handler.cc 2026-07-22 22:44:19.949443345 +0200 +@@ -12,6 +12,12 @@ + # define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED + #endif + ++#ifdef __wasi__ ++// Every Boost.Stacktrace backend needs either dlfcn.h or a platform unwinder; ++// wasi has neither, so traces come out empty. ++# define BOOST_STACKTRACE_USE_NOOP ++#endif ++ + #include + + #ifndef _WIN32 +--- a/src/nix/main.cc 2026-07-22 22:58:04.454701334 +0200 ++++ b/src/nix/main.cc 2026-07-22 23:21:59.807206155 +0200 +@@ -55,7 +55,9 @@ + /* Check if we have a non-loopback/link-local network interface. */ + static bool haveInternet() + { +-#ifndef _WIN32 ++/* wasix-libc declares getifaddrs but only defines getif_addrs (WASIX-TODO.md), ++ so this cannot link. Assume we are online, as on Windows. */ ++#if !defined(_WIN32) && !defined(__wasi__) + struct ifaddrs * addrs; + + if (getifaddrs(&addrs)) +--- a/src/libstore/unix/pathlocks.cc 2026-07-22 22:58:04.467701296 +0200 ++++ b/src/libstore/unix/pathlocks.cc 2026-07-22 22:58:04.495142392 +0200 +@@ -39,6 +39,14 @@ + + bool lockFile(Descriptor desc, LockType lockType, bool wait) + { ++#ifdef __wasi__ ++ /* wasi has neither flock() nor POSIX record locks. Nothing here shares a ++ store with another process, so report every lock as taken. */ ++ (void) desc; ++ (void) lockType; ++ (void) wait; ++ return true; ++#else + int type; + if (lockType == ltRead) + type = LOCK_SH; +@@ -68,6 +76,7 @@ + } + + return true; ++#endif + } + + bool PathLocks::lockPaths(const std::set & paths, const std::string & waitMsg, bool wait) +--- a/src/libexpr/primops.cc 2026-07-22 23:24:51.014770058 +0200 ++++ b/src/libexpr/primops.cc 2026-07-22 22:36:05.525138829 +0200 +@@ -33,7 +33,8 @@ + #include + #include + +-#ifndef _WIN32 ++/* wasi has no dlfcn.h; see the importNative/exec primops below. */ ++#if !defined(_WIN32) && !defined(__wasi__) + # include + #endif + +@@ -435,7 +436,7 @@ + import(state, pos, *args[0], nullptr, v); + }}); + +-#ifndef _WIN32 // TODO implement via DLL loading on Windows ++#if !defined(_WIN32) && !defined(__wasi__) // TODO implement via DLL loading on Windows + + /* Want reasonable symbol names, so extern C */ + /* !!! Should we pass the Pos or the file name too? */ +@@ -5350,7 +5351,7 @@ + )", + }); + +-#ifndef _WIN32 // TODO implement on Windows ++#if !defined(_WIN32) && !defined(__wasi__) // TODO implement on Windows + // Miscellaneous + if (settings.enableNativeCode) { + addPrimOp({ diff --git a/pkgs/overlay/packages/nix/tests/eval.nix b/pkgs/overlay/packages/nix/tests/eval.nix new file mode 100644 index 00000000..b6b0f8ba --- /dev/null +++ b/pkgs/overlay/packages/nix/tests/eval.nix @@ -0,0 +1,69 @@ +# The evaluator under wasmer, checked against the native nix of the same +# version. `--store dummy://` keeps the store in memory, so nothing here needs +# a store directory and nothing builds. +{ + pkgs, + wasmerPkgs, + testLib, + ... +}: let + wasix = [wasmerPkgs.nix]; + + # Two differences that aren't about the evaluator: + # - under wasmer isatty(1) is true even when stdout is a file, so nix's + # logger draws its progress line with CSI escapes (WASIX-TODO.md); + # - the native run is sandboxed and reports itself offline, while the wasi + # build has no getifaddrs and always assumes it is online. + normalizeLog = pkgs.writeShellScript "normalize-nix-log" '' + ${pkgs.gnused}/bin/sed -e 's/\r//g' -e 's/\x1b\[[0-9;]*[A-Za-z]//g' \ + | ${pkgs.gnugrep}/bin/grep -v "you don't have Internet access" || true + ''; + + # nix-command is experimental in a release build, and the store has to be one + # that needs no filesystem. + cmp = name: expr: + testLib.mkScriptComparison { + name = "nix-${name}"; + nativePkgs = [pkgs.nix]; + wasixPkgs = wasix; + normalize = normalizeLog; + script = '' + nix --extra-experimental-features nix-command --store dummy:// \ + eval --expr ${pkgs.lib.escapeShellArg expr} + ''; + }; +in { + version = testLib.mkWasixRun { + name = "nix-version"; + wasixPkgs = wasix; + script = "nix --version"; + }; + + arithmetic = cmp "arithmetic" "builtins.foldl' (a: b: a + b * 2) 0 [1 2 3 4 5]"; + recursion = cmp "recursion" "let fib = n: if n < 2 then n else fib (n - 1) + fib (n - 2); in map fib [10 15 20]"; + strings = cmp "strings" ''let s = "a-b-c"; in { j = builtins.concatStringsSep "/" (builtins.filter builtins.isString (builtins.split "-" s)); u = builtins.substring 2 3 s; }''; + # the calls that only compile untouched against libstdc++ + regex = cmp "regex" ''builtins.match "([a-z]+)-([0-9.]+)" "hello-1.2.3"''; + json = cmp "json" ''builtins.fromJSON "{\"a\":[1,2,{\"b\":true}]}"''; + # toml11 + toml = cmp "toml" ''builtins.fromTOML "x = 1\ny = [1, 2]"''; + # openssl + libblake3 + hashes = cmp "hashes" ''builtins.mapAttrs (a: _: builtins.hashString a "hello") { md5 = 1; sha1 = 1; sha256 = 1; sha512 = 1; }''; + + # Store writes run the NAR serializer through sinkToSource, which is a + # worker-thread handoff here rather than a boost coroutine; a matching store + # path means the bytes it produced are identical. + store-path = testLib.mkScriptComparison { + name = "nix-store-path"; + nativePkgs = [pkgs.nix]; + wasixPkgs = wasix; + normalize = normalizeLog; + script = '' + mkdir -p tree/sub + echo hello > tree/a.txt + echo world > tree/sub/b.txt + nix --extra-experimental-features nix-command --store 'dummy://?read-only=false' \ + eval --impure --expr "builtins.path { path = ./tree; name = \"tree\"; }" + ''; + }; +} diff --git a/pkgs/overlay/trivial.nix b/pkgs/overlay/trivial.nix index 35308d56..9b509590 100644 --- a/pkgs/overlay/trivial.nix +++ b/pkgs/overlay/trivial.nix @@ -4,21 +4,25 @@ [ "brotli" "bzip2" + "editline" # nix repl "expat" "gmp" "jansson" "lcms2" # pillow's ImageCms "libb2" "libdeflate" + "libblake3" # nix "libpng" "libsodium" "libyaml" # pyyaml C ext (langchain/litellm/smolagents pull pyyaml) "lz4" "lzo" "mpfr" + "nlohmann_json" # nix "oniguruma" "openjpeg" "popt" # rsync "tinyxml-2" + "toml11" # nix "xz" ] diff --git a/pkgs/toolchain/wasixcc-relocatable-link-passthrough.patch b/pkgs/toolchain/wasixcc-relocatable-link-passthrough.patch new file mode 100644 index 00000000..e12448bd --- /dev/null +++ b/pkgs/toolchain/wasixcc-relocatable-link-passthrough.patch @@ -0,0 +1,166 @@ +diff --git a/src/compiler.rs b/src/compiler.rs +index faef5bb..829c47f 100644 +--- a/src/compiler.rs ++++ b/src/compiler.rs +@@ -96,6 +96,10 @@ pub(crate) struct BuildSettings { + opt_level: OptLevel, + debug_level: DebugLevel, + use_wasm_opt: bool, ++ // `-r`: merge the inputs into one relocatable object instead of building a ++ // module. Set from the flags, not from the raw argv, so it survives a ++ // response file (nixpkgs' cc-wrapper passes everything in one). ++ relocatable: bool, + } + + /// A single user-supplied token destined for the link stage. Flags (`-Wl`, +@@ -180,9 +184,18 @@ pub(crate) fn run(args: Vec, mut user_settings: UserSettings, run_cxx: b + tracing::debug!("Build settings: {build_settings:?}"); + tracing::debug!("Compiler/linker args: {args:?}"); + +- if args.compiler_inputs.is_empty() && !args.has_linker_inputs() { ++ let relocatable = build_settings.relocatable; ++ ++ if relocatable || (args.compiler_inputs.is_empty() && !args.has_linker_inputs()) { + // If there are no inputs, just pass everything through to clang. + // This lets us support invocations such as `wasixcc -dumpmachine`. ++ // ++ // A relocatable link (`-r`) goes the same way: it merges objects into ++ // one object, so none of the module setup below applies -- no crt, no ++ // sysroot libs, no entry point, no wasm-opt. Driving it through the ++ // module-kind machinery instead links it as an executable and fails on ++ // the undefined `main` that libc's __main_void.o pulls in. Meson emits ++ // this for `prelink : true` static libraries. + let mut command = Command::new(user_settings.llvm_location.get_tool_path(if run_cxx { + "clang++" + } else { +@@ -196,6 +209,12 @@ pub(crate) fn run(args: Vec, mut user_settings: UserSettings, run_cxx: b + fuse_ld.push(user_settings.llvm_location.get_tool_path("wasm-ld")); + command.arg(fuse_ld); + } ++ if relocatable { ++ // clang's wasm driver doesn't infer -nostdlib from -r, so it still ++ // adds crt1.o and the default libs and the merge fails on the ++ // undefined `main` they bring in. ++ command.arg("-nostdlib"); ++ } + return run_command(command); + } + +@@ -266,6 +285,8 @@ pub(crate) fn link_only(args: Vec, mut user_settings: UserSettings) -> R + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: user_settings.run_wasm_opt.unwrap_or(true), ++ // wasixld never merges; `-r` there is a plain wasm-ld passthrough. ++ relocatable: false, + }; + + let state = State { +@@ -902,6 +923,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -929,6 +951,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -956,6 +979,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -984,6 +1008,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1011,6 +1036,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1039,6 +1065,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1067,6 +1094,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1098,6 +1126,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1159,6 +1188,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +@@ -1253,6 +1283,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: false, ++ relocatable: false, + }, + args: PreparedArgs { + compiler_args: Vec::new(), +diff --git a/src/compiler/flags.rs b/src/compiler/flags.rs +index 650f095..37d1a10 100644 +--- a/src/compiler/flags.rs ++++ b/src/compiler/flags.rs +@@ -197,6 +197,7 @@ fn update_build_settings_from_compiler_flag( + Flag::Simple("-c" | "-S" | "-E") => { + user_settings.module_kind = Some(ModuleKind::ObjectFile); + } ++ Flag::Simple("-r" | "--relocatable") => build_settings.relocatable = true, + Flag::Simple("-shared") if user_settings.module_kind.is_none() => { + user_settings.module_kind = Some(ModuleKind::SharedLibrary); + } +@@ -460,6 +461,7 @@ pub(super) fn prepare_compiler_args( + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }; + + let args = DEFAULT_WASM_CLANG_FLAGS +@@ -562,6 +564,7 @@ mod tests { + opt_level: OptLevel::O0, + debug_level: DebugLevel::G0, + use_wasm_opt: true, ++ relocatable: false, + }; + let mut us = UserSettings::default(); + update_build_settings_from_compiler_flag(Flag::Simple("-O3"), &mut bs, &mut us); + diff --git a/pkgs/toolchain/wasixcc.nix b/pkgs/toolchain/wasixcc.nix index 89193fd2..f67b77a5 100644 --- a/pkgs/toolchain/wasixcc.nix +++ b/pkgs/toolchain/wasixcc.nix @@ -42,6 +42,13 @@ patches = [ ./wasixcc-map-libstdcxx-to-libcxx.patch ./wasixcc-openmp-link.patch + # A relocatable link (-r) merges objects into one object; the module-kind + # machinery linked it as an executable instead and died on the undefined + # `main` in libc's __main_void.o. Meson emits this for `prelink : true` + # static libraries, where nix surfaced it. nix now disables prelink on + # wasi (no-prelink-on-wasi.patch) and no longer relies on this; kept as + # general driver correctness. TODO: upstream, then drop. + ./wasixcc-relocatable-link-passthrough.patch ]; doCheck = true;