summary refs log tree commit diff
path: root/pkgs
diff options
context:
space:
mode:
authorAdam Joseph <adam@westernsemico.com>2023-02-23 21:32:36 -0800
committerAdam Joseph <adam@westernsemico.com>2023-04-02 13:49:41 -0700
commit7553d0fe29801938bcb280bb324b579ef9016aea (patch)
tree0e0073ae6d9a5bc8935b9a30adaeba883ca510d7 /pkgs
parentfdd49f1bcd8a7f0b5e29f550d698b2abe5c540cd (diff)
downloadnixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar.gz
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar.bz2
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar.lz
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar.xz
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.tar.zst
nixpkgs-7553d0fe29801938bcb280bb324b579ef9016aea.zip
stdenv: Nix-driven bootstrap of gcc
 #### Summary

By default, when you type `make`, GCC will compile itself three
times.  This PR inhibits that behavior by configuring GCC with
`--disable-bootstrap`, and reimplements the triple-rebuild using
Nix rather than `make`/`sh`.

 #### Immediate Benefits

- Allow `gcc11` and `gcc12` on `aarch64` (without needing new
  `bootstrapFiles`)
- Faster stdenv rebuilds: the third compilation of gcc
  (i.e. stageCompare) is no longer a `drvInput` of the final stdenv.
  This allows Nix to build stageCompare in parallel with the rest of
  nixpkgs instead of in series.
- No more copying `libgcc_s` out of the bootstrap-files or other
  derivations
- No more Frankenstein compiler: the final gcc and the libraries it
  links against (mpfr, mpc, isl, glibc) are all built by the same
  compiler (xgcc) instead of a mixture of the bootstrapFiles'
  compiler and xgcc.
- No more [static lib{mpfr,mpc,gmp,isl}.a hack]
- Many other small `stdenv` hacks eliminated
- `gcc` and `clang` share the same codepath for more of `cc-wrapper`.

 #### Future Benefits

- This should allow using a [foreign] `bootstrap-files` so long as
  `hostPlatform.canExecute bootstrapFiles`.
- This should allow each of the libraries that ship with `gcc`
  (lib{backtrace, atomic, cc1, decnumber, ffi, gomp, iberty,
  offloadatomic, quadmath, sanitizer, ssp, stdc++-v3, vtv}) to be
  built in separate (one-liner) derivations which `inherit src;`
  from `gcc`, much like https://github.com/NixOS/nixpkgs/pull/132343

 #### Incorporates

- https://github.com/NixOS/nixpkgs/pull/210004
- https://github.com/NixOS/nixpkgs/pull/36948 (unreverted)
- https://github.com/NixOS/nixpkgs/pull/210325
- https://github.com/NixOS/nixpkgs/pull/210118
- https://github.com/NixOS/nixpkgs/pull/210132
- https://github.com/NixOS/nixpkgs/pull/210109
- https://github.com/NixOS/nixpkgs/pull/213909
- https://github.com/NixOS/nixpkgs/pull/216136
- https://github.com/NixOS/nixpkgs/pull/216237
- https://github.com/NixOS/nixpkgs/pull/210019
- https://github.com/NixOS/nixpkgs/pull/216232
- https://github.com/NixOS/nixpkgs/pull/216016
- https://github.com/NixOS/nixpkgs/pull/217977
- https://github.com/NixOS/nixpkgs/pull/217995

 #### Closes

- Closes #108305
- Closes #108111
- Closes #201254
- Closes #208412

 #### Credits

This project was made possible by three important insights, none of
which were mine:

1. @ericson2314 was the first to advocate for this change, and
   probably the first to appreciate its advantages.  Nix-driven
   (external) bootstrap is "cross by default".

2. @trofi has figured out a lot about how to get gcc to not mix up
   the copy of `libstdc++` that it depends on with the copy that it
   builds, by moving the `bootstrapFiles`' `libstdc++` into a
   [versioned directory].  This allows a Nix-driven bootstrap of gcc
   without the final gcc would still having references to the
   `bootstrapFiles`.

3. Using the undocumented variable [`user-defined-trusted-dirs`]
   when building glibc.  When glibc `dlopen()`s `libgcc_s.so`, it
   uses a completely different and totally special set of rules for
   finding `libgcc_s.so`.  This trick is the only way we can put
   `libgcc_s.so` in its own separate outpath without creating
   circular dependencies or dependencies on the bootstrapFiles.  I
   would never have guessed to use this (or that it existed!) if it
   were not for a [comment in guix] which @Mic92 [mentioned].

My own role in this PR was basically: being available to go on a
coding binge at an opportune moment, so we wouldn't waste a
[crisis].

