Repository navigation
build: link libutil statically on FreeBSD - #40532
Conversation
FreeBSD 15 bumped libutil's soname from .so.9 to .so.10 and removed libutil.so.9 from base. A binary built against the 14.3 sysroot with -lutil fails to load on 15.x with: ld-elf.so.1: Shared object "libutil.so.9" not found, required by "bun" The only symbol bun imports from libutil is openpty. Link the static archive instead (-l:libutil.a, same idiom as libatomic on Linux) so the NEEDED entry disappears and one binary runs on 14.x and 15.x. Every other library on the FreeBSD link line kept its soname across the 15.0 bump. Fixes #40530
|
Warning Review limit reached
On-demand reviews are free for the next 25 days. After that, they cost $0.25 per reviewed file. Or wait 13 minutes for your next included review. View limit detailsLimit details: You’ve used all 5 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Comment |
There was a problem hiding this comment.
LGTM — one-token link-line change following an existing pattern.
What was reviewed:
-l:filenameis already used in this function forlibatomic.a(line 76), and the FreeBSD build links with lld (both cross and native since FreeBSD 13), which supports the syntax.- The sysroot extraction hint in
config.tspulls./usr/libfrom base.txz, which includeslibutil.a, so the cross build has the archive. - Considered whether this belongs in
workarounds.ts— it doesn't: there's no upstream fix to wait for, static linking is the permanent binary-compat decision, so noexpectedToBeFixedpredicate applies.
Extended reasoning...
Overview
This PR changes one argument on the FreeBSD link line in scripts/build/bun.ts::systemLibs(), replacing -lutil with -l:libutil.a, and updates the adjacent comment to explain why (libutil's soname bumped .so.9 → .so.10 between FreeBSD 14.x and 15.0, so a dynamic NEEDED libutil.so.9 entry from the 14.3 sysroot fails on 15.x; issue #40530). Only openpty is imported from libutil, so static linking pulls in a single small object.
Security risks
None. This is a build-system link-mode selection for a system library on FreeBSD. No user input is parsed, no network or auth surface is touched, and the linked code (openpty) is unchanged — only how the symbol is resolved at link time.
Level of scrutiny
Low. The change is a single flag substitution that follows an established idiom already present a few lines above in the same function (-l:libatomic.a on Linux). scripts/build/CLAUDE.md explicitly names systemLibs() as one of the intended per-platform branch points, and the change satisfies its "explicit over implicit — every decision traceable to a greppable line with a comment explaining why" goal. The -l: syntax is valid for lld, which is both the cross linker and FreeBSD's native system linker. The 14.3 sysroot is an extracted base.txz whose ./usr/lib is pulled in by the documented setup command in config.ts, and FreeBSD base ships libutil.a there.
Other factors
I weighed whether this should register a self-obsoleting entry in workarounds.ts per the build-system conventions. It should not: that registry is for temporary toolchain/OS bugs with an expectedToBeFixed predicate, whereas this is a permanent binary-compatibility decision with no future condition under which reverting to dynamic linking becomes desirable. No CODEOWNERS entry covers scripts/build/. The PR timeline shows no outstanding reviewer objections. The lack of an automated test is acceptable here — this is cross-link configuration for a target CI cannot execute, and the observable effect (absence of a NEEDED libutil.so.9 entry) was verified at the ELF level as described.
|
curious how much larger the binary gets |
|
Effectively nothing. The link pulls two archive members: pty.o (openpty and forkpty, the only symbols bun references) and login_tty.o (forkpty calls it). Compiled from the releng/14.3 sources at -O2, pty.o is 483 bytes of text and login_tty.o is about 150 bytes, with zero data and bss. Everything they call (posix_openpt, grantpt, unlockpt, setsid, ioctl) stays dynamic in libc.so.7. So the binary grows by under 1 KB, which disappears into section alignment on a binary this size. |
Problem
ld-elf.so.1: Shared object "libutil.so.9" not found, required by "bun"(Bun 1.4 binary unable to running on FreeBSD #40530).readelf -don the 1.4.0 release binary shows a NEEDED entry forlibutil.so.9.FREEBSD_VERSION_DEFAULTinscripts/build/config.ts) and link-lutil(scripts/build/bun.ts:99). FreeBSD 15.0 bumped libutil's soname tolibutil.so.10andObsoleteFiles.incremovedlibutil.so.9from base, so a fresh 15.x install cannot satisfy the dependency.Fix
-lutilwith-l:libutil.aon the FreeBSD link line. The static archive ships in base.txz (/usr/lib/libutil.a), so the sysroot already has it.openpty(used bysrc/runtime/api/bun/Terminal.rs). With the static link it resolves at link time and the NEEDED entry disappears, so one binary runs on 14.x and 15.x.build.ninjalink edge now carries-l:libutil.aand no-lutil, and a FreeBSD-target link probe with lld shows the dynamic link producesNEEDED libutil.so.9+ undefinedopenptywhile the static link produces no libutil NEEDED entry and a definedopenpty.Background
-l:nametells the linker to search for that exact file name, which forces the static archive even whenlibutil.soexists next to it. The build already uses this idiom for libatomic on Linux.Notes
Evidence for the soname bump, from freebsd-src:
releng/14.3 lib/libutil/Makefile:SHLIB_MAJOR= 9releng/15.1 lib/libutil/Makefile:SHLIB_MAJOR= 10releng/15.1 ObsoleteFiles.inclistsOLD_LIBS+=lib/libutil.so.9Release binary check (bun-v1.4.0
bun-freebsd-x64.zip):Only
openptyin the dynamic symbol table matches libutil's API set, so static linking pulls in one small object.Link probe (lld,
--target=x86_64-unknown-freebsd14.3): an executable that referencesopenptylinked with-lutilgetsNEEDED libutil.so.9andUND openpty; linked with-l:libutil.ait gets no libutil NEEDED entry and a definedopenptysymbol.Also checked:
scripts/buildtypecheck failures (bunx tsc -p scripts/build/tsconfig.json) are identical with and without this change (14 pre-existing errors in ci.ts, jsonByteClass.ts, rust-lto-fix-cli.ts, xmlByteClass.ts). The native-FreeBSD build path is unaffected:/usr/lib/libutil.ais part of base there too.no test proof · iteration 1 · build/CI scripts only; test-proof not applicable