Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion WASIX-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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 🟡
Expand Down
45 changes: 45 additions & 0 deletions pkgs/overlay/packages/boost.nix
Original file line number Diff line number Diff line change
@@ -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 {};
}
43 changes: 43 additions & 0 deletions pkgs/overlay/packages/libarchive.nix
Original file line number Diff line number Diff line change
@@ -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 <errno.h>
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
32 changes: 32 additions & 0 deletions pkgs/overlay/packages/libgit2.nix
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions pkgs/overlay/packages/llhttp/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
prev,
helpers,
...
}:
helpers.libTweaks {
patches = [./patches/wasi-is-not-the-js-wasm-build.patch];
}
prev.llhttp
Original file line number Diff line number Diff line change
@@ -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 */

92 changes: 92 additions & 0 deletions pkgs/overlay/packages/nix/package.nix
Original file line number Diff line number Diff line change
@@ -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 "+<n>" 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)
Original file line number Diff line number Diff line change
@@ -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',
],
Original file line number Diff line number Diff line change
@@ -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 <sys/time.h>
#include <unistd.h>

-#include <boost/iostreams/device/mapped_file.hpp>
-#include <boost/filesystem/path.hpp>
+#ifndef __wasi__
+# include <boost/iostreams/device/mapped_file.hpp>
+# include <boost/filesystem/path.hpp>
+#endif

#ifdef __FreeBSD__
# include <sys/param.h>
@@ -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));
Loading
Loading