[aarch64-compare-ofborg]: https://github.com/NixOS/nixpkgs/pull/209870/checks?check_run_id=10662822938
[amd64-compare-ofborg]: https://github.com/NixOS/nixpkgs/pull/209870/checks?check_run_id=10662825857
[nonexistent sysroot]: https://github.com/NixOS/nixpkgs/pull/210004
[versioned directory]: https://github.com/NixOS/nixpkgs/pull/209054
[`user-defined-trusted-dirs`]: https://sourceware.org/legacy-ml/libc-help/2013-11/msg00026.html
[comment in guix]: https://github.com/guix-mirror/guix/blob/5e4ec8218142eee8e6e148e787381a5ef891c5b1/gnu/packages/gcc.scm#L253
[mentioned]: https://github.com/NixOS/nixpkgs/pull/210112#issuecomment-1379608483
[crisis]: https://github.com/NixOS/nixpkgs/issues/108305
[foreign]: https://github.com/NixOS/nixpkgs/pull/170857#issuecomment-1170558348
[static lib{mpfr,mpc,gmp,isl}.a hack]: https://github.com/NixOS/nixpkgs/blob/2f1948af9c984ebb82dfd618e67dc949755823e2/pkgs/stdenv/linux/default.nix#L380
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/build-support/cc-wrapper/default.nix35
-rw-r--r--pkgs/development/compilers/gcc/11/default.nix8
-rw-r--r--pkgs/development/compilers/gcc/12/default.nix3
-rw-r--r--pkgs/development/compilers/gcc/common/libgcc.nix96
-rw-r--r--pkgs/development/libraries/glibc/default.nix53
-rw-r--r--pkgs/stdenv/linux/default.nix191
6 files changed, 293 insertions, 93 deletions
diff --git a/pkgs/build-support/cc-wrapper/default.nix b/pkgs/build-support/cc-wrapper/default.nix
index f6fb7bd001c..25481fec664 100644
--- a/pkgs/build-support/cc-wrapper/default.nix
+++ b/pkgs/build-support/cc-wrapper/default.nix
@@ -17,9 +17,40 @@
 , isGNU ? false, isClang ? cc.isClang or false, gnugrep ? null
 , buildPackages ? {}
 , libcxx ? null
+
+# Whether or not to add `-B` and `-L` to `nix-support/cc-{c,ld}flags`
+, useCcForLibs ?
+
+  # Always add these flags for Clang, because in order to compile (most
+  # software) it needs libraries that are shipped and compiled with gcc.
+  if isClang then true
+
+  # Never add these flags for a build!=host cross-compiler or a host!=target
+  # ("cross-built-native") compiler; currently nixpkgs has a special build
+  # path for these (`crossStageStatic`).  Hopefully at some point that build
+  # path will be merged with this one and this conditional will be removed.
+  else if (with stdenvNoCC; buildPlatform != hostPlatform || hostPlatform != targetPlatform) then false
+
+  # Never add these flags when wrapping the bootstrapFiles' compiler; it has a
+  # /usr/-like layout with everything smashed into a single outpath, so it has
+  # no trouble finding its own libraries.
+  else if (cc.passthru.isFromBootstrapFiles or false) then false
+
+  # Add these flags when wrapping `xgcc` (the first compiler that nixpkgs builds)
+  else if (cc.passthru.isXgcc or false) then true
+
+  # Add these flags when wrapping `stdenv.cc`
+  else if (cc.stdenv.cc.cc.passthru.isXgcc or false) then true
+
+  # Do not add these flags in any other situation.  This is `false` mainly to
+  # prevent these flags from being added when wrapping *old* versions of gcc
+  # (e.g. `gcc6Stdenv`), since they will cause the old gcc to get `-B` and
+  # `-L` flags pointing at the new gcc's libstdc++ headers.  Example failure:
+  # https://hydra.nixos.org/build/213125495
+  else false
+
+# the derivation at which the `-B` and `-L` flags added by `useCcForLibs` will point
 , gccForLibs ? if useCcForLibs then cc else null
-# same as `gccForLibs`, but generalized beyond clang
-, useCcForLibs ? isClang
 }:
 
 with lib;
diff --git a/pkgs/development/compilers/gcc/11/default.nix b/pkgs/development/compilers/gcc/11/default.nix
index 6ad7ce27f25..b003a1c30fd 100644
--- a/pkgs/development/compilers/gcc/11/default.nix
+++ b/pkgs/development/compilers/gcc/11/default.nix
@@ -49,7 +49,7 @@ with builtins;
 
 let majorVersion = "11";
     version = "${majorVersion}.3.0";
