fix: fall back to musl binary when gnu prebuild fails to load#9
Merged
Conversation
Before: the napi-rs-generated loader selects between the gnu and musl
binaries purely by libc *family* (`isMusl()`), with no glibc-version
check and no cross-family fallback. On a glibc (gnu) Linux host whose
system glibc is older than what the gnu prebuild was linked against,
`require('./index.linux-<arch>-gnu.node')` throws (e.g.
`GLIBC_2.33 not found`); the loader records the error and gives up,
hard-failing install even though we also ship a statically-linked musl
binary that runs fine on glibc hosts.
After: the binding post-processor injects a fallback into the linux
gnu (x64 and arm64) branches so that, when the gnu `.node` require
fails, the loader also tries the bundled
`./index.linux-<arch>-musl.node` before giving up. The musl require is
wrapped in its own try/catch and pushes to `loadErrors` on failure,
matching the existing generated style.
The change lives in scripts/strip-binding-fallbacks.js (which already
post-processes the generated binding.js) so it survives regeneration,
rather than being a hand-edit of generated output that would be
clobbered on the next build.
The gnu->musl fallback injection in strip-binding-fallbacks.js required the linux-<arch>-gnu require block to be present and process.exit(1)'d otherwise. Per-target musl builds (napi build --target *-musl) emit a loader with no gnu require block, so the guard tripped and failed the linux-x64-musl / linux-arm64-musl CI jobs. Make the injection tolerant: if an arch's gnu block is found, inject the musl fallback as before; if it's absent, skip that arch and continue. The fallback is only meaningful when a gnu block exists, so skipping is correct. The Step 2 package-name fallback strip is unchanged.
This restores the script to its pre-#9 behavior: it only strips the @electron-internal/* package-name fallbacks from the generated binding.js. The gnu->musl fallback is being moved to a @napi-rs/cli patch (mirroring how #7 added the ia32 branch), so the runtime string-rewrite injection is no longer needed here.
Mirror PR #7's approach (which added the linux-ia32-gnu branch by patching the napi-rs loader template) and add a gnu->musl fallback the same way, instead of the runtime regex string-rewrite that was in strip-binding-fallbacks.js. The generated loader picks gnu vs musl purely by libc *family* (isMusl()), with no glibc-version check. On a glibc host whose system glibc is older than what our gnu prebuild was linked against, requiring ./index.linux-<arch>-gnu.node throws (e.g. GLIBC_2.33 not found) and the loader gives up — even though we also bundle a statically-linked musl binary that runs fine on glibc hosts. This patches @napi-rs/cli's requireNative() template so the linux x64 and arm64 gnu (else) branches also try ./index.linux-<arch>-musl.node after the gnu require fails, using the generator's own requireTuple() helper so the emitted code matches the generated style exactly. Because the fallback is baked into the template at install time, the generated binding.js comes out correct on every build regardless of how many times the build runs — avoiding the docker+host double-pass idempotency problem that affected the regex post-processor.
Regenerated by `yarn install --mode update-lockfile` after editing the @napi-rs/cli patch. Only the patched-package resolution hash and checksum change (the patch now also adds the gnu->musl fallback). Keeps `yarn install --immutable` green in CI.
MarshallOfSound
approved these changes
Jun 23, 2026
dsanders11
requested changes
Jun 23, 2026
dsanders11
left a comment
Member
There was a problem hiding this comment.
Blocking on adding validation in CI.
dsanders11
approved these changes
Jun 23, 2026
|
🎉 This PR is included in version 1.0.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
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.
Requested by Samuel Attard · Slack thread
Before: on a glibc (gnu) Linux box where the gnu prebuild can't load — for example because the system glibc is older than what the prebuild was linked against, so
require('./index.linux-x64-gnu.node')throwsGLIBC_2.33 not found— install hard-fails, even though we already ship a working musl binary in the same package.After: when the gnu prebuild fails to load, the loader falls back to the bundled
index.linux-x64-musl.node/index.linux-arm64-musl.node. The musl binary is statically linked, so it runs fine on glibc hosts. A usable binary that's already in the package is no longer unreachable.How: the napi-rs-generated
binding.jspicks gnu vs musl purely by libc family (isMusl()), with no glibc-version check and no cross-family fallback — so on a glibc host it only ever tries the gnu binary and gives up when that throws. Rather than hand-editing generated output (which gets clobbered on the next build), this change lives inscripts/strip-binding-fallbacks.js, the post-processor that already rewrites the generated loader. For the linux gnux64andarm64branches it injects a fallback: after the gnu.noderequire fails it also tries the corresponding musl.node, wrapped in its own try/catch that pushes toloadErrorson failure (matching the existing generated style). It can only ever turn a hard install failure into a working load, and never changes behaviour when the gnu binary loads.This is a defense-in-depth safety net and is one of two PRs. The sibling PR fixes the build toolchain so the gnu prebuild targets an old-enough glibc in the first place; this one ensures a too-new gnu build can never hard-fail when a usable musl binary is present in the package.
Refs electron/electron#52099, electron/electron#52101.