From fcba0313b5b6e388cff4a442aad677a7d5cd1498 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 20:03:40 +0200 Subject: [PATCH 1/5] fix(gnu.org/gcc): add libc-wrapper for end-user pkgx gcc (#8423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-user `pkgx gcc test.c` on a host without distro glibc-devel / libc6-dev couldn't find stdlib.h + crt*.o. Earlier attempts via runtime.env (#13084) and --with-sysroot (#13092) failed because both poisoned gcc's own bootstrap build. This take installs a thin POSIX-sh wrapper at {{prefix}}/bin/ (modeled on the bklibcvenv pattern, brewkit#348) that: 1. resolves the sibling gnu.org/glibc bottle relative to its own install path 2. exec's the real binary (moved to libexec/gcc-wrap/) with -isystem $glibc/include + -L$glibc/lib appended 3. NO-OPs when CPATH is already set (brewkit build-context — the bootstrap gcc loop uses brewkit-composed CPATH, so injecting pkgx-glibc paths twice is unnecessary and could trigger C23 symbol mismatches like __isoc23_strtoul) Pinning gnu.org/glibc <2.38 avoids C23 symbol redirects absent on older CI/host runners; the wrapper's runtime CPATH check guards against build-time poisoning regardless. Wrappers cover gcc, g++, cpp, c++, gfortran. The existing cc -> gcc and gc++ -> c++ symlinks still resolve (now via the wrapper). The wrapper dispatches by $0 basename. Pairs with #13083 (multi-arch triplet symlinks). Together they should fully close #8423. Co-Authored-By: Claude Opus 4.7 --- projects/gnu.org/gcc/package.yml | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/projects/gnu.org/gcc/package.yml b/projects/gnu.org/gcc/package.yml index 5600b1b7d8..6894edf4b0 100644 --- a/projects/gnu.org/gcc/package.yml +++ b/projects/gnu.org/gcc/package.yml @@ -26,6 +26,16 @@ dependencies: gnu.org/mpfr: ">=2.4.0" gnu.org/mpc: ">=0.8.0" zlib.net: ^1.3 + linux: + # Bottle the libc the compiler can target so end-user invocations + # find stdlib.h + crt*.o + libc.so.6 without distro glibc-devel. + # See #8423. <2.38 avoids C23 symbol redirects that the host's + # older glibc lacks. The thin wrapper in build.script injects + # -isystem/-L at user-invocation time (NOT at gcc's own build), + # so the bootstrap loop is unaffected. The wrapper no-ops when + # CPATH is already set (brewkit build context). + gnu.org/glibc: "<2.38" + kernel.org/linux-headers: "*" darwin/x86-64: # since 15.1.0 libisl.sourceforge.io: ^0 @@ -164,6 +174,48 @@ build: if: darwin/x86-64 working-directory: ${{prefix}}/lib + # Linux libc-wrapper: install a thin shim at bin/ that + # injects -isystem $glibc/include + -L$glibc/lib so end-user + # `pkgx gcc test.c` finds stdlib.h + crt*.o + libc.so.6 from the + # pkgx-integrated glibc bottle — without needing distro glibc-devel. + # The wrapper is INERT when CPATH is already populated (brewkit build + # context), so the gcc bootstrap loop is unaffected. See #8423. + - run: | + mkdir -p {{prefix}}/libexec/gcc-wrap + cat > {{prefix}}/libexec/gcc-wrap.sh <<'WRAPPER' + #!/bin/sh + # pkgx libc-wrapper around the real compiler. Resolves the sibling + # gnu.org/glibc bottle relative to its own install path and adds + # -isystem/-L only when the caller hasn't already set up CPATH + # (i.e. only for naked end-user invocations). + self_name=$(basename "$0") + case "$self_name" in + cc) target=gcc ;; + gc++) target=c++ ;; + *) target="$self_name" ;; + esac + bindir=$(cd "$(dirname "$0")" && pwd) + real="$bindir/../libexec/gcc-wrap/$target" + if [ -z "$CPATH" ]; then + for d in "$bindir/../../../glibc/v"*; do + [ -d "$d/include" ] && libc="$d" && break + done + fi + if [ -n "$libc" ]; then + exec "$real" -isystem "$libc/include" -L"$libc/lib" "$@" + else + exec "$real" "$@" + fi + WRAPPER + chmod +x {{prefix}}/libexec/gcc-wrap.sh + cd {{prefix}}/bin + for tool in gcc g++ cpp c++ gfortran; do + [ -f "$tool" ] && [ ! -L "$tool" ] || continue + mv "$tool" ../libexec/gcc-wrap/ + ln -sf ../libexec/gcc-wrap.sh "$tool" + done + if: linux + env: # Branch from the Darwin maintainer of GCC, with a few generic fixes and # Apple Silicon support, located at https://github.com/iains/gcc-12-branch From f58a5b6df6f768cc28192c4364dc6d1595a01e6d Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 20:11:50 +0200 Subject: [PATCH 2/5] fix(gnu.org/gcc): drop runtime glibc dep, keep opportunistic wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime dep on gnu.org/glibc poisoned gcc's own bootstrap build: brewkit exposes runtime deps via CPATH/LIBRARY_PATH during build too, so configure-time test programs end up compiling against pkgx libc 2.34 but running on the host's ld-linux — and ld-linux finds host libc 2.35 in the default path even with LD_LIBRARY_PATH set, producing `cannot run C++ compiled programs`. The wrapper itself doesn't need the dep declared — it does a runtime sibling lookup (`bindir/../../../glibc/v*`) that no-ops if no pkgx glibc is present. Users who want the bottled libc explicitly add it: pkgx +gnu.org/gcc +gnu.org/glibc gcc test.c This trades automatic resolution for a clean bootstrap. Closing this trade-off the "right" way needs a pkgx feature to separate runtime-only deps from build-time-injected deps. Co-Authored-By: Claude Opus 4.7 --- projects/gnu.org/gcc/package.yml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/projects/gnu.org/gcc/package.yml b/projects/gnu.org/gcc/package.yml index 6894edf4b0..72a982ae11 100644 --- a/projects/gnu.org/gcc/package.yml +++ b/projects/gnu.org/gcc/package.yml @@ -26,16 +26,6 @@ dependencies: gnu.org/mpfr: ">=2.4.0" gnu.org/mpc: ">=0.8.0" zlib.net: ^1.3 - linux: - # Bottle the libc the compiler can target so end-user invocations - # find stdlib.h + crt*.o + libc.so.6 without distro glibc-devel. - # See #8423. <2.38 avoids C23 symbol redirects that the host's - # older glibc lacks. The thin wrapper in build.script injects - # -isystem/-L at user-invocation time (NOT at gcc's own build), - # so the bootstrap loop is unaffected. The wrapper no-ops when - # CPATH is already set (brewkit build context). - gnu.org/glibc: "<2.38" - kernel.org/linux-headers: "*" darwin/x86-64: # since 15.1.0 libisl.sourceforge.io: ^0 @@ -176,10 +166,18 @@ build: # Linux libc-wrapper: install a thin shim at bin/ that # injects -isystem $glibc/include + -L$glibc/lib so end-user - # `pkgx gcc test.c` finds stdlib.h + crt*.o + libc.so.6 from the - # pkgx-integrated glibc bottle — without needing distro glibc-devel. - # The wrapper is INERT when CPATH is already populated (brewkit build - # context), so the gcc bootstrap loop is unaffected. See #8423. + # `pkgx +gnu.org/gcc +gnu.org/glibc gcc test.c` finds stdlib.h + + # crt*.o + libc.so.6 from the pkgx-integrated glibc bottle — + # without needing distro glibc-devel. See #8423. + # + # The wrapper is OPPORTUNISTIC: it looks for a sibling pkgx glibc + # bottle and injects only if found AND CPATH is unset. If neither + # condition holds, gcc falls back to the host libc (current + # behaviour). We intentionally do NOT declare gnu.org/glibc as a + # runtime dep here — brewkit would then expose it during gcc's own + # build via CPATH/LIBRARY_PATH, poisoning the bootstrap (test + # programs end up linking pkgx libc but running on host's ld-linux). + # Users opt in explicitly with `pkgx +gnu.org/glibc`. - run: | mkdir -p {{prefix}}/libexec/gcc-wrap cat > {{prefix}}/libexec/gcc-wrap.sh <<'WRAPPER' From 2c260c51d0eb044c238fe9fa14ea06f727528581 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 20:13:55 +0200 Subject: [PATCH 3/5] fix(gnu.org/gcc): wrapper also embeds pkgx ld-linux as PT_INTERP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the wrapper detects a sibling pkgx glibc bottle, it now also passes: -Wl,--dynamic-linker=$glibc/lib/ld-linux-*.so.* -Wl,-rpath,$glibc/lib so the produced binary's ELF interpreter (PT_INTERP) points at pkgx ld-linux. End-to-end consistency: the same libc is used at compile, link, and exec. Without this, the binary's PT_INTERP would be the host's /lib64/ld-linux-x86-64.so.2 and the kernel would load host ld-linux at exec time — even with -L pointing at pkgx libc and rpath set — mixing libc internal layouts across host/pkgx. Co-Authored-By: Claude Opus 4.7 --- projects/gnu.org/gcc/package.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/projects/gnu.org/gcc/package.yml b/projects/gnu.org/gcc/package.yml index 72a982ae11..297e5d48d1 100644 --- a/projects/gnu.org/gcc/package.yml +++ b/projects/gnu.org/gcc/package.yml @@ -200,7 +200,19 @@ build: done fi if [ -n "$libc" ]; then - exec "$real" -isystem "$libc/include" -L"$libc/lib" "$@" + # Pin the pkgx ld-linux as the binary's ELF interpreter and + # rpath the libc dir, so the produced binary is consistent + # end-to-end (compile / link / load all against pkgx glibc). + # Without this, the binary would carry the host's interp path + # in PT_INTERP and the kernel would load host ld-linux at + # exec time, mixing libc versions. + ldso=$(ls "$libc"/lib/ld-linux*.so.* 2>/dev/null | head -n1) + exec "$real" \ + -isystem "$libc/include" \ + -L"$libc/lib" \ + ${ldso:+-Wl,--dynamic-linker="$ldso"} \ + -Wl,-rpath,"$libc/lib" \ + "$@" else exec "$real" "$@" fi From 4977d5c063807471730488537647393c5e64ff4e Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 20:23:03 +0200 Subject: [PATCH 4/5] fix(gnu.org/gcc): wrapper drops host include path (Nix cc-wrapper style) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a pkgx glibc sibling is found, wrapper now also passes -nostdinc (and -nostdinc++ for g++/c++) to drop /usr/include from gcc's search path, then re-adds: - gcc's own builtin headers (lib/gcc///include[+-fixed]) - libstdc++ headers for C++ tools (include/c++/[/]) - pkgx glibc headers via -isystem Without -nostdinc, gcc still found /usr/include/stdlib.h first because -isystem only appends to the search chain — so pkgx-glibc headers were shadowed by whatever the host had. Modeled on Nix's cc-wrapper.sh (pkgs/build-support/cc-wrapper/). See #8423. The wrapper stays opportunistic (CPATH unset + sibling glibc), so the gcc bootstrap loop is unaffected. Co-Authored-By: Claude Opus 4.7 --- projects/gnu.org/gcc/package.yml | 64 ++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/projects/gnu.org/gcc/package.yml b/projects/gnu.org/gcc/package.yml index 297e5d48d1..dae1823de3 100644 --- a/projects/gnu.org/gcc/package.yml +++ b/projects/gnu.org/gcc/package.yml @@ -182,10 +182,18 @@ build: mkdir -p {{prefix}}/libexec/gcc-wrap cat > {{prefix}}/libexec/gcc-wrap.sh <<'WRAPPER' #!/bin/sh - # pkgx libc-wrapper around the real compiler. Resolves the sibling - # gnu.org/glibc bottle relative to its own install path and adds - # -isystem/-L only when the caller hasn't already set up CPATH - # (i.e. only for naked end-user invocations). + # pkgx libc-wrapper modeled on Nix's cc-wrapper. Activates only + # when a sibling pkgx glibc bottle is present and CPATH is unset + # (i.e. naked end-user invocations like `pkgx +gnu.org/gcc + # +gnu.org/glibc gcc test.c`). Otherwise no-ops. + # + # When active: + # -nostdinc drop host /usr/include from search path + # -isystem $gcc/... re-add gcc's own builtin headers + # -isystem $glibc/ add pkgx glibc headers (replaces host) + # -L $glibc/lib link against pkgx libc + crt*.o + # --dynamic-linker bake pkgx ld-linux into PT_INTERP + # -rpath $glibc/lib resolve dyn libs without LD_LIBRARY_PATH self_name=$(basename "$0") case "$self_name" in cc) target=gcc ;; @@ -193,29 +201,45 @@ build: *) target="$self_name" ;; esac bindir=$(cd "$(dirname "$0")" && pwd) - real="$bindir/../libexec/gcc-wrap/$target" + gcc_root="$bindir/.." + real="$gcc_root/libexec/gcc-wrap/$target" if [ -z "$CPATH" ]; then for d in "$bindir/../../../glibc/v"*; do [ -d "$d/include" ] && libc="$d" && break done fi - if [ -n "$libc" ]; then - # Pin the pkgx ld-linux as the binary's ELF interpreter and - # rpath the libc dir, so the produced binary is consistent - # end-to-end (compile / link / load all against pkgx glibc). - # Without this, the binary would carry the host's interp path - # in PT_INTERP and the kernel would load host ld-linux at - # exec time, mixing libc versions. - ldso=$(ls "$libc"/lib/ld-linux*.so.* 2>/dev/null | head -n1) - exec "$real" \ - -isystem "$libc/include" \ - -L"$libc/lib" \ - ${ldso:+-Wl,--dynamic-linker="$ldso"} \ - -Wl,-rpath,"$libc/lib" \ - "$@" - else + if [ -z "$libc" ]; then exec "$real" "$@" fi + # Find gcc's own builtin headers (triplet + version glob) + for inc in "$gcc_root"/lib/gcc/*/*/include; do + [ -d "$inc" ] && gcc_inc="$inc" && gcc_inc_fixed="${inc}-fixed" && break + done + # For C++ tools, also re-add libstdc++ headers via -isystem + case "$target" in + g++|c++) + cxx_flag=-nostdinc++ + for cxx_inc in "$gcc_root"/include/c++/*; do + [ -d "$cxx_inc" ] && gcc_cxx_inc="$cxx_inc" && break + done + for cxx_arch_inc in "$gcc_cxx_inc"/*-linux-gnu; do + [ -d "$cxx_arch_inc" ] && gcc_cxx_arch_inc="$cxx_arch_inc" && break + done + ;; + *) cxx_flag= ;; + esac + ldso=$(ls "$libc"/lib/ld-linux*.so.* 2>/dev/null | head -n1) + exec "$real" \ + -nostdinc $cxx_flag \ + ${gcc_inc:+-isystem "$gcc_inc"} \ + ${gcc_inc_fixed:+-isystem "$gcc_inc_fixed"} \ + ${gcc_cxx_inc:+-isystem "$gcc_cxx_inc"} \ + ${gcc_cxx_arch_inc:+-isystem "$gcc_cxx_arch_inc"} \ + -isystem "$libc/include" \ + -L"$libc/lib" \ + ${ldso:+-Wl,--dynamic-linker="$ldso"} \ + -Wl,-rpath,"$libc/lib" \ + "$@" WRAPPER chmod +x {{prefix}}/libexec/gcc-wrap.sh cd {{prefix}}/bin From 72122da601fadf9d6c2e60cc417340ad118e5ea4 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 29 May 2026 21:20:58 +0200 Subject: [PATCH 5/5] fix(gcc-wrapper): keep real binary in bin/ so cc1 lookup resolves The previous shape moved the real gcc binary to libexec/gcc-wrap/, which broke gcc's relative lookup for cc1 / cc1plus / collect2 (gcc expects them at lib/gcc/// relative to its OWN location). Result: `cannot execute 'cc1': No such file or directory` during fixincludes at make-install time. Keep the real binary at bin/.${tool}-real (hidden by leading dot to avoid cluttering bin/) and have bin/ point at the wrapper. cc1 lookup then resolves the same way it always did. Co-Authored-By: Claude Opus 4.7 --- projects/gnu.org/gcc/package.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/gnu.org/gcc/package.yml b/projects/gnu.org/gcc/package.yml index dae1823de3..44575c2b1d 100644 --- a/projects/gnu.org/gcc/package.yml +++ b/projects/gnu.org/gcc/package.yml @@ -202,7 +202,11 @@ build: esac bindir=$(cd "$(dirname "$0")" && pwd) gcc_root="$bindir/.." - real="$gcc_root/libexec/gcc-wrap/$target" + # IMPORTANT: keep the real binary in bin/ (as .${target}-real) so + # gcc's internal lookup for cc1 / cc1plus / collect2 (all relative + # to its own dir at lib/gcc///) still resolves. + # Moving it under libexec/ broke that lookup (#13094 v1). + real="$bindir/.${target}-real" if [ -z "$CPATH" ]; then for d in "$bindir/../../../glibc/v"*; do [ -d "$d/include" ] && libc="$d" && break @@ -245,7 +249,7 @@ build: cd {{prefix}}/bin for tool in gcc g++ cpp c++ gfortran; do [ -f "$tool" ] && [ ! -L "$tool" ] || continue - mv "$tool" ../libexec/gcc-wrap/ + mv "$tool" ".${tool}-real" ln -sf ../libexec/gcc-wrap.sh "$tool" done if: linux