-    disableBootstrap = !(with stdenv; targetPlatform == hostPlatform && hostPlatform == buildPlatform);
+    disableBootstrap = true;
 
     inherit (stdenv) buildPlatform hostPlatform targetPlatform;
 
@@ -250,9 +250,8 @@ lib.pipe (stdenv.mkDerivation ({
   targetConfig = if targetPlatform != hostPlatform then targetPlatform.config else null;
 
   buildFlags =
-    let target =
-          lib.optionalString (profiledCompiler) "profiled" +
-          lib.optionalString (targetPlatform == hostPlatform && hostPlatform == buildPlatform && !disableBootstrap) "bootstrap";
+    let target = lib.optionalString (profiledCompiler) "profiled"
+      + lib.optionalString (targetPlatform == hostPlatform && hostPlatform == buildPlatform && !disableBootstrap) "bootstrap";
     in lib.optional (target != "") target;
 
   inherit (callFile ../common/strip-attributes.nix { })
@@ -312,4 +311,5 @@ lib.pipe (stdenv.mkDerivation ({
 // optionalAttrs (enableMultilib) { dontMoveLib64 = true; }
 ))
 [
+  (callPackage ../common/libgcc.nix   { inherit langC langCC langJit; })
 ]
diff --git a/pkgs/development/compilers/gcc/12/default.nix b/pkgs/development/compilers/gcc/12/default.nix
index b0c7b431031..5c622593c24 100644
--- a/pkgs/development/compilers/gcc/12/default.nix
+++ b/pkgs/development/compilers/gcc/12/default.nix
@@ -54,7 +54,7 @@ with builtins;
 
 let majorVersion = "12";
     version = "${majorVersion}.2.0";
-    disableBootstrap = !(with stdenv; targetPlatform == hostPlatform && hostPlatform == buildPlatform);
+    disableBootstrap = true;
 
     inherit (stdenv) buildPlatform hostPlatform targetPlatform;
 
@@ -346,5 +346,6 @@ lib.pipe (stdenv.mkDerivation ({
 // optionalAttrs (enableMultilib) { dontMoveLib64 = true; }
 ))
 [
+  (callPackage ../common/libgcc.nix   { inherit langC langCC langJit; })
 ]
 
diff --git a/pkgs/development/compilers/gcc/common/libgcc.nix b/pkgs/development/compilers/gcc/common/libgcc.nix
new file mode 100644
index 00000000000..e3523936019
--- /dev/null
+++ b/pkgs/development/compilers/gcc/common/libgcc.nix
@@ -0,0 +1,96 @@
+{ lib
+, stdenv
+, langC
+, langCC
+, langJit
+}:
+
+let
+  enableLibGccOutput = (with stdenv; targetPlatform == hostPlatform) && !langJit;
+in
+(pkg: pkg.overrideAttrs (previousAttrs: lib.optionalAttrs ((!langC) || langJit || enableLibGccOutput) {
+  outputs = previousAttrs.outputs ++ lib.optionals enableLibGccOutput [ "libgcc" ];
+  # This is a separate phase because gcc assembles its phase scripts
+  # in bash instead of nix (we should fix that).
+  preFixupPhases = (previousAttrs.preFixupPhases or []) ++ [ "preFixupLibGccPhase" ];
+  preFixupLibGccPhase =
+    # delete extra/unused builds of libgcc_s in non-langC builds
+    # (i.e. libgccjit, gnat, etc) to avoid potential confusion
+    lib.optionalString (!langC) ''
+      rm -f $out/lib/libgcc_s.so*
+    ''
+
+    # TODO(amjoseph): remove the `libgcc_s.so` symlinks below and replace them
+    # with a `-L${gccForLibs.libgcc}/lib` in cc-wrapper's
+    # `$out/nix-support/cc-flags`.  See also:
+    # - https://github.com/NixOS/nixpkgs/pull/209870#discussion_r1130614895
+    # - https://github.com/NixOS/nixpkgs/pull/209870#discussion_r1130635982
+    # - https://github.com/NixOS/nixpkgs/commit/404155c6acfa59456aebe6156b22fe385e7dec6f
+    #
+    # move `libgcc_s.so` into its own output, `$libgcc`
+    + lib.optionalString enableLibGccOutput (''
+      # move libgcc from lib to its own output (libgcc)
+      mkdir -p $libgcc/lib
+      mv    $lib/lib/libgcc_s.so      $libgcc/lib/
+      mv    $lib/lib/libgcc_s.so.1    $libgcc/lib/
+      ln -s $libgcc/lib/libgcc_s.so   $lib/lib/
+      ln -s $libgcc/lib/libgcc_s.so.1 $lib/lib/
+    ''
+    #
+    # Nixpkgs ordinarily turns dynamic linking into pseudo-static linking:
+    # libraries are still loaded dynamically, exactly which copy of each
+    # library is loaded is permanently fixed at compile time (via RUNPATH).
+    # For libgcc_s we must revert to the "impure dynamic linking" style found
+    # in imperative software distributions.  We must do this because
+    # `libgcc_s` calls `malloc()` and therefore has a `DT_NEEDED` for `libc`,
+    # which creates two problems:
+    #
+    #  1. A circular package dependency `glibc`<-`libgcc`<-`glibc`
+    #
+    #  2. According to the `-Wl,-rpath` flags added by Nixpkgs' `ld-wrapper`,
+    #     the two versions of `glibc` in the cycle above are actually
+    #     different packages.  The later one is compiled by this `gcc`, but
+    #     the earlier one was compiled by the compiler *that compiled* this
+    #     `gcc` (usually the bootstrapFiles).  In any event, the `glibc`
+    #     dynamic loader won't honor that specificity without namespaced
+    #     manual loads (`dlmopen()`).  Once a `libc` is present in the address
+    #     space of a process, that `libc` will be used to satisfy all
+    #     `DT_NEEDED`s for `libc`, regardless of `RUNPATH`s.
+    #
+    # So we wipe the RUNPATH using `patchelf --set-rpath ""`.  We can't use
+    # `patchelf --remove-rpath`, because at least as of patchelf 0.15.0 it
+    # will leave the old RUNPATH string in the file where the reference
+    # scanner can still find it:
+    #
+    #   https://github.com/NixOS/patchelf/issues/453
+    #
+    # Note: we might be using the bootstrapFiles' copy of patchelf, so we have
+    # to keep doing it this way until both the issue is fixed *and* all the
+    # bootstrapFiles are regenerated, on every platform.
+    #
+    # This patchelfing is *not* effectively equivalent to copying
+    # `libgcc_s` into `glibc`'s outpath.  There is one minor and one
+    # major difference:
+    #
+    # 1. (Minor): multiple builds of `glibc` (say, with different
+    #    overrides or parameters) will all reference a single store
+    #    path:
+    #
+    #      /nix/store/xxx...xxx-gcc-libgcc/lib/libgcc_s.so.1
+    #
+    #    This many-to-one referrer relationship will be visible in the store's
+    #    dependency graph, and will be available to `nix-store -q` queries.
+    #    Copying `libgcc_s` into each of its referrers would lose that
+    #    information.
+    #
+    # 2. (Major): by referencing `libgcc_s.so.1`, rather than copying it, we
+    #    are still able to run `nix-store -qd` on it to find out how it got
+    #    built!  Most importantly, we can see from that deriver which compiler
+    #    was used to build it (or if it is part of the unpacked
+    #    bootstrap-files).  Copying `libgcc_s.so.1` from one outpath to
+    #    another eliminates the ability to make these queries.
+    #
+    + ''
+      patchelf --set-rpath "" $libgcc/lib/libgcc_s.so.1
+    '');
+}))
diff --git a/pkgs/development/libraries/glibc/default.nix b/pkgs/development/libraries/glibc/default.nix
index e1a427e35b6..8ad9c90ff7a 100644
--- a/pkgs/development/libraries/glibc/default.nix
+++ b/pkgs/development/libraries/glibc/default.nix
@@ -66,33 +66,26 @@ in
         ]);
     };
 
-    # When building glibc from bootstrap-tools, we need libgcc_s at RPATH for
-    # any program we run, because the gcc will have been placed at a new
-    # store path than that determined when built (as a source for the
-    # bootstrap-tools tarball)
-    # Building from a proper gcc staying in the path where it was installed,
-    # libgcc_s will now be at {gcc}/lib, and gcc's libgcc will be found without
-    # any special hack.
-    # TODO: remove this hack. Things that rely on this hack today:
-    # - dejagnu: during linux bootstrap tcl SIGSEGVs
-    # - clang-wrapper in cross-compilation
-    # Last attempt: https://github.com/NixOS/nixpkgs/pull/36948
-    preInstall = lib.optionalString (stdenv.hostPlatform == stdenv.buildPlatform) ''
-      if [ -f ${lib.getLib stdenv.cc.cc}/lib/libgcc_s.so.1 ]; then
-          mkdir -p $out/lib
-          cp ${lib.getLib stdenv.cc.cc}/lib/libgcc_s.so.1 $out/lib/libgcc_s.so.1
-          # the .so It used to be a symlink, but now it is a script
-          cp -a ${lib.getLib stdenv.cc.cc}/lib/libgcc_s.so $out/lib/libgcc_s.so
-          # wipe out reference to previous libc it was built against
-          chmod +w $out/lib/libgcc_s.so.1
-          # rely on default RUNPATHs of the binary and other libraries
-          # Do no force-pull wrong glibc.
-          patchelf --remove-rpath $out/lib/libgcc_s.so.1
-          # 'patchelf' does not remove the string itself. Wipe out
-          # string reference to avoid possible link to bootstrapTools
-          ${buildPackages.nukeReferences}/bin/nuke-refs $out/lib/libgcc_s.so.1
-      fi
-    '';
+    # glibc needs to `dlopen()` `libgcc_s.so` but does not link
+    # against it.  Furthermore, glibc doesn't use the ordinary
+    # `dlopen()` call to do this; instead it uses one which ignores
+    # most paths:
+    #
+    #   https://sourceware.org/legacy-ml/libc-help/2013-11/msg00026.html
+    #
+    # In order to get it to not ignore `libgcc_s.so`, we have to add its path to
+    # `user-defined-trusted-dirs`:
+    #
+    #   https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/Makefile;h=b509b3eada1fb77bf81e2a0ca5740b94ad185764#l1355
+    #
+    # Conveniently, this will also inform Nix of the fact that glibc depends on
+    # gcc.libgcc, since the path will be embedded in the resulting binary.
+    #
+    makeFlags =
+      (previousAttrs.makeFlags or [])
+      ++ lib.optionals (stdenv.cc.cc?libgcc) [
+        "user-defined-trusted-dirs=${stdenv.cc.cc.libgcc}/lib"
+      ];
 
     postInstall = (if stdenv.hostPlatform == stdenv.buildPlatform then ''
       echo SUPPORTED-LOCALES=C.UTF-8/UTF-8 > ../glibc-2*/localedata/SUPPORTED
@@ -164,6 +157,12 @@ in
 
     separateDebugInfo = true;
 
+    passthru =
+      (previousAttrs.passthru or {})
+      // lib.optionalAttrs (stdenv.cc.cc?libgcc) {
+        inherit (stdenv.cc.cc) libgcc;
+      };
+
   meta = (previousAttrs.meta or {}) // { description = "The GNU C Library"; };
 })
 
