- `gccNGPackages.libbacktrace`: Remove, in favor of the `libbacktrace` package in `pkgs/by-name` - `stdenvNoCxx`: New, a compiler with a libc but no C++ standard library - `libbacktrace`: Build with `stdenvNoCxx`, so `libstdc++` can link it without a cycle - `gccNGPackages.gcc`, `gccNGPackages.libgfortran`, `gccNGPackages.libstdcxx`: Take `libbacktrace` as a build input and pass `--with-system-libbacktrace` That option is not upstream; it comes from a patch posted to gcc-patches[^1], which we fetch from the archive rather than vendor. One posting covers every component that links libbacktrace, so each package filters it down to the files its own `src` carries, and leaves out the generated files because we regenerate those locally anyway. `gcc` and `libgfortran` were already using this `libbacktrace`, but by faking up the sibling directory an in-tree build would have had -- archive, libtool archive and headers -- for the relative paths to resolve against. Passing the flag lets that go. Note that this means our libstdc++ is getting `std::stacktrace` support for the first time; we were unwittingly excluding the vendored support. Nothing failed, which is why it went unnoticed: `--enable-libstdcxx-backtrace` defaults to `auto`, and `auto` means yes wherever the library is hosted, so the feature was asked for. But the probe that picks the object format runs `libbacktrace/filetype.awk`, which was not among the files we copied, so it got nothing back, warned "could not determine output file type", and quietly settled on `enable_libstdcxx_backtrace=no`. Note also that with this approach, a downstream `libbacktrace` *cannot* be built by the regular `stdenv` with `useGccNG = true`, because its symbols would conflict. We'll see whether that is acceptable or not. If it isn't, we can still avoid vendoring, but we will need to replicate the symbol renaming that GCC would have done externally. [^1]: https://inbox.sourceware.org/gcc-patches/20260814013206.3818461-1-git@JohnEricson.me/ Assisted-by: Claude Code (Claude Opus 5)
GCC Next-Generation
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 libgccs 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.