glibcCross: use a libgcc built separately from gcc
### Summary
This PR completely and finally solves the gcc<->glibc circular
`buildInputs` problem, for cross compilation. The same technique
can be applied to native builds in the future.
Closes #213453
### Motivation
Prior to this PR, we had the following circular `buildInputs` problem:
1. gcc has glibc in its `buildInputs`
- a compiled copy of glibc must be present before building gcc;
if it isn't, gcc cripples itself (`inhibit_libc`) and refuses
to build libgcc_s.so
2. glibc has libgcc_s.so in its `buildInputs`
- glibc `dlopen()`s libgcc_s.so in order to implement POSIX
thread cancellation. For security reasons `glibc` requires
that the path to `libgcc_s.so` is [hardwired] into `glibc` at
compile time, so it's technically not a true dynamic link -- it
just pretends to be one.
3. libgcc_s.so is built in the same derivation as gcc
- libgcc_s.so is built as part of the gcc build process
We must cut one of these three links in the loop.
### Previous Attempts
Previously https://github.com/NixOS/nixpkgs/pull/238154 had
attempted to cut link (1) by building `gcc` without `glibc`, and
using the `libgcc_s` which emerges from that build. Unfortunately
this just doesn't work. GCC's configure script extracts quite a lot
of information from the glibc headers (which are a build artifact --
you can't just copy them out of the source tarball) and various
`./configure`-driven linking attempts. If `glibc` isn't around at
build time you wind up with a `libgcc_s.so` that is missing various
unwinder features (see https://github.com/NixOS/nixpkgs/issues/213453
for the most problematic one).
Musl "cuts" link (2), or rather never creates it in the first place.
["Cancellation cleanup handling in musl has no relationship to C++
exceptions and unwinding... glibc implements cancellation as an
exception"](https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-cancellation).
IMHO Musl made the smarter decision here. It is incredibly rare to
find a codebase that uses both POSIX thread cancellation *and* C++
exceptions. I have never seen a codebase that uses both *and*
expects them to be aware of each other, and I would be astonished if
one existed. Glibc paid an immense cost in complexity for something
nobody has ever used.
### Changes Made
This PR cuts link (3): instead of building libgcc_s.so as part of
gcc, we build it separately from gcc. Now there is a strict acyclic
graph of `buildInputs`:
```
gccWithoutTargetLibc
|
+--->glibc-nolibgcc
| |
| v
+--->libgcc
| |
| v
+--->glibc
| |
| v
+--->gcc
```
In other words, there's a simple linear `buildInputs` chain
`glibc-nolibgcc` `->` `libgcc` `->` `glibc` `->` `gcc` where all
four packages are compiled by (and therefore have as a
`(native)BuildInput`) `gccWithoutTargetLibc`.
`gccWithoutTargetLibc` and `glibc-nolibgcc` are strictly
bootstrapping artifacts; nothing else has them as a `buildInput` and
they shouldn't appear in the closure of any final deployment
packages. `glibc-nolibgcc` lacks `libgcc_s.so`, so it will segfault
if you try to use it with POSIX thread cancellation. Fortunately
all we need from it is (a) its headers (`lib.getDev`) and (b) to use
it in the `./configure` script for `libgcc`.
When translated over to the native bootstrap, `xgcc` takes the place
of `gccWithoutTargetLibc`, and the "first `glibc`" (we build two of
them) takes the place of `glibc-nolibgcc`. At that point our native
and cross bootstrap have the same overall architecture, and it
becomes possible to merge them (at last!)
[213453]: https://github.com/NixOS/nixpkgs/issues/213453
[238154]: https://github.com/NixOS/nixpkgs/pull/238154
[hardwired]: 7553d0fe29/pkgs/development/libraries/glibc/default.nix (L69-L88)
This commit is contained in:
parent
18c52d09bc
commit
64046f0191
|
@ -4,6 +4,7 @@
|
||||||
, withGd ? false
|
, withGd ? false
|
||||||
, withLibcrypt? false
|
, withLibcrypt? false
|
||||||
, buildPackages
|
, buildPackages
|
||||||
|
, libgcc
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -16,7 +17,7 @@ in
|
||||||
|
|
||||||
(callPackage ./common.nix { inherit stdenv; } {
|
(callPackage ./common.nix { inherit stdenv; } {
|
||||||
inherit withLinuxHeaders withGd profilingLibraries withLibcrypt;
|
inherit withLinuxHeaders withGd profilingLibraries withLibcrypt;
|
||||||
pname = "glibc" + lib.optionalString withGd "-gd";
|
pname = "glibc" + lib.optionalString withGd "-gd" + lib.optionalString (stdenv.cc.isGNU && libgcc==null) "-nolibgcc";
|
||||||
}).overrideAttrs(previousAttrs: {
|
}).overrideAttrs(previousAttrs: {
|
||||||
|
|
||||||
# Note:
|
# Note:
|
||||||
|
@ -90,8 +91,8 @@ in
|
||||||
#
|
#
|
||||||
makeFlags =
|
makeFlags =
|
||||||
(previousAttrs.makeFlags or [])
|
(previousAttrs.makeFlags or [])
|
||||||
++ lib.optionals (stdenv.cc.cc?libgcc) [
|
++ lib.optionals (libgcc != null) [
|
||||||
"user-defined-trusted-dirs=${stdenv.cc.cc.libgcc}/lib"
|
"user-defined-trusted-dirs=${libgcc}/lib"
|
||||||
];
|
];
|
||||||
|
|
||||||
postInstall = previousAttrs.postInstall + (if stdenv.hostPlatform == stdenv.buildPlatform then ''
|
postInstall = previousAttrs.postInstall + (if stdenv.hostPlatform == stdenv.buildPlatform then ''
|
||||||
|
@ -166,8 +167,8 @@ in
|
||||||
|
|
||||||
passthru =
|
passthru =
|
||||||
(previousAttrs.passthru or {})
|
(previousAttrs.passthru or {})
|
||||||
// lib.optionalAttrs (stdenv.cc.cc?libgcc) {
|
// lib.optionalAttrs (libgcc != null) {
|
||||||
inherit (stdenv.cc.cc) libgcc;
|
inherit libgcc;
|
||||||
};
|
};
|
||||||
|
|
||||||
meta = (previousAttrs.meta or {}) // { description = "The GNU C Library"; };
|
meta = (previousAttrs.meta or {}) // { description = "The GNU C Library"; };
|
||||||
|
|
|
@ -21439,6 +21439,11 @@ with pkgs;
|
||||||
# Being redundant to avoid cycles on boot. TODO: find a better way
|
# Being redundant to avoid cycles on boot. TODO: find a better way
|
||||||
glibcCross = callPackage ../development/libraries/glibc {
|
glibcCross = callPackage ../development/libraries/glibc {
|
||||||
stdenv = gccCrossLibcStdenv; # doesn't compile without gcc
|
stdenv = gccCrossLibcStdenv; # doesn't compile without gcc
|
||||||
|
libgcc = callPackage ../development/libraries/gcc/libgcc {
|
||||||
|
gcc = gccCrossLibcStdenv.cc;
|
||||||
|
glibc = glibcCross.override { libgcc = null; };
|
||||||
|
stdenvNoLibs = gccCrossLibcStdenv;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
muslCross = musl.override {
|
muslCross = musl.override {
|
||||||
|
|
Loading…
Reference in a new issue