diff --git a/pkgs/stdenv/linux/default.nix b/pkgs/stdenv/linux/default.nix
index 1ce284f6560..20aa670ba94 100644
--- a/pkgs/stdenv/linux/default.nix
+++ b/pkgs/stdenv/linux/default.nix
@@ -10,13 +10,10 @@
 #
 # Goals of the bootstrap process:
 # 1. final stdenv must not reference any of the bootstrap files.
-# 2. final stdenv must not contain any of the bootstrap files
-#    (the only current violation is libgcc_s.so in glibc).
+# 2. final stdenv must not contain any of the bootstrap files.
 # 3. final stdenv must not contain any of the files directly
 #    generated by the bootstrap code generators (assembler, linker,
-#    compiler). The only current violations are: libgcc_s.so in glibc,
-#    the lib{mpfr,mpc,gmp,isl} which are statically linked
-#    into the final gcc).
+#    compiler).
 #
 # These goals ensure that final packages and final stdenv are built
 # exclusively using nixpkgs package definitions and don't depend
@@ -116,6 +113,14 @@ let
     # see https://github.com/NixOS/nixpkgs/issues/108475 for context
     reproducibleBuild = true;
     profiledCompiler = false;
+
+    # It appears that libcc1 (which is not a g++ plugin; it is a gdb plugin) gets linked against
+    # the libstdc++ from the compiler that *built* g++, not the libstdc++ which was just built.
+    # This causes a reference chain from stdenv to the bootstrapFiles:
+    #
+    #   stdenv -> gcc-lib -> xgcc-lib -> bootstrapFiles
+    #
+    disableGdbPlugin = true;
   };
 
   commonPreHook =
