@@ -38,31 +38,31 @@ build:
3838 # transitive-deps $ORIGIN RPATH chain onto every ELF in the
3939 # install, INCLUDING ld-linux-*.so.*. The loader parses its own
4040 # RPATH at startup and SIGSEGVs (see pkgxdev/brewkit#345 and the
41- # diagnostic output in this PR's history). We do the patchelf
42- # ourselves below for the ELFs that actually need RPATH (bin/*),
43- # and leave ld.so alone .
41+ # diagnostic output in this PR's history). We handle relocatability
42+ # below: linker scripts use basenames, and glibc-owned ELF tools are
43+ # invoked through relative ld.so wrappers .
4444 skip :
4545 - fix-patchelf
4646
4747 dependencies :
48- gnu.org/make : ' >=4.0'
49- gnu.org/gawk : ' >=3'
50- gnu.org/gcc : ' * ' # pkgx ships 10.5–16.1; verified with 16.1
51- gnu.org/binutils : ' >=2.40' # required by glibc 2.42+; older glibcs also build with 2.46
52- gnu.org/gettext : ' * '
53- gnu.org/texinfo : ' * ' # required for `make install` (manual/libc.info)
54- gnu.org/bison : ' ^3 '
55- gnu.org/sed : ' * '
56- gnu.org/coreutils : ' * '
57- gnu.org/findutils : ' * '
58- gnu.org/grep : ' * '
59- gnu.org/diffutils : ' * '
60- gnu.org/patch : ' * '
61- gnu.org/m4 : ' * '
62- perl.org : ' ^5 '
63- python.org : ' ~3.11'
64- kernel.org/linux-headers : ' * ' # the file that makes the build host-independent
65- nixos.org/patchelf : ' * ' # we patchelf bin/* ourselves (see `skip: fix-patchelf` above)
48+ gnu.org/make : " >=4.0"
49+ gnu.org/gawk : " >=3"
50+ gnu.org/gcc : " * " # pkgx ships 10.5–16.1; verified with 16.1
51+ gnu.org/binutils : " >=2.40" # required by glibc 2.42+; older glibcs also build with 2.46
52+ gnu.org/gettext : " * "
53+ gnu.org/texinfo : " * " # required for `make install` (manual/libc.info)
54+ gnu.org/bison : " ^3 "
55+ gnu.org/sed : " * "
56+ gnu.org/coreutils : " * "
57+ gnu.org/findutils : " * "
58+ gnu.org/grep : " * "
59+ gnu.org/diffutils : " * "
60+ gnu.org/patch : " * "
61+ gnu.org/m4 : " * "
62+ perl.org : " ^5 "
63+ python.org : " ~3.11"
64+ kernel.org/linux-headers : " * " # the file that makes the build host-independent
65+ nixos.org/patchelf : " * " # used to detect ELF tools before wrapping them
6666
6767 working-directory : build
6868
7777 # patch leaves `LD_SO_CACHE` referencing an undefined `PREFIX` macro.
7878 # We therefore gate the patches on the version.
7979 - run :
80- - patch -p1 < props/dont-use-system-ld-so-cache.patch
81- - patch -p1 --forward --no-backup-if-mismatch < props/dont-use-system-ld-so-preload.patch || true
80+ - patch -p1 < props/dont-use-system-ld-so-cache.patch
81+ - patch -p1 --forward --no-backup-if-mismatch < props/dont-use-system-ld-so-preload.patch || true
8282 if : " >=2.32"
8383 working-directory : ..
8484
9494 - run :
9595 - EXTRA_CFLAGS=""
9696 - if [ {{version.major}}{{version.minor}} -lt 232 ]; then
97- - EXTRA_CFLAGS="-fcommon -Wno-error"
97+ - EXTRA_CFLAGS="-fcommon -Wno-error"
9898 - fi
9999 # glibc decides its own pie/-shared per target — pkgx gcc 16
100100 # is built with --enable-default-pie, so its CC defaults add
@@ -131,63 +131,67 @@ build:
131131
132132 # Convenience: arch-agnostic ld.so symlink that points into the
133133 # sub-libdir (kept out of the top {{prefix}}/lib/).
134- - run : |
135- case "{{hw.arch}}" in
136- x86-64) L=ld-linux-x86-64.so.2 ;;
137- aarch64) L=ld-linux-aarch64.so.1 ;;
138- esac
139- ln -sf ../lib/glibc-{{version.marketing}}/$L ld.so
134+ - run : ln -sf ../lib/glibc-{{version.marketing}}/$LDSO ld.so
140135 working-directory : ${{prefix}}/bin
141136
142137 # libc.so / libm.so / … are TEXT linker scripts (GROUP (...)) with
143- # +brewing baked into absolute paths. Strip +brewing so downstream
144- # `- lc` doesn't hit "cannot find +brewing/lib/.../libc.so.6" after
145- # brewkit's +brewing → final-prefix rename. ( Detect via first-line
146- # marker, not file(1) — not on the test-sandbox PATH.)
138+ # absolute build-prefix paths. Make the GROUP members basenames so
139+ # downstream `-L $LIBDIR - lc` works after the bottle relocates from
140+ # /opt to ~/.pkgx (or anywhere else). Detect via first-line marker,
141+ # not file(1) — not on the test-sandbox PATH.
147142 - run : |
143+ PREFIX_FINAL=$(echo "{{prefix}}" | sed 's/+brewing$//')
148144 for f in $(find . -maxdepth 2 -type f -name '*.so'); do
149145 if head -c 16 "$f" | grep -q "GNU ld script"; then
150- sed -i 's|+brewing||g' "$f"
151- echo "stripped +brewing from $f"
146+ sed -i \
147+ -e 's|{{prefix}}/lib/glibc-{{version.marketing}}/||g' \
148+ -e "s|$PREFIX_FINAL/lib/glibc-{{version.marketing}}/||g" \
149+ "$f"
150+ echo "relocated linker script $f"
152151 fi
153152 done
154153 working-directory: ${{prefix}}/lib
155154
156- # With brewkit's fix-patchelf skipped (see `skip:` at top of build),
157- # we patchelf the ELFs that need it ourselves:
155+ # PT_INTERP is absolute and cannot use $ORIGIN or fall back. Do not
156+ # bake {{prefix}} or its post-rename variant into installed binaries:
157+ # that breaks when the bottle relocates to ~/.pkgx. Instead, seal the
158+ # glibc-owned ELF tools into libexec and replace bin/* / sbin/* with
159+ # POSIX sh wrappers that invoke this bottle's ld.so by relative path.
158160 #
159- # bin/*, sbin/* — fix +brewing PT_INTERP, add $ORIGIN-relative
160- # RPATH so libc.so.6 resolves at runtime
161- # lib/glibc-X.Y/ld-linux-* — leave alone (it's the loader;
162- # bootstraps from nothing)
163- # lib/glibc-X.Y/*.so.* — co-located with libc.so.6, no extra
164- # RPATH needed (executable's RPATH propagates)
165- #
166- # patchelf --set-interpreter exits non-zero on non-ELF inputs;
167- # we use that to filter shell scripts etc. without needing file(1).
168- #
169- # {{prefix}} at build time is the .../v2.43.0+brewing path; brewkit
170- # renames it to .../v2.43.0 before the bottle ships. PT_INTERP needs
171- # the *post-rename* path, otherwise the binary references a path
172- # that no longer exists ("required file not found"). RPATH uses
173- # $ORIGIN-relative so it survives the rename automatically.
161+ # Running an ELF through ld.so explicitly ignores the ELF's own
162+ # PT_INTERP, so the inner libexec binaries may keep whatever glibc's
163+ # build installed. The wrapper supplies --library-path explicitly.
174164 - run : |
175- case "{{hw.arch}}" in
176- x86-64) LDSO=ld-linux-x86-64.so.2 ;;
177- aarch64) LDSO=ld-linux-aarch64.so.1 ;;
178- esac
179- PREFIX_FINAL=$(echo "{{prefix}}" | sed 's/+brewing$//')
180- LIBDIR_INTERP="$PREFIX_FINAL/lib/glibc-{{version.marketing}}"
181- RPATH='$ORIGIN/../lib/glibc-{{version.marketing}}'
182- for f in {{prefix}}/bin/* {{prefix}}/sbin/*; do
183- [ -f "$f" ] && [ ! -L "$f" ] || continue
184- if patchelf --set-interpreter "$LIBDIR_INTERP/$LDSO" "$f" 2>/dev/null; then
185- patchelf --force-rpath --set-rpath "$RPATH" "$f" 2>/dev/null
186- echo "patchelfd $f"
187- fi
165+ for dir in bin sbin; do
166+ mkdir -p "{{prefix}}/libexec/glibc-$dir"
167+ for f in "{{prefix}}/$dir"/*; do
168+ [ -f "$f" ] && [ ! -L "$f" ] || continue
169+ patchelf --print-interpreter "$f" >/dev/null 2>&1 || continue
170+ mv "$f" "{{prefix}}/libexec/glibc-$dir"
171+ sed -e "s/@LDSO@/$LDSO/g" -e "s/@DIR@/$dir/g" "$PROP" >$f
172+ chmod 775 $f
173+ echo "wrapped $f"
174+ done
188175 done
176+ prop: |
177+ #!/bin/sh
178+
179+ case "$0" in
180+ */*) bindir=${0%/*} ;;
181+ *) bindir=$(command -v -- "$0"); bindir=${bindir%/*} ;;
182+ esac
183+
184+ prefix=$(CDPATH= cd -- "$bindir/.." && pwd)
185+
186+ libdir="$prefix/lib/glibc-{{version.marketing}}"
187+
188+ exec "$libdir/@LDSO@" --library-path "$libdir" "$prefix/libexec/glibc-@DIR@/$(basename "$0")" "$@"
189189
190190 env :
191+ x86-64 :
192+ LDSO : ld-linux-x86-64.so.2
193+ aarch64 :
194+ LDSO : ld-linux-aarch64.so.1
191195 SCRIPTS :
192196 - catchsegv
193197 - ldd
@@ -212,22 +216,23 @@ build:
212216 - --disable-werror
213217 - --without-gd
214218 - --without-selinux
215- - --enable-kernel=3.10.0 # CentOS 7 / manylinux2014 baseline
219+ - --enable-kernel=3.10.0 # CentOS 7 / manylinux2014 baseline
216220 - --with-headers={{deps.kernel.org/linux-headers.prefix}}/include
217221 - --with-binutils={{deps.gnu.org/binutils.prefix}}/bin
218222 - --disable-multi-arch
219223
220224# Compile + run test.c against the bottle's libc + ld.so end-to-end.
221225# Exercises every piece of the bottle: headers, crt files, libc.so.6,
222- # the loader, and `iconv --version` to confirm the patchelf'd bin/* work.
226+ # the loader, and `iconv --version` to confirm the wrapped bin/* work.
223227#
224228# Previously the loader's RPATH was poisoned by brewkit's fix-patchelf
225229# (a 46-pkg $ORIGIN chain → SIGSEGV at startup; see pkgxdev/brewkit#345).
226- # Now that we `skip: fix-patchelf` and patchelf bin/* ourselves with
227- # clean RPATH, the loader bootstraps cleanly and the bin/* work.
230+ # Now that we `skip: fix-patchelf`, keep ld.so untouched, and wrap
231+ # bin/* through that loader, the loader bootstraps cleanly and the
232+ # bin/* work after relocation.
228233test :
229234 dependencies :
230- gnu.org/gcc : ' * '
235+ gnu.org/gcc : " * "
231236 env :
232237 x86-64 :
233238 LDSO : ld-linux-x86-64.so.2
@@ -242,51 +247,47 @@ test:
242247 - test -L "{{prefix}}/bin/ld.so" || { echo "missing bin/ld.so symlink"; exit 1; }
243248
244249 # 2) Loader self-test (was SIGSEGV before the fix-patchelf skip).
245- - run : |
246- echo "--- ld.so --version ---"
247- "$LIBDIR/$LDSO" --version | head -2
250+ - echo "--- ld.so --version ---"
251+ - ' "$LIBDIR/$LDSO" --version | head -2'
248252
249253 # 3) Compile test.c dynamically against the bottle's headers /
250254 # libs / loader. -B $LIBDIR picks our Scrt1.o / crti.o / crtn.o
251255 # instead of the host's pre-2.34 versions which reference
252256 # removed __libc_csu_init / __libc_csu_fini.
253- - run : |
254- gcc -o test-bottle test.c \
255- -nostdinc \
256- -isystem {{prefix}}/include \
257- -isystem {{deps.gnu.org/gcc.prefix}}/lib/gcc/*-linux-gnu/{{deps.gnu.org/gcc.version}}/include \
258- -B "$LIBDIR" \
259- -L "$LIBDIR" \
260- -Wl,--rpath="$LIBDIR" \
261- -Wl,--dynamic-linker="$LIBDIR/$LDSO"
262- readelf -d test-bottle | grep -E "NEEDED|RPATH" | head -5
257+ - gcc -o test-bottle test.c
258+ -nostdinc
259+ -isystem {{prefix}}/include
260+ -isystem {{deps.gnu.org/gcc.prefix}}/lib/gcc/*-linux-gnu/{{deps.gnu.org/gcc.version}}/include
261+ -B "$LIBDIR"
262+ -L "$LIBDIR"
263+ -Wl,--rpath="$LIBDIR"
264+ -Wl,--dynamic-linker="$LIBDIR/$LDSO"
265+ - readelf -d test-bottle | grep -E "NEEDED|RPATH|RUNPATH" | head -5
263266
264267 # 4) Run the dynamically-linked test bin — exercises PT_INTERP,
265268 # the loader, RPATH resolution, libc symbol lookup, printf, etc.
266- - run : |
267- set +e
268- stdout=$(./test-bottle 2>/tmp/test.err)
269- rc=$?
270- stderr=$(cat /tmp/test.err)
271- set -e
272- echo "exit code: $rc"
273- echo "stdout: $stdout"
274- echo "stderr: $stderr"
275- case "$stdout" in
276- "gnu_get_libc_version() = {{version.marketing}}"*) echo PASS ;;
277- *) echo "FAIL: expected gnu_get_libc_version() = {{version.marketing}}, got stdout=\"$stdout\" stderr=\"$stderr\" rc=$rc"; exit 1 ;;
278- esac
269+ - set +e
270+ - stdout=$(./test-bottle 2>/tmp/test.err)
271+ - rc=$?
272+ - stderr=$(cat /tmp/test.err)
273+ - set -e
274+ - ' echo "exit code: $rc"'
275+ - ' echo "stdout: $stdout"'
276+ - ' echo "stderr: $stderr"'
277+ - case "$stdout" in
278+ - ' "gnu_get_libc_version() = {{version.marketing}}"*) echo PASS ;;'
279+ - ' *) echo "FAIL: expected gnu_get_libc_version() = {{version.marketing}}, got stdout=\"$stdout\" stderr=\"$stderr\" rc=$rc"; exit 1 ;;'
280+ - esac
279281
280282 # 5) Bonus: one of the bottle's own bin/* should now work end-to-end.
281- # iconv --version prints "iconv (GNU libc) X.Y" — confirms our
282- # manual patchelf step gave bin/iconv a usable PT_INTERP + RPATH.
283- - run : |
284- out=$("{{prefix}}/bin/iconv" --version 2>&1 | head -1)
285- echo "bin/iconv --version: $out"
286- case "$out" in
287- *"(GNU libc) {{version.marketing}}"*) echo "bin/iconv PASS" ;;
288- *) echo "FAIL: expected '(GNU libc) {{version.marketing}}' in iconv --version output, got: $out"; exit 1 ;;
289- esac
283+ # iconv --version prints "iconv (GNU libc) X.Y" — confirms the
284+ # wrapper can find this bottle's relocated ld.so + libc.
285+ - out=$("{{prefix}}/bin/iconv" --version 2>&1 | head -1)
286+ - ' echo "bin/iconv --version: $out"'
287+ - case "$out" in
288+ - ' *"(GNU libc) {{version.marketing}}"*) echo "bin/iconv PASS" ;;'
289+ - ' *) echo "FAIL: expected (GNU libc) {{version.marketing}} in iconv --version output, got: $out"; exit 1 ;;'
290+ - esac
290291
291292provides :
292293 # Binaries that are installed across all supported versions
0 commit comments