everything is wasm#1524
Merged
Merged
Conversation
e929c9e to
76259aa
Compare
14023ea to
afd454b
Compare
5f30b85 to
6f558a8
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the Nix/CI build system to better support cross targets (notably musl + aarch64) and adds an explicit wasm32-wasip1 “check” path, along with related dependency/linking fixes (FRR/libatomic, libgcc/libunwind handling, and a couple of upstream build workarounds).
Changes:
- Add wasm32-wasip1 “check” support (including package allow/deny metadata in Cargo workspace metadata), plus a CI job to exercise it.
- Improve cross-compilation ergonomics for aarch64/musl (new generic aarch64 platform, build-vs-host tool selection, libc-aware FRR linking, container tarball handling).
- Update Nix pins and apply small upstream workarounds for musl/C23 builds in external C components.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
npins/sources.json |
Updates pinned revisions/hashes (FRR, nixpkgs, rust-overlay). |
nix/platforms.nix |
Adds a generic aarch64 platform and name mapping used by cross builds. |
nix/pkgs/frr/default.nix |
Adjusts FRR inputs/flags for musl vs glibc and disables grpc/protobuf via configure flags. |
nix/pkgs/dplane-rpc/default.nix |
Adds a postPatch workaround to fix missing <string.h> include under musl/C23. |
nix/pkgs/dplane-plugin/default.nix |
Adds a postPatch workaround for musl in6_addr layout and adjusts cmake source dir usage. |
nix/overlays/frr.nix |
Makes FRR overlay libc-aware for -latomic selection and improves cross build CC/BUILD_CC handling. |
justfile |
Adds build-each, check, check-each recipes (note: check currently doesn’t narrow by package). |
default.nix |
Wires libc through overlays, adds wasm package selection logic, adds check derivations, and refines cross/container handling. |
Cargo.toml |
Reworks workspace metadata into per-crate tables with miri/wasm flags used for wasm/miri gating. |
.github/workflows/dev.yml |
Adds cross_wasm and cross_matrix CI jobs and aggregates their results in the summary job. |
.cargo/config.toml |
Adds a wasm32-wasip1 runner configuration using wasmtime. |
3f327fe to
732ca87
Compare
9b40ef3 to
22cb9ea
Compare
libcap's Makefile compiles a helper binary `_makenames` and *executes*
it during the build to generate `cap_names.h`. Make.Rules defaults
`BUILD_CC ?= $(CC)`, so without an explicit BUILD_CC the helper gets
built with whatever CC is, which under cross stdenv is the wrapped
cross-clang. For same-arch cross (x86_64 -> x86_64-musl) the produced
binary happens to run on the build host; for cross-arch it does not:
./_makenames > cap_names.h
/bin/bash: ./_makenames: cannot execute binary file: Exec format error
make[1]: *** [Makefile:86: cap_names.h] Error 126
`pkgsBuildBuild.llvmPackages'.clang` is the clang that runs on, and
targets, the build host -- exactly what BUILD_CC should be regardless
of the target architecture. Pinning the absolute path avoids the
ambiguity around which `clang` PATH resolves to during a libcap build.
Note: `pkgsBuildHost` would be the cross compiler (runs on build,
targets host = aarch64), which is the wrong splice for a build-host
helper; this is the same trap that fooled the dev-shell tooling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`clib/bin/cpmock.c` calls memset/strcpy/strerror/memcmp without an
`#include <string.h>`. Under glibc the standard headers transitively
re-expose those declarations (via <sys/socket.h>, <stdlib.h>, etc.) so
the compile happens to succeed. Under musl the headers are strict and
disjoint, so the declarations are missing. Combined with the
`-DCMAKE_C_STANDARD=23` cmake flag (which makes clang treat
`-Wimplicit-function-declaration` as an error per C23), the build dies:
clib/bin/cpmock.c:33:5: error: call to undeclared library function
'memset' with type 'void *(void *, int, __size_t)'; ISO C99 and
later do not support implicit function declarations
... and similar for strcpy, strerror, memcmp.
This is a real correctness bug in the upstream source (implicit function
declarations have been UB since C99 and outright illegal since C23) -
glibc just covers for it. Workaround here patches the include in
locally so the FRR/dataplane container can be built against musl.
TODO: remove once fixed upstream in githedgehog/dplane-rpc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`src/hh_dp_msg.c` accesses `struct in6_addr` through glibc's internal
anonymous union member name `.__in6_u.__u6_addr8` (three callsites:
lines 74, 131, 179). That name is a glibc implementation detail, not
part of any standard. POSIX defines `struct in6_addr` to expose the
bytes via the `.s6_addr` member directly, and musl does only that --
no wrapping anonymous union -- so the access fails to compile against
musl headers:
src/hh_dp_msg.c:74:57: error: no member named '__in6_u' in 'struct in6_addr'
memcpy(ifa.address.addr.ipv6, ifaddr->u.prefix6.__in6_u.__u6_addr8, 16);
The three sites are all doing the same thing -- copying 16 bytes out of
the address -- and `.s6_addr` is the portable spelling that works on
both glibcs and musl.
To make the postPatch actually take effect, also remove `dontUnpack = true`
so nix copies the source to a writable build directory, and change the
cmake `-S "$src"` reference to `-S .` so cmake consumes the patched copy
rather than the (read-only) original store path.
TODO: remove once fixed upstream in githedgehog/dplane-plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Restructure `[workspace.metadata.package]` from a single inline-table
section to per-package `[workspace.metadata.package.<name>]` subsections.
Each entry now carries:
- `package`: the cargo package name (e.g. `dataplane-args`).
- `miri`: whether the crate is meaningful to run under miri.
- `wasm`: whether the crate is meaningful to compile for wasm.
Each `miri = false` / `wasm = false` line is followed by a short tag
(`hopeless + pointless`, `split`, `miss`) and a header comment block
explains what those tags mean:
- hopeless + pointless: crate is fundamentally tied to hardware or OS
capabilities that don't exist in the target environment.
- split: crate is partly usable but contains hardware/OS interactions
that would have to be conditionally compiled out.
- miss: crate is logically fine but currently happens to contain
something that needs to be factored out before it works.
This is structural prep work: it expands the metadata coverage from
only miri-exclusions to every workspace member, and adds the `wasm`
dimension that the next commit consumes from `default.nix`.
The existing miri.just jq query
(`to_entries[].value | select(.miri == false).package`) is unchanged
by this restructure: dotted-subtables and inline-subtables present
identically when iterated via to_entries. All previously-`miri = false`
crates retain that flag with the same value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
When `platform == "wasm32-wasip1"`, generate the workspace `package-list` by reading `[workspace.metadata.package.<name>].wasm` and excluding any member whose `wasm` flag is `false`. For all other platforms the original behavior is preserved: include every workspace member, look each one up via its own `Cargo.toml` package name. This is what makes the `wasm` flag added in the previous commit load-bearing: the wasm package-list shrinks to crates that can plausibly compile for wasm32-wasip1, and the per-package `workspace.dataplane.<name>` derivations (and `workspace`-level attribute names that crane uses for caching) follow suit. No behavior change for libc=gnu / libc=musl / non-wasm platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adds a `must-build` CI job that exercises the platforms and libcs the project intends to ship for, without yet routing them through `check` / `build` (those jobs assume gnu/x86-64-v3 today). The matrix: - wasm32-wasip1 / unknown / release (build-each) - zen5 / musl / release (build-container dataplane) - bluefield3 / gnu / debug (build-container dataplane) - bluefield3 / musl / debug (build-container dataplane) `build-each` is added to the justfile as a thin wrapper that maps to the `workspace` nix target (build every workspace member to its own output), parallel to the existing `test-each`. The wasm matrix entry uses `build-each`; the cross/native container entries use `build-container dataplane`. All entries pass platform, profile, and libc explicitly via `JUST_VARS` so the just default fallbacks don't matter and the row is self-documenting. `must-build` is added to the final-status aggregator's `needs` list along with a step that flags any matrix-row failure -- so a red must-build row turns the workflow conclusion red even though the job itself only blocks the summary aggregator. The `# - check # TODO: add dependency before PR lands` line in the job's `needs:` block flags that under the current `check` / `build` implementation those jobs do not exercise cross/wasm, so making `must-build` strictly depend on them would just slow down feedback for the cross builds without buying coverage. Tighten when `check` itself learns to honour the platform/libc axis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The dataplane.tar `buildPhase` unconditionally tarred in `libc.out`
(`pkgs.pkgsHostHost.libc`) and `${pkgs.pkgsHostHost.glibc.libgcc}`.
The latter is a glibc-evaluation reference: under a musl pkgsCross
`pkgs.pkgsHostHost.glibc` triggers nix trying to build glibc against
musl, which is a nonsensical combination and fails outright with
errors from `sysdeps/x86_64/multiarch/strcasecmp_l.c` ifunc redirection
inside `glibc-nolibgcc-x86_64-unknown-linux-musl-2.42-61`.
Gate the libgcc input on `libc == "gnu"`: glibc-dynamic Rust binaries
need `libgcc_s.so.1` for unwinding, but musl Rust targets statically
link musl and Rust's compiler-builtins and have no libgcc_s consumer.
The exact store path of the libgcc input matters: nix's glibc ld-linux
is compiled with `pkgs.pkgsHostHost.glibc.libgcc` (`xgcc-<ver>-libgcc`
on native, `libgcc-<triple>-<ver>` on cross) in its system search list.
Bundling libgcc_s.so.1 from any other store path (e.g.
`pkgs.stdenv.cc.cc.lib`, which has the same content but a different
name) results in the file being present in the container but
unreachable: the loader doesn't search the directory and there's no
RUNPATH to redirect it. Symptom on a binary built that way:
/bin/dataplane: error while loading shared libraries:
libgcc_s.so.1: cannot open shared object file: No such file or
directory
Keep libc.out bundled in both libc variants. The Rust binaries are
static on musl and don't need it, but the busybox we ship alongside
for `/bin/*` shell utilities is dynamically linked against whichever
libc its pkgset uses. On musl-cross-arch the busybox interpreter is
`${pkgs.pkgsHostHost.libc}/lib/ld-musl-<arch>.so.1`; without that store
path inside the tar the applets fail at exec with no usable libc.
Rename the inner `libc` let-binding to `libc-pkg` so the outer
function-arg `libc` (the string "gnu" / "musl" / "unknown") stays
visible inside this scope for the conditional. Add a header comment
on the new `libgcc-tar-input` documenting the path-matching constraint
so the next person doesn't "fix" it to a more-natural-looking
`stdenv.cc.cc.lib`. Add a matching comment on `libc-tar-input`
explaining why busybox makes the bundled libc unconditional.
Also: typo fix on the surrounding comment block (`hazzards` -> `hazards`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The debug-tools container content list referenced `pkgs.libgcc.libgcc`, which only exists as a passthru on the native gcc derivation (`outputs = [ "out" "man" "info" "lib" "libgcc" "checksum" ]`). Under a cross pkgs (`pkgsCross.aarch64-multiplatform`), `pkgs.libgcc` is a different derivation -- the static cross-libgcc helpers (`crtbegin.o`/`libgcc.a`/...) with `outputs = [ "out" "dev" ]` and no `libgcc` output -- so the attribute access raises "attribute 'libgcc' missing" during eval of debug-tools for any non-native build. `pkgs.pkgsHostHost.glibc.libgcc` resolves correctly in both contexts and points at the same store path that the bundled glibc ld-linux has in its compiled-in library search list (so runtime resolution still works the way it has been). Gate the inclusion on `libc == "gnu"` so musl-libc targets don't drag in glibc.libgcc, which has no purpose there -- musl Rust binaries use the system llvm libunwind, not libgcc_s.so.1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
FRR's LDFLAGS hard-coded `-L${fancy.libgccjit}/lib -latomic`, pointing
at the glibc-targeted libgccjit-bundled `libatomic.so.1`. That's the
right path when targeting glibc but is ABI-wrong (and at the wrong
store path) when targeting musl.
Add `libc` to the overlay's function args (and inherit it through
`default.nix`'s overlays import) so the LDFLAGS can pick a
libc-specific path:
- libc == "gnu": `${fancy.libgccjit}/lib/libatomic.so.1`
The libgccjit overlay still builds with the host stdenv (its
own override has `# TODO: debug issue preventing clang build`),
so its libatomic is glibc-targeted. Same path FRR has always
used.
- libc == "musl": `${stdenv.cc.cc.lib}/${triple}/lib/libatomic.so.1`
The gcc-libs output of the cross-musl gcc that backs the
cross-musl clang stdenv. Note: `stdenv`, *not* `stdenv'` --
the prime is the clang stdenv whose `cc.cc` is clang and
contains no libatomic at all.
- else: throw, to fail loudly on a libc this layout doesn't yet
cover.
Link is dynamic on both sides (the `-L .. -latomic` sits *outside* the
`-Wl,--push-state,...,-Bstatic` block). Single-`.so`-process-wide is
the only ODR-safe choice for libatomic: its `lock_for_pointer` lock
table is per-image, so a static copy inside libfrr.so cannot
synchronize with any other consumer that picks up libatomic.so
dynamically.
Also extend the `preFixup` nuke-refs allowlist to keep the cross-musl
gcc-libs path when targeting musl, so the RUNPATH entry pointing at
the dynamic libatomic.so survives fixup -- without it, nuke-refs
would scrub the entry, the binary's loader couldn't find libatomic,
and `watchfrr` / friends would die at startup with
`Error relocating .../libfrr.so: __atomic_*: symbol not found`.
(`libxcrypt` was already preserved in the working tree's allowlist
shape but missing from this branch -- include it here so the allowlist
matches what's actually linked.)
The same libc-aware nuke-refs treatment applies to `frr-agent`: its
fixupPhase referenced `libgcc.lib`, but under cross pkgs `final.libgcc`
is the static cross-libgcc helpers derivation (outputs `[ "out" "dev" ]`
only -- no `lib` attribute), so the package failed to evaluate for
every non-native target. Move the fixupPhase from the package into
the overlay so `libc` is in scope, gate the libgcc exclusion on
`libc == "gnu"`, and use `pkgsHostHost.glibc.libgcc` -- the path the
bundled ld-linux's compiled-in search list points at, matching the
libgcc handling already in place at the dataplane.tar bundler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Two unrelated FRR-package cleanups that surface once the overlay starts
exercising libc=musl:
1. `libgccjit` is unconditional in FRR's buildInputs but is only meaningful
on glibc. The overlay's LDFLAGS already gates its `-L${libgccjit}/lib
-latomic` on `libc == "gnu"` and pulls libatomic from the cross-musl
gcc-libs output instead, so on musl libgccjit is just dead weight in
the build closure (and a possible source of glibc-targeted ABI leakage
into the musl runtime image). Gate the buildInput on
`stdenv.hostPlatform.isGnu`.
2. `--enable-grpc=no` / `--enable-protobuf=no` are mis-spelled autoconf
flags. Autoconf's `AC_ARG_ENABLE(grpc, ...)` accepts `--enable-grpc[=ARG]`
and `--disable-grpc`, but `--enable-grpc=no` is parsed as
`enableval=no` for the `grpc` feature -- which most projects handle by
coincidence, but it's not the documented spelling and breaks under
stricter autoconf versions. `--disable-grpc` / `--disable-protobuf`
is the canonical spelling. No runtime-behavior change today, but
the syntax is no longer wrong-looking-but-works-anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
FRR was just built with --disable-protobuf, and dplane-plugin does not actually link against protobufc itself, so the buildInput (and the matching call arg) were dead weight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Native cargo invocations from the dev shell (cargo build/clippy/test --doc) defaulted to the build-host triple while LIBRARY_PATH and PKG_CONFIG_PATH were already pointing at the cross sysroot. The resulting links pulled in cross-target libraries against the host rust-std and failed (e.g. glibc rust-std + musl libc gave undefined references to `open64`, `fstat64`, ...). Pin CARGO_BUILD_TARGET to the same triple the sysroot is built for, and set PKG_CONFIG_ALLOW_CROSS=1 so the Rust pkg-config crate stops refusing cross-target builds (PKG_CONFIG_PATH already points at the matching cross sysroot). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Glibc-targeted Rust binaries unwind through libgcc_s.so.1 (still pulled into the image via glibc.libgcc). Musl and wasi have no libgcc consumer, so panic-unwind has no unwinder unless we ask build-std to pull LLVM's libunwind out of the sysroot. Gate the build-std-features flag on libc != "gnu" so glibc keeps using the libgcc path while musl/wasi get system-llvm-libunwind appended. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Extract a generic aarch64 platform definition (march=generic, no march or mcpu overrides) and refactor bluefield2 as a recursiveUpdate of it to retain its armv8.2-a / cortex-a72 specialization. Lets cross-platform CI exercise a vanilla aarch64 build without depending on the bluefield-specific CPU/march flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Wire a `cargo check` derivation per workspace package as `check.<name>`, mirroring the existing `workspace.<name>` build derivations. The point is to give CI a fast, no-link verification path -- particularly useful for wasm32-wasip1 where a full container build is overkill but we still want a build-error gate. Add matching `just check` (single package) and `just check-each` (all packages) recipes that drive the new derivation through the standard nix build pipeline. Also rearrange the `inherit` list so `check`/`dataplane` land in alphabetical order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Lets contributors execute the wasm32-wasip1 binaries we already build without leaving the dev shell: - wasmtime in the dev-shell tooling - `[target.wasm32-wasip1] runner = "wasmtime run --dir ."` so plain `cargo run`/`cargo test` against the wasm target invokes wasmtime automatically, with the cwd bound in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The single must-build matrix was conflating two different jobs:
a fast wasm sanity check and a slow cross-arch container build sweep.
Split them along that line:
wasm: same triggers as must-build (devfiles, tags, dispatch),
runs the wasm32-wasip1 `check` recipe only. Keeps wasm verification
as a blocker for any PR that touches dev files. Recipe is shaped as
a `{name, args}` object so the matrix axis carries recipe+args as a
single tuple rather than as parallel axes.
cross: {aarch64, bluefield3} x {gnu, musl} x {debug} x
{build-container dataplane, build-container frr.dataplane}. Covers
both the dataplane container and the FRR sidecar, the two images
most affected by this PR's FRR overlay / dplane-plugin / dplane-rpc
cross-arch fixes. Release profile is dropped to keep the leg count
flat while picking up FRR; cross-arch build problems surface in
debug just as well. Gated narrowly (PR label `ci:+cross`, or
push/merge_group).
Advisory failure handling: job-level `continue-on-error: true`. A
failing leg still reports `failure` to its own conclusion -- the job
badge goes red, the build step's `failure()` predicate fires (so the
shared `*tmate` anchor still opens a session on workflow_dispatch
debug), and the leg is visible in the run summary. The workflow's
overall conclusion ignores the failure, so cross is non-blocking.
`needs.cross.result` reports `success` to dependents under job-level
CoE, so the summary aggregator does not (and cannot) gate on cross;
treat cross as purely informational.
Add `check` as a hard dependency so we don't burn a full cross build
on code that won't even type-check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`docker import` reads a raw rootfs tar (no manifest, no platform metadata).
Without an explicit --platform, the imported image is tagged with the host
runner's architecture even when the rootfs is for a different one, which
silently mislabels cross-built images and would mislead anything that
consumes them by platform-aware pull or digest.
Map the just `platform` variable (aarch64 / bluefield2 / bluefield3 /
x86-64-v3 / x86-64-v4 / zen3 / zen4 / zen5) to the docker linux/{arm64,amd64}
pair before calling import. Fail loudly on an unmapped platform rather
than silently fall back to host arch.
Only the `dataplane` arm of `build-container` is affected; the other arms
use `docker load` on real image tarballs (which carry their own platform
metadata) and do not need --platform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`nuke-refs` is generated by `replaceVarsWith` that bakes a perl path
into the script. Under a cross pkgset `prev.nukeReferences` (and a
plain `nukeReferences` argument grabbed without splicing) substitutes
a target-arch perl: e.g. `perl-aarch64-unknown-linux-gnu-5.42.0` for
an aarch64 cross. Both `frr-build`'s preFixup and `frr-agent`'s
fixupPhase invoke `nuke-refs` on the build host during this
derivation's own build, where the kernel cannot execute target-arch
ELF and the build dies with:
nuke-refs: ...-perl-aarch64-...-5.42.0/bin/perl:
cannot execute binary file: Exec format error
Reach into `final.buildPackages.nukeReferences` in both spots so the
substituted perl is the build-host one. Verified by building
`containers.frr.dataplane` for aarch64/gnu, which the cross matrix
added in this stack exercises.
The sibling `removeReferencesTo` is shell-only -- it doesn't bake in
perl, and the `@shell@` substituted into the script is
`stdenvNoCC.shell`, which resolves to build-host bash on every
pkgset. No equivalent fix is needed there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
No need to bother with cross compile until miri passes. Best not to overload the CI, and miri failures will often indicate that cross is going to be pointless. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The `libc` string is a tag carried through the build for derivation naming and platform dispatch. For `wasm32-wasip1` it was set to `"unknown"`, which read as "we don't know what libc this is" rather than the truth: there is no libc -- WASI Preview 1 provides its own system interface and the Rust target name uses the `wasi` env field, not a libc. Use `"none"` so the tag describes the actual situation. Mechanical rename across `default.nix` (comment), `justfile` (`libc` default for `wasm32-wasip1`), `nix/platforms.nix` (`wasm32.wasip1.<libc>` attr key), and `.github/workflows/dev.yml` (matrix `libc` value). No build-output change for non-wasm targets. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
nixpkgs' musl `rustPlatform` (which `frr-agent` is currently built
with) emits binaries with `DT_NEEDED libgcc_s.so.1` and
`_Unwind_*@GCC_*` references for stack unwinding. Our musl FRR
image has no `libgcc_s.so.1` -- the `frr-agent` overlay's
`fixupPhase` deliberately nukes the libgcc store-path on musl -- so
the musl loader bails before `main`:
Error relocating /bin/frr-agent: _Unwind_Resume: symbol not found
Error relocating /bin/frr-agent: _Unwind_Backtrace: symbol not found
...
Force `+crt-static` on the musl variant of `frr-agent` so the C
runtime (and with it `libgcc_eh.a`) is linked statically. The
resulting binary is `static-pie linked` with no `_Unwind_*` undefs
and no `NEEDED` entries, and the container starts.
The accompanying comment on the `fixupPhase` is also corrected: the
prior claim that "musl Rust uses llvm-libunwind and has no libgcc_s
consumer" was wrong for the way `frr-agent` is currently built --
that's the path the rest of the workspace takes via crane +
`-Zbuild-std=...,system-llvm-libunwind`, not the path `frr-agent`
takes through nixpkgs' `rustPlatform.buildRustPackage`.
TODO: switch `frr-agent` onto the workspace's crane +
`system-llvm-libunwind` build path so it pulls LLVM libunwind
statically like every other Rust binary in this repo, rather than
relying on libgcc_eh from the gcc backing the cross-musl clang
stdenv.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
06bea51 to
f45af96
Compare
qmonnet
approved these changes
May 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎶 everything is WASM 🎼
Add
wasm32-wasip1build checks for packages that aren't explicitlyexcluded, and tune up the cross-compile path so aarch64 and musl builds
reliably succeed.
wasm32-wasip1 check path: a
cargo checkderivation per workspacepackage (
check.<name>), awasmtimerunner in.cargo/config.toml,and per-package
[workspace.metadata.package]carrying awasmflagso
default.nixcan filter the package list. CI gains awasmjob(blocker; same triggers as the previous
must-build) that runs thecheckrecipe onwasm32-wasip1.Workspace feature hygiene: stop enabling features at
[workspace.dependencies]-- those bleed into every consumer andbreak builds under restricted environments (wasm, cross, miri). Move
feature enablement down to the individual packages that actually need
them, and document the rule in a comment.
musl cross-compilation: rewrite glibc-internal
__in6_uto POSIXs6_addrindplane-plugin; patch missing<string.h>intodplane-rpc'scpmock.c; route FRR's-latomicto the cross-muslgcc-libspath under musl and preserve its store path throughnuke-refs; drop the glibc-onlylibgccjitbuildInput from the muslFRR build; gate
glibc.libgccbundling (in bothdataplane.tarandthe
debug-toolscontainer) onlibc == "gnu"so musl images don'tpull glibc-targeted outputs, while keeping
libc.outbundled on bothlibcs so the dynamically-linked busybox can resolve its
ld-musl-<arch>.so.1interpreter; reach intobuildPackages.nukeReferencesin the FRR overlay so the substitutedperl in
nuke-refsis build-host and not the target-arch binary thatthe build host can't execute.
aarch64 cross-compilation: add a generic
aarch64platformalongside the existing
bluefield3(vendor-specific aarch64) entry,and fix the correctness bugs the new matrix flushed out: pin libcap's
BUILD_CCto a build-host clang and pass the wrapped cross-clang asthe target
CC; usepkgsBuildBuildfor dev-shell tooling; comparebuildPlatform(nothostPlatform) when deciding whether we'recross-compiling; pass the cross
pkgsset through to crane; enablesystem-llvm-libunwindon non-glibc Rust targets; pin the dev-shellcargo target to the sysroot triple.
CI: split
must-buildinto two jobs ---wasm: fast wasm sanity check, blocker on devfile changes /tags / dispatch.
cross:{aarch64, bluefield3} x {gnu, musl} x {debug} x {build-container dataplane, build-container frr.dataplane},gated by
ci:+crosslabel or push/merge_group. Job-levelcontinue-on-error: true: a failing leg still reportsfailureto its own conclusion (the leg badge goes red and the leg is
visible in the run summary), but the workflow's overall conclusion
ignores the failure so cross is non-blocking.
needs.cross.resultreportssuccessto dependents underjob-level CoE, so the summary aggregator does not (and cannot)
gate on cross; treat it as purely informational.
Plus
build-each/check-eachjustrecipes for local iteration,and a
docker import --platformfix so cross-builtdataplane.tarrootfs imports are tagged with the right architecture instead of
silently inheriting the runner's.