@@ -177,7 +182,7 @@ let
 
         cc = if prevStage.gcc-unwrapped == null
              then null
-             else lib.makeOverridable (import ../../build-support/cc-wrapper) {
+             else (lib.makeOverridable (import ../../build-support/cc-wrapper) {
           name = "${name}-gcc-wrapper";
           nativeTools = false;
           nativeLibc = false;
@@ -191,7 +196,12 @@ let
           inherit lib;
           inherit (prevStage) coreutils gnugrep;
           stdenvNoCC = prevStage.ccWrapperStdenv;
-        };
+        }).overrideAttrs(a: lib.optionalAttrs (prevStage.gcc-unwrapped.passthru.isXgcc or false) {
+          # This affects only `xgcc` (the compiler which compiles the final compiler).
+          postFixup = (a.postFixup or "") + ''
+            echo "--sysroot=${lib.getDev (getLibc prevStage)}" >> $out/nix-support/cc-cflags
+          '';
+        });
 
         overrides = self: super: (overrides self super) // { fetchurl = thisStdenv.fetchurlBoot; };
       };
@@ -233,7 +243,7 @@ in
       ${localSystem.libc} = self.stdenv.mkDerivation {
         pname = "bootstrap-stage0-${localSystem.libc}";
         strictDeps = true;
-        version = "bootstrap";
+        version = "bootstrapFiles";
         enableParallelBuilding = true;
         buildCommand = ''
           mkdir -p $out
@@ -289,7 +299,7 @@ in
       };
       inherit (prevStage)
         ccWrapperStdenv
