Gccng misc fixes (#551313)

This commit is contained in:
John Ericson
2026-08-12 21:47:27 +00:00
committed by GitHub
20 changed files with 657 additions and 174 deletions

View File

@@ -153,6 +153,16 @@ let
# Derived meta-data
useLLVM = final.isFreeBSD || final.isOpenBSD;
# Use the split GCC package set (`gccNGPackages`) instead of the
# monolithic `gcc`. No platform selects it yet; it is opt-in, set
# explicitly on a platform spec, so that the split set can be exercised
# before anything depends on it.
#
# I (@Ericson2314) plan on making obscure low-tier platforms (e.g.
# NetBSD) use it soon, so we can dogfood GCC NG and thereby iron out its
# bugs.
useGccNG = false;
libc =
if final.isDarwin then
"libSystem"

View File

@@ -804,9 +804,15 @@ stdenvNoCC.mkDerivation {
include -cxx-isystem "${getDev libcxx}/include/c++/v1" >> $out/nix-support/libcxx-cxxflags
echo "-stdlib=libc++" >> $out/nix-support/libcxx-ldflags
''
# GCC NG friendly libc++
# This is the GCC NG case, libstdc++ is being built as a separate package.
#
# Point at `include-cxx`, not `include`. `libcxx` is also a propagated
# target-target dep, so the generic setup hook puts its `include` on the *C*
# include path -- and libstdc++ ships headers named after C headers
# (`math.h`, `stdlib.h`, `stdckdint.h`, ...) that are only meant to shadow
# the C ones in C++. A sibling directory the setup hook ignores is enough.
+ optionalString (libcxx != null && libcxx.isGNU or false) ''
include -isystem "${getDev libcxx}/include" >> $out/nix-support/libcxx-cxxflags
include -isystem "${getDev libcxx}/include-cxx" >> $out/nix-support/libcxx-cxxflags
''
##

View File

@@ -174,7 +174,14 @@ stdenv.mkDerivation (finalAttrs: {
install -D ${tree_h} $dev/include/sys/tree.h
'';
passthru.linuxHeaders = linuxHeaders;
passthru = {
linuxHeaders = linuxHeaders;
# musl's threads are POSIX threads. `libgcc` and `libstdc++` have to be
# configured for the same threading model as each other, so rather than
# have each guess, they take it from the libc they are built against.
threadModel = "posix";
};
meta = {
description = "Efficient, small, quality libc implementation";

View File

@@ -1,9 +1,21 @@
From db427c55334dd2edc11397d3a92d55dc9c06d1c3 Mon Sep 17 00:00:00 2001
From: John Ericson <git@JohnEricson.me>
Date: Sun, 20 Jul 2025 14:20:00 -0400
Subject: [PATCH] libstdc++: Force regular include/lib dir
Subject: [PATCH] libstdc++: Force regular lib dir, headers in include-cxx
Delete a bunch of unneeded logic to do this.
The library goes straight in $(libdir), with no version-specific or
target-specific subdirectory.
The headers go in $(includedir)-cxx rather than $(includedir). libstdc++
intentionally ships headers named after C headers -- `math.h`, `stdlib.h`,
`complex.h`, `stdckdint.h` and friends -- whose whole purpose is to shadow
the C ones when compiling C++. Installed into the ordinary include dir they
shadow them when compiling *C* too, because the generic setup hook puts that
directory on the C include path. A sibling directory keeps them out of C's
way while staying findable for C++; no version or target subdirectory is
needed, since the store path already separates one libstdc++ from another.
---
libstdc++-v3/acinclude.m4 | 80 ++------------------------------
libstdc++-v3/include/Makefile.am | 2 +-
@@ -21,7 +33,7 @@ index a0094c2dd95..a0718dff394 100644
- glibcxx_toolexeclibdir=no
+ glibcxx_toolexecdir='$(libdir)'
+ glibcxx_toolexeclibdir='$(libdir)'
+ gxx_include_dir='$(includedir)'
+ gxx_include_dir='$(includedir)-cxx'
glibcxx_prefixdir=$prefix
- AC_MSG_CHECKING([for gxx-include-dir])

View File

@@ -1,3 +1,58 @@
# GCC Next-Generation
Experimental split GCC package set based on the LLVM package set design.
Experimental split GCC package set, based on the LLVM package set design.
The monolithic `gcc` derivation builds the compiler and every runtime library in one go, so a change to the target libc rebuilds the compiler too.
This set separates them — `gcc`, `libgcc`, `libstdcxx` and the rest are individual packages — so the compiler stops depending on the libc, and each piece can be rebuilt on its own.
Because GCC is not a multi-target compiler — a single target is baked into the binary with CPP — we still need to rebuild it more than we do LLVM, but someday that should change.
A platform opts in with `useGccNG`, in the same way it would opt into `useLLVM`.
## The bootstrap chain
The libc and libgcc depend on each other: a libc's own sources call into libgcc for integer and floating-point helpers and for stack unwinding, and a libgcc that can use the libc's threads needs the libc.
The way out we currently use is the same one the LLVM set takes with `compiler-rt-no-libc` and `compiler-rt-libc` — build the runtime twice, either side of the libc.
It is unclear whether this works in general to resolve the circularity, but we shall see.
That gives four compilers, each one step further along:
| compiler | libc | libgcc | used to build |
|---|---|---|---|
| `gccNoLibgcc` | headers, or nothing | — | `libgcc-no-libc` |
| `gccWithLibgcc` | headers, or nothing | `libgcc-no-libc` | the libc |
| `gccWithLibcAndBasicLibgcc` | real | `libgcc-no-libc` | `libgcc-libc` |
| `gccWithLibc` / `gcc` | real | `libgcc-libc` | everything else |
`libgcc` resolves to `libgcc-libc` wherever a libc exists; only those first three stages ever see `libgcc-no-libc`.
The bootstrap one is single-threaded and compiled against the libc's headers at best, so it is not intended for use beyond building the libc.
Nothing is passed down to say which stage is which.
It follows from the compiler: each package reads `stdenv.cc.libc` and needs no flag of its own.
That is also how the two `libgcc`s differ — same expression, different `stdenv`.
## Where the pre-libc stage is written down
`binutilsNoLibc` carries `preLibcHeaders` as its `libc`: the header-only stand-in for platforms that have one, and nothing at all for platforms that do not.
`wrapCCWith` defaults `libc` to `bintools.libc`, so the bootstrap compilers inherit it, and everything built with them reads `stdenv.cc.libc`.
Setting a sysroot as well is unusual for nixpkgs, since headers normally reach a compiler through the wrapper, as they do here.
It is needed because `gcc/configure` takes `target_header_dir` from `--with-sysroot`, and `target_header_dir` is what decides `inhibit_libc`.
A libgcc built with `inhibit_libc` still compiles, links and installs, just with pieces silently missing.
Given that, the sysroot is derived from `stdenv.cc.libc` too, so it cannot disagree with the headers.
## Threading
Which threading model is available is a property of the libc, so the libc declares it as `passthru.threadModel`; `libgcc` reads it from there, and `libstdcxx` takes both the model and the generated `gthr-default.h` from `libgcc`.
Reading it from the compiler instead, with `$CC -v | sed -n 's/^Thread model: //p'`, reports the wrong component: in this set the compiler is configured separately from libgcc, so the two can disagree.
A platform whose libc declares nothing gets `single`.
## Relationship to the monolithic set
Both are packaged from the same sources and, for now, the same version: `gccNGPackages` tracks `default-gcc-version` with no fallback, so bumping the monolithic default past what is packaged here is an evaluation error rather than a silent version skew.
Nothing selects GGN NG yet by default.
The plan is for very exotic package sets to switch to this first.
The main tier-1 native Linux package sets cached on `cache.nixos.org` will come later.

View File

@@ -131,6 +131,7 @@ makeScopeWithSplicing' {
"-B${targetGccPackages.libssp}/lib"
"-B${targetGccPackages.libatomic}/lib"
"-B${targetGccPackages.libgomp}/lib"
"-B${targetGccPackages.libstdcxx}/lib"
"-B${targetGccPackages.libgfortran}/lib/"
];
};
@@ -163,10 +164,20 @@ makeScopeWithSplicing' {
"-B${targetGccPackages.libssp}/lib"
"-B${targetGccPackages.libatomic}/lib"
"-B${targetGccPackages.libgomp}/lib"
# `libcxx` above tells cc-wrapper where the C++ *headers* are; it does
# not put the library itself on the link path for a GNU compiler. So
# every C++ link failed with `cannot find -lstdc++` until this was
# added, in the same style as the other runtime libraries.
"-B${targetGccPackages.libstdcxx}/lib"
"-I${targetGccPackages.libgomp}/lib/gcc/${metadata.release_version}/include"
];
};
# Stage 1 of the bootstrap chain; see ../README.md.
#
# No `libc` is passed: `wrapCCWith` defaults it to `bintools.libc`, and
# `binutilsNoLibc` carries `preLibcHeaders`. That is the only place the
# pre-libc stage is written down.
gccNoLibgcc = wrapCCWith {
cc = gccPackages.gcc-unwrapped;
libcxx = null;
@@ -177,10 +188,51 @@ makeScopeWithSplicing' {
];
};
libgcc = callPackage ./libgcc {
# Built before there is a libc, and not intended for use beyond getting
# one built. Note the two differ only by `stdenv`: which stage this is
# follows from the compiler, never from an argument.
libgcc-no-libc = callPackage ./libgcc {
stdenv = overrideCC stdenv buildGccPackages.gccNoLibgcc;
};
# The real one, built against the finished libc, so it can use that
# libc's threads. This is what everything above the libc gets.
libgcc-libc = callPackage ./libgcc {
stdenv = overrideCC stdenv buildGccPackages.gccWithLibcAndBasicLibgcc;
};
libgcc =
if stdenv.hostPlatform.libc == null then gccPackages.libgcc-no-libc else gccPackages.libgcc-libc;
# Stage 2: libgcc available, libc not yet — what compiling a libc needs.
# `binutilsNoLibc` is what keeps the libc out, so nothing here refers to
# a libc derivation and the cycle stays broken.
gccWithLibgcc = wrapCCWith {
cc = gccPackages.gcc-unwrapped;
libcxx = null;
bintools = binutilsNoLibc;
extraPackages = [
targetGccPackages.libgcc-no-libc
];
nixSupport.cc-cflags = [
"-B${targetGccPackages.libgcc-no-libc}/lib"
];
};
# Stage 3: real libc, bootstrap libgcc still. The finished libgcc is what
# this is about to build.
gccWithLibcAndBasicLibgcc = wrapCCWith {
cc = gccPackages.gcc-unwrapped;
libcxx = null;
bintools = binutils;
extraPackages = [
targetGccPackages.libgcc-no-libc
];
nixSupport.cc-cflags = [
"-B${targetGccPackages.libgcc-no-libc}/lib"
];
};
gccWithLibc = wrapCCWith {
cc = gccPackages.gcc-unwrapped;
libcxx = null;

View File

@@ -27,11 +27,18 @@
texinfo,
which,
gettext,
flex,
bison,
# Whether `monorepoSrc` is a VCS checkout rather than a release tarball. A
# checkout lacks the generated sources (gengtype-lex.cc and friends) that a
# tarball ships pre-built, so they have to be regenerated with flex and bison.
fromVCS ? false,
getVersionFile,
buildGccPackages,
targetPackages,
libc,
bintools,
# Build the shared runtime libraries, and so have the driver's specs emit
# `-lgcc_s`. Derived the way the monolithic build derives it.
enableTargetShared ? stdenv.targetPlatform.hasSharedLibraries,
}:
let
inherit (stdenv) targetPlatform hostPlatform;
@@ -81,6 +88,36 @@ stdenv.mkDerivation (finalAttrs: {
hash = "sha256-54/HzM+aeWq8CTkQu8Pualqc/LgRLS0+8EY8uPUsD+s=";
})
# Not upstream yet; a follow-up to the series above (drop the `/raw` to
# read them). They extend that series' `<target>-as` preference to `PATH`,
# where we put the cross toolchain, so a cross compiler finds its tools
# the way a native one does. See below for the problems `--with-as` and
# `--with-ld` cause, and thus why we want to avoid them.
(fetchpatch {
name = "driver-factor-out-env-path-parsing.patch";
url = "https://inbox.sourceware.org/gcc-patches/20260810065714.2215299-1-git@JohnEricson.me/raw";
hash = "sha256-2qUUMWuyxX4mVaBPeNnHIiMl/aN7ejWM5stTSFWxD7g=";
})
(fetchpatch {
name = "driver-search-PATH-ourselves.patch";
url = "https://inbox.sourceware.org/gcc-patches/20260810065714.2215299-2-git@JohnEricson.me/raw";
# The posted patch is against trunk, which spells this cast with the C++
# operator. GCC 15 still uses the CONST_CAST macro, and the line is
# context rather than a change, so it cannot fuzz-match. Rewrite it
# here rather than keeping a forked copy of the whole patch.
postFetch = ''
substituteInPlace "$out" \
--replace-fail 'string, const_cast<char **> (commands[i].argv),' \
'string, CONST_CAST (char **, commands[i].argv),'
'';
hash = "sha256-uD8xJxQus2qyNgNDN/63WnURNuUJFDkhaXPph7g/DIk=";
})
(fetchpatch {
name = "driver-search-PATH-machine-prefix.patch";
url = "https://inbox.sourceware.org/gcc-patches/20260810065714.2215299-3-git@JohnEricson.me/raw";
hash = "sha256-Q5CJpJKD11kadIKselQdHgNe26GqojpyAAmlAyHnsB0=";
})
(getVersionFile "gcc/fix-collect2-paths.diff")
];
@@ -93,12 +130,39 @@ stdenv.mkDerivation (finalAttrs: {
strictDeps = true;
depsBuildBuild = [ buildPackages.stdenv.cc ];
# The target assembler and linker have to be *runnable here*, during
# configure. GCC probes them for capabilities, and a probe it cannot run is
# not an error -- it silently records "no". Without the target `ld` on
# `PATH`, every `gcc_cv_ld_*` probe fails that way, and the two that matter
# most are `HAVE_GAS_HIDDEN` (which needs `gcc_cv_as_hidden` *and*
# `gcc_cv_ld_hidden`) and `HAVE_LD_EH_FRAME_HDR`.
#
# Losing `HAVE_GAS_HIDDEN` is the nasty one: `-fvisibility=hidden` degrades
# into a no-op that merely warns, so every symbol stays preemptible and GCC's
# own same-translation-unit `R_X86_64_PC32` references become invalid when
# linking a shared library.
#
# The *unwrapped* bintools: `as` and `ld` proper carry no target-libc
# reference, so the decoupling this package set exists for still holds.
#
# Note this is `PATH` rather than `--with-as`/`--with-ld` on purpose. We want
# configure to *ask* the real tools what they support, and we want it to bake
# nothing else: those flags record `DEFAULT_ASSEMBLER`/`DEFAULT_LINKER` as
# absolute store paths, and the driver then runs exactly those binaries
# instead of the wrapped ones it is meant to defer to.
depsBuildTarget = [ (bintools.bintools or bintools) ];
nativeBuildInputs = [
texinfo
which
gettext
]
++ lib.optional (perl != null) perl;
++ lib.optional (perl != null) perl
++ lib.optionals fromVCS [
flex
bison
];
buildInputs = [
gmp
@@ -197,7 +261,11 @@ stdenv.mkDerivation (finalAttrs: {
"--disable-install-libiberty"
"--disable-multilib"
"--disable-nls"
"--disable-shared"
# Derived rather than forced off: the driver's specs only emit `-lgcc_s`
# for a target that has shared libraries, so hardcoding this leaves every
# throwing C++ program unlinkable even though `libgcc_s.so` is built and
# findable. Same predicate the monolithic build uses.
(lib.enableFeature enableTargetShared "shared")
"--enable-default-pie"
"--enable-languages=${
lib.concatStrings (
@@ -218,12 +286,26 @@ stdenv.mkDerivation (finalAttrs: {
"--without-headers"
"--with-gnu-as"
"--with-gnu-ld"
"--with-as=${lib.getExe' bintools "${bintools.targetPrefix}as"}"
# Deliberately no `--with-as` / `--with-ld`. Those bake
# `DEFAULT_ASSEMBLER` and `DEFAULT_LINKER` -- absolute store paths -- into
# the compiler, so the driver runs exactly those binaries and stops
# deferring to the wrapped tools it is meant to use.
#
# Nothing has to be baked. At configure time the tools are on `PATH` via
# `depsBuildTarget`, which is what the capability probes need; at use time
# the driver finds them on `PATH` under their target-prefixed names, via
# the `find_a_program` patches above.
"--with-system-zlib"
"--without-included-gettext"
"--enable-linker-build-id"
"--with-sysroot=${lib.getDev (targetPackages.libc or libc)}"
"--with-native-system-header-dir=/include"
# Deliberately *no* `--with-sysroot` / `--with-native-system-header-dir`
# pointing at the target libc. Baking a libc store path into the compiler
# makes every libc change rebuild the compiler, which is precisely the
# coupling this split package set exists to remove. cc-wrapper already
# supplies the target libc (`-idirafter <libc.dev>/include` and the
# corresponding `-B`/`-L` flags), so the compiler proper does not need to
# know about it -- exactly as in the LLVM package set, where `clang`
# likewise carries no libc reference (`--without-headers` above).
]
++ lib.optionals enablePlugin [
"--enable-plugin"
@@ -236,6 +318,20 @@ stdenv.mkDerivation (finalAttrs: {
"--with-multilib-list="
];
# `LIMITS_H_TEST` decides whether gcc's generated `syslimits.h` chains to the
# target libc's `limits.h` (`#include_next`) or is emitted self-contained. It
# defaults to a `[ -f $(BUILD_SYSTEM_HEADER_DIR)/limits.h ]` probe, which
# necessarily fails here: we deliberately do not point the compiler at a
# sysroot (see `configureFlags`), so there is no libc for it to find at build
# time.
#
# Self-contained is the wrong answer regardless. Every target in this package
# set is hosted, and cc-wrapper always supplies a libc, so the chained header
# is what resolves correctly at *use* time. Without it, anything the libc's
# `limits.h` defines and gcc's does not -- `PATH_MAX` being the common one --
# goes missing from every libgcc source that needs it.
makeFlags = [ "LIMITS_H_TEST=true" ];
doCheck = false;
postInstall = ''

View File

@@ -14,29 +14,34 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libatomic";
inherit version;
src = runCommand "libatomic-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libatomic-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r libatomic "$out"
cp -r libatomic "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r libtool.m4 "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r libtool.m4 "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
patches = [
(fetchpatch {

View File

@@ -11,28 +11,33 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libbacktrace";
inherit version;
src = runCommand "libbacktrace-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libbacktrace-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r include "$out"
cp -r libbacktrace "$out"
cp -r include "$out"
cp -r libbacktrace "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp move-if-change "$out"
cp mkinstalldirs "$out"
cp test-driver "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp move-if-change "$out"
cp mkinstalldirs "$out"
cp test-driver "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
outputs = [
"out"

View File

@@ -12,9 +12,37 @@
buildPackages,
which,
python3,
# Build `libgcc_s` as well as `libgcc.a`, deriving it the way the monolithic
# build does rather than forcing it off.
enableShared ? stdenv.hostPlatform.hasSharedLibraries,
}:
let
# A libc needs libgcc to build, and a libgcc that can use the libc's threads
# needs the libc, so this package is instantiated twice — see
# `libgcc-no-libc` and `libgcc-libc` in the package set, the same split the
# LLVM package set makes with `compiler-rt-no-libc` and `compiler-rt-libc`.
#
# Nothing here says which of the two it is. The difference is entirely in the
# compiler it is handed: the bootstrap wrapper's `libc` is `preLibcHeaders`
# (or nothing at all, on platforms without one), the later wrapper's is the
# finished libc. Everything below reads that one value.
libc = stdenv.cc.libc or null;
# Which threading model may be used is decided by the libc, so take it from
# there rather than guess — and pass it on in `passthru` so that `libstdcxx`,
# which has to agree, reads the same answer instead of probing for its own.
#
# This is what makes the bootstrap build single-threaded without being told
# to be: a headers-only package declares no `threadModel`, and a real libc
# does. That build exists only to get the libc built and is thrown away
# afterwards, so there is nothing to be gained from threading it, and plenty
# to go wrong: `gthr-posix.h` includes `<pthread.h>` unconditionally, which
# at that point is either absent or a stand-in for a libc that does not exist
# yet.
threadModel = if libc == null then "single" else libc.threadModel or "single";
in
stdenv.mkDerivation (finalAttrs: {
pname = "libgcc";
pname = "libgcc" + lib.optionalString (libc == null) "-no-libc";
inherit version;
src = monorepoSrc;
@@ -83,9 +111,37 @@ stdenv.mkDerivation (finalAttrs: {
buildRoot=$(readlink -e "./build")
'';
postPatch = ''
sourceRoot=$(readlink -e "./libgcc")
'';
postPatch =
# `SHLIB_LC` defaults to `-lc`, so the `libgcc_s.so` rule cannot link
# before libc exists. The monolithic build clobbers it for exactly this
# reason; do the same rather than giving up on shared libgcc. Runs while
# the working directory is still the monorepo root, before `sourceRoot`
# is repointed below.
lib.optionalString enableShared (
import ../../../common/libgcc-buildstuff.nix { inherit lib stdenv; }
)
# gcc's installed `limits.h` chains to the target libc's with
# `#include_next`. Where the compiler has a libc — headers-only or real —
# that resolves, and it has to: some targets build libgcc sources that need
# what only the libc header defines. Where it does not, the chain has
# nowhere to land, and
# even configure's `AC_PROG_CPP` probe fails -- it includes `<limits.h>`
# precisely because that "exists even on freestanding compilers" -- after
# which configure falls back to `/lib/cpp` and reports that as the error.
#
# Only in that case, put gcc's own `glimits.h` earlier on the include path
# for this build. That is the self-contained variant, the same file gcc
# installs when configured against no libc, so nothing is invented here.
# The compiler keeps shipping the chained header either way, which is what
# has to stay correct for everything compiled against a real libc later.
+ lib.optionalString (libc == null) ''
mkdir -p "$NIX_BUILD_TOP/freestanding-include"
cp gcc/glimits.h "$NIX_BUILD_TOP/freestanding-include/limits.h"
export NIX_CFLAGS_COMPILE="-isystem $NIX_BUILD_TOP/freestanding-include ''${NIX_CFLAGS_COMPILE-}"
''
+ ''
sourceRoot=$(readlink -e "./libgcc")
'';
enableParallelBuilding = true;
@@ -100,6 +156,35 @@ stdenv.mkDerivation (finalAttrs: {
cd "$buildRoot/gcc"
(
''
# `AS`, `CC`, `CPP` and `LD` still name the *target* tools at this point,
# under their machine-prefixed names. Snapshot them before the
# `*_FOR_BUILD` assignments below overwrite them.
#
# Deriving `AS_FOR_TARGET` from `$AS` *after* `AS=$AS_FOR_BUILD` asked for
# the basename of the build assembler -- plain `as` -- inside the target
# compiler's `bin`, and a cross wrapper installs only prefixed names, so
# the path did not exist. Likewise `ld`.
#
# That is not an error `gcc/configure` reports. It probes the target
# assembler and linker for capabilities, and a probe it cannot run simply
# records "no"; with neither tool found, *every* `gcc_cv_as_*`/`gcc_cv_ld_*`
# answer came back "no". The one that matters here is
# `HAVE_LD_EH_FRAME_HDR`: `unwind-dw2-fde-dip.c` gates `USE_PT_GNU_EH_FRAME`
# on it, so without it the unwinder is compiled with no `dl_iterate_phdr`
# lookup at all -- only the `__register_frame` registry, which nothing
# populates for normally linked objects. libgcc and libstdc++ then build,
# link and install perfectly cleanly, and every C++ `throw` finds no FDE
# and calls `std::terminate`.
#
# `CPP` in particular is not always exported, so fall back to the
# machine-prefixed name the wrappers install.
+ ''
targetAs=$(basename "''${AS:-${stdenv.hostPlatform.config}-as}")
targetCc=$(basename "''${CC:-${stdenv.hostPlatform.config}-cc}")
targetCpp=$(basename "''${CPP:-${stdenv.hostPlatform.config}-cpp}")
targetLd=$(basename "''${LD:-${stdenv.hostPlatform.config}-ld}")
export AS_FOR_BUILD=${lib.getExe' buildPackages.stdenv.cc "$(basename $AS_FOR_BUILD)"}
export CC_FOR_BUILD=${lib.getExe' buildPackages.stdenv.cc "$(basename $CC_FOR_BUILD)"}
export CPP_FOR_BUILD=${lib.getExe' buildPackages.stdenv.cc "$(basename $CPP_FOR_BUILD)"}
@@ -112,10 +197,10 @@ stdenv.mkDerivation (finalAttrs: {
export CXX=$CXX_FOR_BUILD
export LD=$LD_FOR_BUILD
export AS_FOR_TARGET=${lib.getExe' stdenv.cc "$(basename $AS)"}
export CC_FOR_TARGET=${lib.getExe' stdenv.cc "$(basename $CC)"}
export CPP_FOR_TARGET=${lib.getExe' stdenv.cc "$(basename $CPP)"}
export LD_FOR_TARGET=${lib.getExe' stdenv.cc.bintools "$(basename $LD)"}
export AS_FOR_TARGET=${lib.getExe' stdenv.cc "$targetAs"}
export CC_FOR_TARGET=${lib.getExe' stdenv.cc "$targetCc"}
export CPP_FOR_TARGET=${lib.getExe' stdenv.cc "$targetCpp"}
export LD_FOR_TARGET=${lib.getExe' stdenv.cc.bintools "$targetLd"}
export NIX_CFLAGS_COMPILE_FOR_BUILD+=' -DGENERATOR_FILE=1'
@@ -184,6 +269,21 @@ stdenv.mkDerivation (finalAttrs: {
"--with-system-zlib"
]
# `gcc/configure` sets `inhibit_libc=true` when host != target and
# `$target_header_dir/stdio.h` does not exist. `inhibit_libc` makes
# `tsystem.h` skip <unistd.h> and friends, which is fine for the generic
# sources but breaks the target-specific ones that genuinely need libc
# declarations -- the profiling support files are the usual casualty.
#
# `target_header_dir` is derived from `--with-sysroot`, *not* from
# `--with-headers`, so the sysroot pair is what has to be set. Point it at
# whichever libc the compiler carries, which in the bootstrap build is the
# headers-only one. This affects only the `gcc/configure` run that generates
# libgcc's makefile fragments, not the compiler that gets shipped.
++ lib.optionals (libc != null) [
"--with-sysroot=${lib.getDev libc}"
"--with-native-system-header-dir=/include"
]
++
lib.optional (!stdenv.hostPlatform.isRiscV)
# RISC-V does not like it being empty
@@ -202,13 +302,14 @@ stdenv.mkDerivation (finalAttrs: {
configureFlags = [
"--disable-dependency-tracking"
"gcc_cv_target_thread_file=single"
"gcc_cv_target_thread_file=${threadModel}"
# $CC cannot link binaries, let alone run then
"cross_compiling=true"
# Do not have dynamic linker without libc
"--enable-static"
"--disable-shared"
];
]
# `libgcc_s` needs no libc: it comes out with an empty `DT_NEEDED`, which is
# why the monolithic build ships one even from its nolibc stage.
++ lib.optional (!enableShared) "--disable-shared";
# Set the variable back the way it was, see corresponding code in
# `preConfigure`.
@@ -226,6 +327,7 @@ stdenv.mkDerivation (finalAttrs: {
passthru = {
isGNU = true;
inherit threadModel;
};
meta = gcc_meta // {

View File

@@ -13,30 +13,35 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libgomp";
inherit version;
src = runCommand "libgomp-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libgomp-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r libgomp "$out"
cp -r include "$out"
cp -r libgomp "$out"
cp -r include "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r libtool.m4 "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r libtool.m4 "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
outputs = [
"out"

View File

@@ -11,26 +11,31 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libiberty";
inherit version;
src = runCommand "libiberty-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libiberty-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r include "$out"
cp -r libiberty "$out"
cp -r include "$out"
cp -r libiberty "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
outputs = [
"out"

View File

@@ -11,25 +11,30 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libquadmath";
inherit version;
src = runCommand "libquadmath-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libquadmath-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r libquadmath "$out"
cp -r libquadmath "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
sourceRoot = "${finalAttrs.src.name}/libquadmath";

View File

@@ -12,25 +12,30 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libsanitizer";
inherit version;
src = runCommand "libsanitizer-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libsanitizer-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r libsanitizer "$out"
cp -r libsanitizer "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
sourceRoot = "${finalAttrs.src.name}/libsanitizer";

View File

@@ -14,28 +14,33 @@ stdenv.mkDerivation (finalAttrs: {
pname = "libssp";
inherit version;
src = runCommand "libssp-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libssp-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
cp -r libssp "$out"
cp -r libssp "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
outputs = [
"out"

View File

@@ -10,41 +10,55 @@
autoreconfHook269,
runCommand,
gettext,
libgcc,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "libstdcxx";
inherit version;
src = runCommand "libstdcxx-src-${version}" { src = monorepoSrc; } ''
runPhase unpackPhase
src = runCommand "libstdcxx-src-${version}" { src = monorepoSrc; } (
''
runPhase unpackPhase
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/gcc"
cp gcc/BASE-VER "$out/gcc"
cp gcc/DATESTAMP "$out/gcc"
mkdir -p "$out/libgcc"
cp libgcc/gthr*.h "$out/libgcc"
cp libgcc/unwind-pe.h "$out/libgcc"
mkdir -p "$out/libgcc"
cp libgcc/gthr*.h "$out/libgcc"
cp libgcc/unwind-pe.h "$out/libgcc"
cp -r libstdc++-v3 "$out"
cp -r libstdc++-v3 "$out"
cp -r libiberty "$out"
cp -r include "$out"
cp -r contrib "$out"
cp -r libiberty "$out"
cp -r include "$out"
cp -r contrib "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp -r config "$out"
cp -r multilib.am "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
cp config.guess "$out"
cp config.rpath "$out"
cp config.sub "$out"
cp config-ml.in "$out"
cp ltmain.sh "$out"
cp install-sh "$out"
cp mkinstalldirs "$out"
[[ -f MD5SUMS ]]; cp MD5SUMS "$out"
'';
''
# `src/Makefile` runs this to compute `LTLDFLAGS`, reaching out of the
# subdirectory for it as `$(top_srcdir)/../libtool-ldflags`. Missing, the
# shell says "No such file or directory" and `LTLDFLAGS` silently comes out
# empty, dropping our `LDFLAGS` from every library link.
+ ''
cp libtool-ldflags "$out"
''
# `MD5SUMS` exists only in release tarballs, not in a VCS checkout.
+ ''
if [[ -f MD5SUMS ]]; then cp MD5SUMS "$out"; fi
''
);
outputs = [
"out"
@@ -85,6 +99,59 @@ stdenv.mkDerivation (finalAttrs: {
cd "$buildRoot"
configureScript=$sourceRoot/configure
chmod +x "$configureScript"
''
# Build libstdc++ with the *C* driver, not `g++`. `g++` implies `-lstdc++`,
# which cannot be satisfied while building the very library that provides
# it, and the link fails with `cannot find -lstdc++`.
#
# This is not our invention; it is what a monolithic build does, and
# `libstdc++-v3/src/Makefile.am` says so where it defines `CXXLINK`:
#
# We cannot allow g++ to be used since this would add -lstdc++ to the
# link line which of course is problematic at this point. So, we get
# the top-level directory to configure libstdc++-v3 to use gcc as the
# C++ compilation driver.
#
# The top level does that through `RAW_CXX_FOR_TARGET`, which is `xgcc`
# -- not `xg++` -- plus `-shared-libgcc` and `-nostdinc++`. Building
# standalone there is no top level to arrange it, so arrange it here. The
# C driver still compiles `.cc` as C++ by extension; what it drops is the
# implicit `-lstdc++`. `-shared-libgcc` puts back the linkage `g++` would
# have chosen, which the C driver does not default to, and `-nostdinc++`
# keeps any already-installed C++ headers out of a build whose whole
# purpose is to produce them.
+ ''
cxxForLibstdcxx="$CC -shared-libgcc -nostdinc++"
''
# Put libgcc's `gthr-default.h` where libstdc++ expects to find it.
#
# It is not a source file. In a monolithic build libgcc's Makefile creates
# it by copying whichever `gthr-<model>.h` matches the target's thread
# model, and libstdc++ -- configured inside that same build tree -- picks
# it up. Standalone there is no such tree, so take the one `libgcc`
# installed. The two implement a single threading model between them, and
# copying libgcc's own answer is what makes them agree by construction
# rather than by two independent guesses that can drift apart. `libgcc`
# decided it from the libc; `gcc_cv_target_thread_file` below repeats the
# same value.
#
# The consequence of getting this wrong is worth spelling out, because the
# installed headers do not show it. `GLIBCXX_CHECK_GTHREADS` compiles
# `#include "gthr.h"` and requires `__GTHREADS_CXX0X`, which
# `gthr-posix.h` defines unconditionally. With no posix `gthr-default.h`
# on the include path that test fails, configure concludes there are no
# gthreads, and `_GLIBCXX_HAS_GTHREADS` is left undefined in
# `c++config.h` -- so `<mutex>` compiles away and `std::mutex` does not
# exist. Stating the intent with `--enable-threads=posix` does not help:
# the probe overrides intent with an empirical answer, so the header has
# to actually be there.
+ ''
cp ${lib.getDev libgcc}/include/gthr-default.h "$sourceRoot/../libgcc/gthr-default.h"
export CXX="$cxxForLibstdcxx"
echo "libstdcxx: building with CXX=$CXX"
'';
configurePlatforms = [
@@ -94,11 +161,19 @@ stdenv.mkDerivation (finalAttrs: {
configureFlags = [
"--disable-dependency-tracking"
"gcc_cv_target_thread_file=posix"
# The same answer `libgcc` used, and the model of the `gthr-default.h`
# copied in above. A mismatch here is silent: the unwinder's locks vanish
# while `libstdc++` still hands out `std::thread`.
"gcc_cv_target_thread_file=${libgcc.threadModel}"
"cross_compiling=true"
"--disable-multilib"
"--enable-clocale=gnu"
# `gnu` is the glibc locale model: `config/locale/gnu/ctype_members.cc`
# reads `__ctype_b`, which only glibc has. Forcing it everywhere breaks any
# other libc -- on musl the build fails converting `const unsigned short *`
# to `const ctype_base::mask *`. Configure picks the right model from the
# host triple on its own, as it does for the monolithic build, which passes
# no `--enable-clocale` at all.
"--disable-libstdcxx-pch"
"--disable-vtable-verify"
"--enable-libstdcxx-visibility"

View File

@@ -278,6 +278,11 @@ stdenv.mkDerivation (
passthru = {
inherit version;
minorRelease = version;
# glibc's threads are POSIX threads. `libgcc` and `libstdc++` have to be
# configured for the same threading model as each other, so rather than
# have each guess, they take it from the libc they are built against.
threadModel = "posix";
};
}

View File

@@ -47,5 +47,11 @@ symlinkJoin {
fixupPhase
'';
# NetBSD's threads are POSIX threads — `libpthread` is joined in above.
# `libgcc` and `libstdc++` have to be configured for the same threading model
# as each other, so rather than have each guess, they take it from the libc
# they are built against.
passthru.threadModel = "posix";
meta.platforms = lib.platforms.netbsd;
}

View File

@@ -127,6 +127,8 @@ lib.init bootStages
buildPackages.zig.cc
else if crossSystem.useArocc or false then
buildPackages.arocc
else if crossSystem.useGccNG or false then
buildPackages.gccNGPackages.gcc
else
buildPackages.gcc;

View File

@@ -88,6 +88,8 @@ with pkgs;
(
if stdenvNoCC.hostPlatform.isDarwin || stdenvNoCC.hostPlatform.useLLVM or false then
overrideCC stdenvNoCC buildPackages.llvmPackages.clangNoCompilerRt
else if stdenvNoCC.hostPlatform.useGccNG or false then
overrideCC stdenvNoCC buildPackages.gccNGPackages.gccNoLibgcc
else
gccCrossLibcStdenv
)
@@ -99,6 +101,11 @@ with pkgs;
(
if stdenvNoCC.hostPlatform.isDarwin || stdenvNoCC.hostPlatform.useLLVM or false then
overrideCC stdenvNoCC buildPackages.llvmPackages.clangNoLibc
else if stdenvNoCC.hostPlatform.useGccNG or false then
# The split package set can express the two rungs separately, the way
# the LLVM set does: no libgcc above, libgcc but no libc here. The
# monolithic `gccCrossLibcStdenv` has to serve both.
overrideCC stdenvNoCC buildPackages.gccNGPackages.gccWithLibgcc
else
gccCrossLibcStdenv
)
@@ -3244,9 +3251,11 @@ with pkgs;
# NOTE: keep this with the "NG" label until we're ready to drop the monolithic GCC
gccNGPackagesSet = recurseIntoAttrs (callPackages ../development/compilers/gcc/ng { });
gccNGPackages_15 = gccNGPackagesSet."15";
gccNGPackages = gccNGPackagesSet.${toString default-gcc-version};
mkGCCNGPackages = gccNGPackagesSet.mkPackage;
})
gccNGPackages_15
gccNGPackages
mkGCCNGPackages
;
@@ -5622,11 +5631,22 @@ with pkgs;
if stdenv.hostPlatform != stdenv.buildPlatform then
{
stdenv = gccCrossLibcStdenv; # doesn't compile without gcc
libgcc = callPackage ../development/libraries/gcc/libgcc {
gcc = gccCrossLibcStdenv.cc;
glibc = glibc.override { libgcc = null; };
stdenvNoLibs = gccCrossLibcStdenv;
};
# glibc `dlopen`s `libgcc_s.so` without consulting the usual search
# path, so it has to name one that already exists. The monolithic
# build gets there by building glibc twice: the inner `libgcc = null`
# one names no libgcc at all, and is used for nothing but building the
# libgcc the real one names. The split package set already has that
# rung as a package of its own — `libgcc-no-libc` is what exists
# before any libc does — so there is nothing left to open-code here.
libgcc =
if stdenv.hostPlatform.useGccNG or false then
gccNGPackages.libgcc-no-libc
else
callPackage ../development/libraries/gcc/libgcc {
gcc = gccCrossLibcStdenv.cc;
glibc = glibc.override { libgcc = null; };
stdenvNoLibs = gccCrossLibcStdenv;
};
}
else
{