-        gcc-unwrapped coreutils gnugrep;
+        gcc-unwrapped coreutils gnugrep binutils;
 
       ${localSystem.libc} = getLibc prevStage;
 
@@ -302,6 +312,77 @@ in
     };
   })
 
+  # First rebuild of gcc; this is linked against all sorts of junk
+  # from the bootstrap-files, but we only care about the code that
+  # this compiler *emits*.  The `gcc` binary produced in this stage
+  # is not part of the final stdenv.
+  (prevStage:
+    assert isBuiltByBootstrapFilesCompiler prevStage.binutils-unwrapped;
+    assert            isFromBootstrapFiles prevStage."${localSystem.libc}";
+    assert            isFromBootstrapFiles prevStage.gcc-unwrapped;
+    assert            isFromBootstrapFiles prevStage.coreutils;
+    assert            isFromBootstrapFiles prevStage.gnugrep;
+    stageFun prevStage {
+      name = "bootstrap-stage-xgcc";
+      overrides = final: prev: {
+        inherit (prevStage) ccWrapperStdenv coreutils gnugrep gettext bison texinfo zlib gnum4 perl;
+        patchelf = bootstrapTools;
+        ${localSystem.libc} = getLibc prevStage;
+        gmp      = prev.gmp.override { cxx = false; };
+        gcc-unwrapped =
+          (prev.gcc-unwrapped.override (commonGccOverrides // {
+            enablePlugins = false;
+
+            # The most logical name for this package would be something like
+            # "gcc-stage1".  Unfortunately "stage" is already reserved for the
+            # layers of stdenv, so using "stage" in the name of this package
+            # would cause massive confusion.
+            #
+            # Gcc calls its "stage1" compiler `xgcc` (--disable-bootstrap results
+            # in `xgcc` being copied to $prefix/bin/gcc).  So we imitate that.
+            #
+            name = "xgcc";
+
+          })).overrideAttrs (a: {
+
+            # This signals to cc-wrapper (as overridden above in this file) to add `--sysroot`
+            # to `$out/nix-support/cc-cflags`.
+            passthru = a.passthru // { isXgcc = true; };
+
+            # Gcc will look for the C library headers in
+            #
+            #    ${with_build_sysroot}${native_system_header_dir}
+            #
+            # The ordinary gcc expression sets `--with-build-sysroot=/` and sets
+            # `native-system-header-dir` to `"${lib.getDev stdenv.cc.libc}/include`.
+            #
+            # Unfortunately the value of "--with-native-system-header-dir=" gets "burned in" to the
+            # compiler, and it is quite difficult to get the compiler to change or ignore it
+            # afterwards.  On the other hand, the `sysroot` is very easy to change; you can just pass
+            # a `--sysroot` flag to `gcc`.
+            #
+            # So we override the expression to remove the default settings for these flags, and
+            # replace them such that the concatenated value will be the same as before, but we split
+            # the value between the two variables differently: `--native-system-header-dir=/include`,
+            # and `--with-build-sysroot=${lib.getDev stdenv.cc.libc}`.
+            #
+            configureFlags = (a.configureFlags or []) ++ [
+              "--with-native-system-header-dir=/include"
+              "--with-build-sysroot=${lib.getDev final.stdenv.cc.libc}"
+            ];
+
+            # This is a separate phase because gcc assembles its phase scripts
+            # in bash instead of nix (we should fix that).
+            preFixupPhases = (a.preFixupPhases or []) ++ [ "preFixupXgccPhase" ];
+
+            # This is needed to prevent "error: cycle detected in build of '...-xgcc-....drv'
+            # in the references of output 'lib' from output 'out'"
+            preFixupXgccPhase = ''
+              find $lib/lib/ -name \*.so\* -exec patchelf --shrink-rpath {} \; || true
+            '';
+          });
+      };
+    })
 
   # 2nd stdenv that contains our own rebuilt binutils and is used for
   # compiling our own Glibc.
@@ -310,7 +391,7 @@ in
     # previous stage1 stdenv:
     assert isBuiltByBootstrapFilesCompiler prevStage.binutils-unwrapped;
     assert            isFromBootstrapFiles prevStage."${localSystem.libc}";
-    assert            isFromBootstrapFiles prevStage.gcc-unwrapped;
+    assert isBuiltByBootstrapFilesCompiler prevStage.gcc-unwrapped;
     assert            isFromBootstrapFiles prevStage.coreutils;
     assert            isFromBootstrapFiles prevStage.gnugrep;
     stageFun prevStage {
@@ -320,7 +401,7 @@ in
       inherit (prevStage)
         ccWrapperStdenv gettext
         gcc-unwrapped coreutils gnugrep
-        perl gnum4 bison;
+        perl gnum4 bison texinfo which;
       dejagnu = super.dejagnu.overrideAttrs (a: { doCheck = false; } );
 
       # We need libidn2 and its dependency libunistring as glibc dependency.
@@ -385,11 +466,12 @@ in
   # binutils and rest of the bootstrap tools, including GCC.
   (prevStage:
     # previous stage2 stdenv:
-    assert isBuiltByBootstrapFilesCompiler prevStage.binutils-unwrapped;
-    assert isBuiltByBootstrapFilesCompiler prevStage.${localSystem.libc};
-    assert            isFromBootstrapFiles prevStage.gcc-unwrapped;
+    assert        isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
+    assert        isBuiltByNixpkgsCompiler prevStage.${localSystem.libc};
+    assert isBuiltByBootstrapFilesCompiler prevStage.gcc-unwrapped;
     assert            isFromBootstrapFiles prevStage.coreutils;
     assert            isFromBootstrapFiles prevStage.gnugrep;
+    assert lib.all isBuiltByNixpkgsCompiler (with prevStage; [ gmp isl_0_20 libmpc mpfr ]);
     stageFun prevStage {
     name = "bootstrap-stage3";
 
@@ -397,21 +479,20 @@ in
       inherit (prevStage)
         ccWrapperStdenv
         binutils coreutils gnugrep gettext
-        perl patchelf linuxHeaders gnum4 bison libidn2 libunistring;
+        perl patchelf linuxHeaders gnum4 bison libidn2 libunistring libxcrypt;
+        # We build a special copy of libgmp which doesn't use libstdc++, because
+        # xgcc++'s libstdc++ references the bootstrap-files (which is what
+        # compiles xgcc++).
+        gmp = super.gmp.override { cxx = false; };
+      } // {
       ${localSystem.libc} = getLibc prevStage;
-      gcc-unwrapped =
-        let makeStaticLibrariesAndMark = pkg:
-              lib.makeOverridable (pkg.override { stdenv = self.makeStaticLibraries self.stdenv; })
-                .overrideAttrs (a: { pname = "${a.pname}-stage3"; });
-        in super.gcc-unwrapped.override (commonGccOverrides // {
-        # Link GCC statically against GMP etc.  This makes sense because
-        # these builds of the libraries are only used by GCC, so it
-        # reduces the size of the stdenv closure.
-        gmp = makeStaticLibrariesAndMark super.gmp;
-        mpfr = makeStaticLibrariesAndMark super.mpfr;
-        libmpc = makeStaticLibrariesAndMark super.libmpc;
-        isl = makeStaticLibrariesAndMark super.isl_0_20;
-        });
+      gcc-unwrapped = (super.gcc-unwrapped.override (commonGccOverrides // {
+        inherit (prevStage) which;
+      }
+      )).overrideAttrs (a: {
+        # so we can add them to allowedRequisites below
+        passthru = a.passthru // { inherit (self) gmp mpfr libmpc isl; };
+      });
     };
     extraNativeBuildInputs = [ prevStage.patchelf ] ++
       # Many tarballs come with obsolete config.sub/config.guess that don't recognize aarch64.
@@ -425,18 +506,11 @@ in
   #
   (prevStage:
     # previous stage3 stdenv:
-    assert isBuiltByBootstrapFilesCompiler prevStage.binutils-unwrapped;
-    assert isBuiltByBootstrapFilesCompiler prevStage.${localSystem.libc};
-    assert isBuiltByBootstrapFilesCompiler prevStage.gcc-unwrapped;
-    assert            isFromBootstrapFiles prevStage.coreutils;
-    assert            isFromBootstrapFiles prevStage.gnugrep;
-    # Can assume prevStage.gcc-unwrapped has almost no code from
-    # bootstrapTools as gcc bootstraps internally. The only
-    # exceptions are crt files from glibc built bybootstrapTools
-    # used to link executables and libraries, and the
-    # bootstrapTools-built, statically-linked
-    # lib{mpfr,mpc,gmp,isl}.a which are linked into the final gcc
-    # (see commit cfde88976ba4cddd01b1bb28b40afd12ea93a11d).
+    assert isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
+    assert isBuiltByNixpkgsCompiler prevStage.${localSystem.libc};
+    assert isBuiltByNixpkgsCompiler prevStage.gcc-unwrapped;
+    assert     isFromBootstrapFiles prevStage.coreutils;
+    assert     isFromBootstrapFiles prevStage.gnugrep;
     stageFun prevStage {
     name = "bootstrap-stage4";
 
@@ -456,11 +530,6 @@ in
         };
       };
 
-      # force gmp to rebuild so we have the option of dynamically linking
-      # libgmp without creating a reference path from:
-      #   stage5.gcc -> stage4.coreutils -> stage3.glibc -> bootstrap
-      gmp = lib.makeOverridable (super.gmp.override { stdenv = self.stdenv; }).overrideAttrs (a: { pname = "${a.pname}-stage4"; });
-
       # To allow users' overrides inhibit dependencies too heavy for
       # bootstrap, like guile: https://github.com/NixOS/nixpkgs/issues/181188
       gnumake = super.gnumake.override { inBootstrap = true; };
@@ -497,11 +566,11 @@ in
   (prevStage:
     # previous stage4 stdenv; see stage3 comment regarding gcc,
     # which applies here as well.
-    assert        isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
-    assert isBuiltByBootstrapFilesCompiler prevStage.${localSystem.libc};
-    assert isBuiltByBootstrapFilesCompiler prevStage.gcc-unwrapped;
-    assert        isBuiltByNixpkgsCompiler prevStage.coreutils;
-    assert        isBuiltByNixpkgsCompiler prevStage.gnugrep;
+    assert isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
+    assert isBuiltByNixpkgsCompiler prevStage.${localSystem.libc};
+    assert isBuiltByNixpkgsCompiler prevStage.gcc-unwrapped;
+    assert isBuiltByNixpkgsCompiler prevStage.coreutils;
+    assert isBuiltByNixpkgsCompiler prevStage.gnugrep;
     {
     inherit config overlays;
     stdenv = import ../generic rec {
@@ -549,11 +618,15 @@ in
           )
         # More complicated cases
         ++ (map (x: getOutput x (getLibc prevStage)) [ "out" "dev" "bin" ] )
-        ++  [ /*propagated from .dev*/ linuxHeaders
-            binutils gcc gcc.cc gcc.cc.lib gcc.expand-response-params
+        ++  [ linuxHeaders # propagated from .dev
+            binutils gcc gcc.cc gcc.cc.lib gcc.expand-response-params gcc.cc.libgcc glibc.passthru.libgcc
           ]
-          ++ lib.optionals (!localSystem.isx86 || localSystem.libc == "musl")
-            [ prevStage.updateAutotoolsGnuConfigScriptsHook prevStage.gnu-config ];
+        ++ lib.optionals (!localSystem.isx86 || localSystem.libc == "musl")
+            [ prevStage.updateAutotoolsGnuConfigScriptsHook prevStage.gnu-config ]
+        ++ (with gcc-unwrapped.passthru; [
+          gmp libmpc mpfr isl
+        ])
+      ;
 
       overrides = self: super: {
         inherit (prevStage)
@@ -582,10 +655,10 @@ in
   (prevStage:
     # previous stage5 stdenv; see stage3 comment regarding gcc,
     # which applies here as well.
-    assert        isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
-    assert isBuiltByBootstrapFilesCompiler prevStage.${localSystem.libc};
-    assert isBuiltByBootstrapFilesCompiler prevStage.gcc-unwrapped;
-    assert        isBuiltByNixpkgsCompiler prevStage.coreutils;
-    assert        isBuiltByNixpkgsCompiler prevStage.gnugrep;
+    assert isBuiltByNixpkgsCompiler prevStage.binutils-unwrapped;
+    assert isBuiltByNixpkgsCompiler prevStage.${localSystem.libc};
+    assert isBuiltByNixpkgsCompiler prevStage.gcc-unwrapped;
+    assert isBuiltByNixpkgsCompiler prevStage.coreutils;
+    assert isBuiltByNixpkgsCompiler prevStage.gnugrep;
     { inherit (prevStage) config overlays stdenv; })
 ]