about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRobert Hensing <roberth@users.noreply.github.com>2022-12-10 21:12:43 +0100
committerGitHub <noreply@github.com>2022-12-10 21:12:43 +0100
commit16f5747575f3da6c7f4e4ecabc54063fef31914b (patch)
tree388ee9e72e08695e4fb862f0efb1bad736aeac76
parentd17577b991dd855e6e3ad8f31f6c015bdcbacccf (diff)
parent11c3127e38dafdf95ca71a85b1591a29b67e0c09 (diff)
Merge pull request #175649 from Artturin/opt-in-structured-attrs
stdenv: support opt-in __structuredAttrs
-rw-r--r--doc/stdenv/stdenv.chapter.md26
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/builder.sh1
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh1
-rw-r--r--pkgs/applications/misc/adobe-reader/builder.sh1
-rw-r--r--pkgs/applications/networking/cluster/nomad-autoscaler/default.nix2
-rw-r--r--pkgs/applications/office/libreoffice/download-list-builder.sh1
-rw-r--r--pkgs/build-support/bintools-wrapper/default.nix32
-rw-r--r--pkgs/build-support/cc-wrapper/default.nix30
-rw-r--r--pkgs/build-support/fetchbzr/builder.sh1
-rw-r--r--pkgs/build-support/fetchcvs/builder.sh1
-rw-r--r--pkgs/build-support/fetchdarcs/builder.sh1
-rw-r--r--pkgs/build-support/fetchdocker/fetchdocker-builder.sh3
-rw-r--r--pkgs/build-support/fetchfossil/builder.sh1
-rw-r--r--pkgs/build-support/fetchgit/builder.sh2
-rw-r--r--pkgs/build-support/fetchhg/builder.sh1
-rw-r--r--pkgs/build-support/fetchipfs/builder.sh1
-rw-r--r--pkgs/build-support/fetchmtn/builder.sh1
-rw-r--r--pkgs/build-support/fetchsvn/builder.sh1
-rw-r--r--pkgs/build-support/fetchsvnssh/builder.sh1
-rw-r--r--pkgs/build-support/fetchurl/builder.sh1
-rw-r--r--pkgs/build-support/nuke-references/default.nix10
-rw-r--r--pkgs/build-support/pkg-config-wrapper/default.nix13
-rw-r--r--pkgs/build-support/release/nix-build.nix2
-rw-r--r--pkgs/build-support/setup-hooks/auto-patchelf.sh2
-rw-r--r--pkgs/build-support/setup-hooks/move-docs.sh12
-rw-r--r--pkgs/build-support/setup-hooks/multiple-outputs.sh34
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh2
-rw-r--r--pkgs/build-support/setup-hooks/strip.sh21
-rw-r--r--pkgs/build-support/setup-hooks/win-dll-link.sh2
-rw-r--r--pkgs/build-support/testers/expect-failure.sh2
-rw-r--r--pkgs/data/icons/catppuccin-cursors/default.nix2
-rw-r--r--pkgs/data/icons/comixcursors/default.nix2
-rw-r--r--pkgs/desktops/gnome/core/gdm/default.nix2
-rw-r--r--pkgs/desktops/gnustep/make/builder.sh1
-rwxr-xr-xpkgs/development/compilers/aspectj/builder.sh1
-rw-r--r--pkgs/development/compilers/chicken/4/fetchegg/builder.sh1
-rw-r--r--pkgs/development/compilers/chicken/5/fetchegg/builder.sh1
-rwxr-xr-xpkgs/development/compilers/fpc/binary-builder-darwin.sh1
-rwxr-xr-xpkgs/development/compilers/fpc/binary-builder.sh1
-rw-r--r--pkgs/development/compilers/gcc/builder.sh1
-rw-r--r--pkgs/development/compilers/ios-cross-compile/9.2_builder.sh1
-rw-r--r--pkgs/development/compilers/ocaml/builder.sh1
-rw-r--r--pkgs/development/compilers/openjdk/11.nix4
-rw-r--r--pkgs/development/compilers/openjdk/12.nix4
-rw-r--r--pkgs/development/compilers/openjdk/13.nix4
-rw-r--r--pkgs/development/compilers/openjdk/14.nix4
-rw-r--r--pkgs/development/compilers/openjdk/15.nix4
-rw-r--r--pkgs/development/compilers/openjdk/16.nix4
-rw-r--r--pkgs/development/compilers/openjdk/17.nix4
-rw-r--r--pkgs/development/compilers/openjdk/18.nix4
-rw-r--r--pkgs/development/compilers/openjdk/19.nix4
-rw-r--r--pkgs/development/compilers/openjdk/8.nix4
-rw-r--r--pkgs/development/interpreters/python/cpython/default.nix20
-rw-r--r--pkgs/development/interpreters/python/setup-hook.nix4
-rw-r--r--pkgs/development/libraries/gettext/default.nix4
-rw-r--r--pkgs/development/libraries/glibc/common.nix15
-rw-r--r--pkgs/development/libraries/glibc/locales-builder.sh1
-rw-r--r--pkgs/development/libraries/gtk-sharp/builder.sh1
-rw-r--r--pkgs/development/libraries/polkit/default.nix2
-rw-r--r--pkgs/development/libraries/wtk/builder.sh1
-rw-r--r--pkgs/development/libraries/xapian/default.nix2
-rw-r--r--pkgs/development/nim-packages/fetch-nimble/builder.sh1
-rw-r--r--pkgs/development/perl-modules/generic/builder.sh1
-rw-r--r--pkgs/development/perl-modules/generic/default.nix9
-rw-r--r--pkgs/development/tools/build-managers/apache-maven/builder.sh1
-rw-r--r--pkgs/development/tools/build-managers/boot/builder.sh1
-rw-r--r--pkgs/development/tools/misc/automake/builder.sh1
-rw-r--r--pkgs/development/tools/parsing/antlr/builder.sh1
-rw-r--r--pkgs/misc/cups/drivers/samsung/4.00.39/builder.sh1
-rw-r--r--pkgs/os-specific/darwin/signing-utils/auto-sign-hook.sh2
-rwxr-xr-xpkgs/os-specific/linux/nvidia-x11/builder.sh1
-rw-r--r--pkgs/os-specific/linux/opengl/xorg-sys/builder.sh1
-rw-r--r--pkgs/servers/http/tomcat/axis2/builder.sh1
-rw-r--r--pkgs/servers/monitoring/munin/default.nix2
-rw-r--r--pkgs/servers/x11/xorg/builder.sh1
-rw-r--r--pkgs/stdenv/darwin/default.nix7
-rw-r--r--pkgs/stdenv/generic/default-builder.sh4
-rw-r--r--pkgs/stdenv/generic/make-derivation.nix31
-rw-r--r--pkgs/stdenv/generic/setup.sh276
-rw-r--r--pkgs/stdenv/linux/default.nix2
-rw-r--r--pkgs/test/default.nix1
-rw-r--r--pkgs/test/simple/builder.sh1
-rw-r--r--pkgs/test/stdenv/default.nix159
-rwxr-xr-xpkgs/tools/typesetting/lout/builder.sh1
-rw-r--r--pkgs/tools/typesetting/tex/texlive/bin.nix2
-rw-r--r--pkgs/top-level/all-packages.nix8
-rw-r--r--pkgs/top-level/config.nix4
-rw-r--r--pkgs/top-level/perl-packages.nix4
88 files changed, 645 insertions, 193 deletions
diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md
index e3d874ef507e5..78560a29a3377 100644
--- a/doc/stdenv/stdenv.chapter.md
+++ b/doc/stdenv/stdenv.chapter.md
@@ -990,6 +990,32 @@ Convenience function for `makeWrapper` that replaces `<\executable\>` with a wra
 
 If you will apply it multiple times, it will overwrite the wrapper file and you will end up with double wrapping, which should be avoided.
 
+### `prependToVar` \<variableName\> \<elements...\> {#fun-prependToVar}
+
+Prepend elements to a variable.
+
+Example:
+
+```shellSession
+$ configureFlags="--disable-static"
+$ prependToVar configureFlags --disable-dependency-tracking --enable-foo
+$ echo $configureFlags
+--disable-dependency-tracking --enable-foo --disable-static
+```
+
+### `appendToVar` \<variableName\> \<elements...\> {#fun-appendToVar}
+
+Append elements to a variable.
+
+Example:
+
+```shellSession
+$ configureFlags="--disable-static"
+$ appendToVar configureFlags --disable-dependency-tracking --enable-foo
+$ echo $configureFlags
+--disable-static --disable-dependency-tracking --enable-foo
+```
+
 ## Package setup hooks {#ssec-setup-hooks}
 
 Nix itself considers a build-time dependency as merely something that should previously be built and accessible at build time—packages themselves are on their own to perform any additional setup. In most cases, that is fine, and the downstream derivation can deal with its own dependencies. But for a few common tasks, that would result in almost every package doing the same sort of setup work—depending not on the package itself, but entirely on which dependencies were used.
diff --git a/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixos/modules/services/networking/ircd-hybrid/builder.sh
index 38312210df251..d9d2e4264dfd1 100644
--- a/nixos/modules/services/networking/ircd-hybrid/builder.sh
+++ b/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 doSub() {
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
index 0e5af324c13fe..ac573089cd5a7 100644
--- a/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -1,5 +1,6 @@
 set -e
 
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/bin
diff --git a/pkgs/applications/misc/adobe-reader/builder.sh b/pkgs/applications/misc/adobe-reader/builder.sh
index 41281385c990d..6047c08264301 100644
--- a/pkgs/applications/misc/adobe-reader/builder.sh
+++ b/pkgs/applications/misc/adobe-reader/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 echo "unpacking $src..."
diff --git a/pkgs/applications/networking/cluster/nomad-autoscaler/default.nix b/pkgs/applications/networking/cluster/nomad-autoscaler/default.nix
index 6329ff9ed11c0..eb185f9743c1f 100644
--- a/pkgs/applications/networking/cluster/nomad-autoscaler/default.nix
+++ b/pkgs/applications/networking/cluster/nomad-autoscaler/default.nix
@@ -45,7 +45,7 @@ let
       mv bin/nomad-autoscaler $bin/bin
       ln -s $bin/bin/nomad-autoscaler $out/bin/nomad-autoscaler
 
-      for d in $outputs; do
+      for d in $(getAllOutputNames); do
         mkdir -p ''${!d}/share
       done
       rmdir $bin/share
diff --git a/pkgs/applications/office/libreoffice/download-list-builder.sh b/pkgs/applications/office/libreoffice/download-list-builder.sh
index c054e2c72cbe9..31cab28fd82e2 100644
--- a/pkgs/applications/office/libreoffice/download-list-builder.sh
+++ b/pkgs/applications/office/libreoffice/download-list-builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 tar --extract --file=$src libreoffice-$version/download.lst -O > $out
diff --git a/pkgs/build-support/bintools-wrapper/default.nix b/pkgs/build-support/bintools-wrapper/default.nix
index 121b50fe0f523..be2e3567da856 100644
--- a/pkgs/build-support/bintools-wrapper/default.nix
+++ b/pkgs/build-support/bintools-wrapper/default.nix
@@ -55,9 +55,9 @@ let
   bintoolsVersion = lib.getVersion bintools;
   bintoolsName = lib.removePrefix targetPrefix (lib.getName bintools);
 
-  libc_bin = if libc == null then null else getBin libc;
-  libc_dev = if libc == null then null else getDev libc;
-  libc_lib = if libc == null then null else getLib libc;
+  libc_bin = if libc == null then "" else getBin libc;
+  libc_dev = if libc == null then "" else getDev libc;
+  libc_lib = if libc == null then "" else getLib libc;
   bintools_bin = if nativeTools then "" else getBin bintools;
   # The wrapper scripts use 'cat' and 'grep', so we may need coreutils.
   coreutils_bin = if nativeTools then "" else getBin coreutils;
@@ -68,7 +68,7 @@ let
   # The dynamic linker has different names on different platforms. This is a
   # shell glob that ought to match it.
   dynamicLinker =
-    /**/ if sharedLibraryLoader == null then null
+    /**/ if sharedLibraryLoader == null then ""
     else if targetPlatform.libc == "musl"             then "${sharedLibraryLoader}/lib/ld-musl-*"
     else if targetPlatform.libc == "uclibc"           then "${sharedLibraryLoader}/lib/ld*-uClibc.so.1"
     else if (targetPlatform.libc == "bionic" && targetPlatform.is32bit) then "/system/bin/linker"
@@ -87,7 +87,7 @@ let
     else if targetPlatform.isDarwin                   then "/usr/lib/dyld"
     else if targetPlatform.isFreeBSD                  then "/libexec/ld-elf.so.1"
     else if lib.hasSuffix "pc-gnu" targetPlatform.config then "ld.so.1"
-    else null;
+    else "";
 
   expand-response-params =
     if buildPackages ? stdenv && buildPackages.stdenv.hasCC && buildPackages.stdenv.cc != "/dev/null"
@@ -103,15 +103,10 @@ stdenv.mkDerivation {
 
   preferLocalBuild = true;
 
-  inherit bintools_bin libc_bin libc_dev libc_lib coreutils_bin;
-  shell = getBin shell + shell.shellPath or "";
-  gnugrep_bin = if nativeTools then "" else gnugrep;
-
-  inherit targetPrefix suffixSalt;
-
   outputs = [ "out" ] ++ optionals propagateDoc ([ "man" ] ++ optional (bintools ? info) "info");
 
   passthru = {
+    inherit targetPrefix suffixSalt;
     inherit bintools libc nativeTools nativeLibc nativePrefix;
 
     emacsBufferSetup = pkgs: ''
@@ -193,8 +188,6 @@ stdenv.mkDerivation {
   strictDeps = true;
   depsTargetTargetPropagated = extraPackages;
 
-  wrapperName = "BINTOOLS_WRAPPER";
-
   setupHooks = [
     ../setup-hooks/role.bash
     ./setup-hook.sh
@@ -366,10 +359,15 @@ stdenv.mkDerivation {
     ##
     + extraBuildCommands;
 
-  inherit dynamicLinker;
-
-  # for substitution in utils.bash
-  expandResponseParams = "${expand-response-params}/bin/expand-response-params";
+  env = {
+    # for substitution in utils.bash
+    expandResponseParams = "${expand-response-params}/bin/expand-response-params";
+    shell = getBin shell + shell.shellPath or "";
+    gnugrep_bin = if nativeTools then "" else gnugrep;
+    wrapperName = "BINTOOLS_WRAPPER";
+    inherit dynamicLinker targetPrefix suffixSalt coreutils_bin;
+    inherit bintools_bin libc_bin libc_dev libc_lib;
+  };
 
   meta =
     let bintools_ = if bintools != null then bintools else {}; in
diff --git a/pkgs/build-support/cc-wrapper/default.nix b/pkgs/build-support/cc-wrapper/default.nix
index a59505d082584..abc88910c36d2 100644
--- a/pkgs/build-support/cc-wrapper/default.nix
+++ b/pkgs/build-support/cc-wrapper/default.nix
@@ -42,9 +42,9 @@ let
   ccVersion = lib.getVersion cc;
   ccName = lib.removePrefix targetPrefix (lib.getName cc);
 
-  libc_bin = if libc == null then null else getBin libc;
-  libc_dev = if libc == null then null else getDev libc;
-  libc_lib = if libc == null then null else getLib libc;
+  libc_bin = if libc == null then "" else getBin libc;
+  libc_dev = if libc == null then "" else getDev libc;
+  libc_lib = if libc == null then "" else getLib libc;
   cc_solib = getLib cc
     + optionalString (targetPlatform != hostPlatform) "/${targetPlatform.config}";
 
@@ -131,22 +131,16 @@ stdenv.mkDerivation {
 
   preferLocalBuild = true;
 
-  inherit cc libc_bin libc_dev libc_lib bintools coreutils_bin;
-  shell = getBin shell + shell.shellPath or "";
-  gnugrep_bin = if nativeTools then "" else gnugrep;
-
-  inherit targetPrefix suffixSalt;
-  inherit darwinPlatformForCC darwinMinVersion darwinMinVersionVariable;
-
   outputs = [ "out" ] ++ optionals propagateDoc [ "man" "info" ];
 
   passthru = {
+    inherit targetPrefix suffixSalt;
     # "cc" is the generic name for a C compiler, but there is no one for package
     # providing the linker and related tools. The two we use now are GNU
     # Binutils, and Apple's "cctools"; "bintools" as an attempt to find an
     # unused middle-ground name that evokes both.
     inherit bintools;
-    inherit libc nativeTools nativeLibc nativePrefix isGNU isClang;
+    inherit cc libc nativeTools nativeLibc nativePrefix isGNU isClang;
 
     emacsBufferSetup = pkgs: ''
       ; We should handle propagation here too
@@ -270,8 +264,6 @@ stdenv.mkDerivation {
   propagatedBuildInputs = [ bintools ] ++ extraTools ++ optionals cc.langD or false [ zlib ];
   depsTargetTargetPropagated = optional (libcxx != null) libcxx ++ extraPackages;
 
-  wrapperName = "CC_WRAPPER";
-
   setupHooks = [
     ../setup-hooks/role.bash
   ] ++ lib.optional (cc.langC or true) ./setup-hook.sh
@@ -538,8 +530,16 @@ stdenv.mkDerivation {
         nixSupport);
 
 
-  # for substitution in utils.bash
-  expandResponseParams = "${expand-response-params}/bin/expand-response-params";
+  env = {
+    # for substitution in utils.bash
+    expandResponseParams = "${expand-response-params}/bin/expand-response-params";
+    shell = getBin shell + shell.shellPath or "";
+    gnugrep_bin = if nativeTools then "" else gnugrep;
+    wrapperName = "CC_WRAPPER";
+    inherit suffixSalt coreutils_bin bintools cc;
+    inherit libc_bin libc_dev libc_lib;
+    inherit darwinPlatformForCC darwinMinVersion darwinMinVersionVariable;
+  };
 
   meta =
     let cc_ = if cc != null then cc else {}; in
diff --git a/pkgs/build-support/fetchbzr/builder.sh b/pkgs/build-support/fetchbzr/builder.sh
index e424fd92d51e5..163f6fc60eea3 100644
--- a/pkgs/build-support/fetchbzr/builder.sh
+++ b/pkgs/build-support/fetchbzr/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source "$stdenv/setup"
 
 header "exporting \`$url' (revision $rev) into \`$out'"
diff --git a/pkgs/build-support/fetchcvs/builder.sh b/pkgs/build-support/fetchcvs/builder.sh
index fe1019aafc2f5..90363275b9737 100644
--- a/pkgs/build-support/fetchcvs/builder.sh
+++ b/pkgs/build-support/fetchcvs/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 (echo "#!$SHELL"; \
diff --git a/pkgs/build-support/fetchdarcs/builder.sh b/pkgs/build-support/fetchdarcs/builder.sh
index 301deb98307ff..018852770504a 100644
--- a/pkgs/build-support/fetchdarcs/builder.sh
+++ b/pkgs/build-support/fetchdarcs/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 tagtext=""
diff --git a/pkgs/build-support/fetchdocker/fetchdocker-builder.sh b/pkgs/build-support/fetchdocker/fetchdocker-builder.sh
index 7443591e6569b..e5a1a61b78d30 100644
--- a/pkgs/build-support/fetchdocker/fetchdocker-builder.sh
+++ b/pkgs/build-support/fetchdocker/fetchdocker-builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source "${stdenv}/setup"
 header "exporting ${repository}/${imageName} (tag: ${tag}) into ${out}"
 mkdir -p "${out}"
@@ -8,7 +9,7 @@ cat <<EOF > "${out}/compositeImage.sh"
 # Create a tar archive of a docker image's layers, docker image config
 # json, manifest.json, and repositories json; this streams directly to
 # stdout and is intended to be used in concert with docker load, i.e:
-# 
+#
 # ${out}/compositeImage.sh | docker load
 
 # The first character follow the 's' command for sed becomes the
diff --git a/pkgs/build-support/fetchfossil/builder.sh b/pkgs/build-support/fetchfossil/builder.sh
index 5f08aca424fed..009b23c406d6b 100644
--- a/pkgs/build-support/fetchfossil/builder.sh
+++ b/pkgs/build-support/fetchfossil/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 header "Cloning Fossil $url [$rev] into $out"
 
diff --git a/pkgs/build-support/fetchgit/builder.sh b/pkgs/build-support/fetchgit/builder.sh
index 66b6c168e41d3..acb970639ab17 100644
--- a/pkgs/build-support/fetchgit/builder.sh
+++ b/pkgs/build-support/fetchgit/builder.sh
@@ -2,6 +2,8 @@
 # - no revision specified and remote has a HEAD which is used
 # - revision specified and remote has a HEAD
 # - revision specified and remote without HEAD
+#
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 header "exporting $url (rev $rev) into $out"
diff --git a/pkgs/build-support/fetchhg/builder.sh b/pkgs/build-support/fetchhg/builder.sh
index 847f18fa59759..cec0e441f2296 100644
--- a/pkgs/build-support/fetchhg/builder.sh
+++ b/pkgs/build-support/fetchhg/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 header "getting $url${rev:+ ($rev)} into $out"
 
diff --git a/pkgs/build-support/fetchipfs/builder.sh b/pkgs/build-support/fetchipfs/builder.sh
index 7a6a517566f5e..ca77962b53847 100644
--- a/pkgs/build-support/fetchipfs/builder.sh
+++ b/pkgs/build-support/fetchipfs/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 # Curl flags to handle redirects, not use EPSV, handle cookies for
diff --git a/pkgs/build-support/fetchmtn/builder.sh b/pkgs/build-support/fetchmtn/builder.sh
index 73eff9c272529..7db66730dab82 100644
--- a/pkgs/build-support/fetchmtn/builder.sh
+++ b/pkgs/build-support/fetchmtn/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 set -x
diff --git a/pkgs/build-support/fetchsvn/builder.sh b/pkgs/build-support/fetchsvn/builder.sh
index ed3e65f076959..b58e5a88b3cd8 100644
--- a/pkgs/build-support/fetchsvn/builder.sh
+++ b/pkgs/build-support/fetchsvn/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 header "exporting $url (r$rev) into $out"
diff --git a/pkgs/build-support/fetchsvnssh/builder.sh b/pkgs/build-support/fetchsvnssh/builder.sh
index d9c6dc7da31a6..b0441299dd2c6 100644
--- a/pkgs/build-support/fetchsvnssh/builder.sh
+++ b/pkgs/build-support/fetchsvnssh/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 header "exporting $url (r$rev) into $out"
diff --git a/pkgs/build-support/fetchurl/builder.sh b/pkgs/build-support/fetchurl/builder.sh
index 5ca09b6fc77d5..dd987f41b4467 100644
--- a/pkgs/build-support/fetchurl/builder.sh
+++ b/pkgs/build-support/fetchurl/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 source $mirrorsFile
diff --git a/pkgs/build-support/nuke-references/default.nix b/pkgs/build-support/nuke-references/default.nix
index 8dd9704aa4b4a..c2dfe27b2c603 100644
--- a/pkgs/build-support/nuke-references/default.nix
+++ b/pkgs/build-support/nuke-references/default.nix
@@ -32,8 +32,10 @@ stdenvNoCC.mkDerivation {
   '';
 
   # FIXME: get rid of perl dependency.
-  inherit perl;
-  inherit (builtins) storeDir;
-  shell = lib.getBin shell + (shell.shellPath or "");
-  signingUtils = if darwinCodeSign then signingUtils else null;
+  env = {
+    inherit perl;
+    inherit (builtins) storeDir;
+    shell = lib.getBin shell + (shell.shellPath or "");
+    signingUtils = if darwinCodeSign then signingUtils else "";
+  };
 }
diff --git a/pkgs/build-support/pkg-config-wrapper/default.nix b/pkgs/build-support/pkg-config-wrapper/default.nix
index 312d2fe02610a..ca14a4495fed6 100644
--- a/pkgs/build-support/pkg-config-wrapper/default.nix
+++ b/pkgs/build-support/pkg-config-wrapper/default.nix
@@ -36,13 +36,10 @@ stdenv.mkDerivation {
 
   preferLocalBuild = true;
 
-  shell = getBin stdenvNoCC.shell + stdenvNoCC.shell.shellPath or "";
-
-  inherit targetPrefix suffixSalt baseBinName;
-
   outputs = [ "out" ] ++ optionals propagateDoc ([ "man" ] ++ optional (pkg-config ? doc) "doc");
 
   passthru = {
+    inherit targetPrefix suffixSalt;
     inherit pkg-config;
   };
 
@@ -83,8 +80,6 @@ stdenv.mkDerivation {
       ln -s ${pkg-config}/share $out/share
     '';
 
-  wrapperName = "PKG_CONFIG_WRAPPER";
-
   setupHooks = [
     ../setup-hooks/role.bash
     ./setup-hook.sh
@@ -120,6 +115,12 @@ stdenv.mkDerivation {
     ##
     + extraBuildCommands;
 
+  env = {
+    shell = getBin stdenvNoCC.shell + stdenvNoCC.shell.shellPath or "";
+    wrapperName = "PKG_CONFIG_WRAPPER";
+    inherit targetPrefix suffixSalt baseBinName;
+  };
+
   meta =
     let pkg-config_ = if pkg-config != null then pkg-config else {}; in
     (if pkg-config_ ? meta then removeAttrs pkg-config.meta ["priority"] else {}) //
diff --git a/pkgs/build-support/release/nix-build.nix b/pkgs/build-support/release/nix-build.nix
index ac51b90e01639..5ed2b0752efc5 100644
--- a/pkgs/build-support/release/nix-build.nix
+++ b/pkgs/build-support/release/nix-build.nix
@@ -124,7 +124,7 @@ stdenv.mkDerivation (
       echo "$system" > $out/nix-support/system
 
       if [ -z "${toString doingAnalysis}" ]; then
-          for i in $outputs; do
+          for i in $(getAllOutputNames); do
               if [ "$i" = out ]; then j=none; else j="$i"; fi
               mkdir -p ''${!i}/nix-support
               echo "nix-build $j ''${!i}" >> ''${!i}/nix-support/hydra-build-products
diff --git a/pkgs/build-support/setup-hooks/auto-patchelf.sh b/pkgs/build-support/setup-hooks/auto-patchelf.sh
index 7f5ff146e30b6..8a74a69bdcebb 100644
--- a/pkgs/build-support/setup-hooks/auto-patchelf.sh
+++ b/pkgs/build-support/setup-hooks/auto-patchelf.sh
@@ -84,7 +84,7 @@ autoPatchelf() {
 # (Expressions don't expand in single quotes, use double quotes for that.)
 postFixupHooks+=('
     if [ -z "${dontAutoPatchelf-}" ]; then
-        autoPatchelf -- $(for output in $outputs; do
+        autoPatchelf -- $(for output in $(getAllOutputNames); do
             [ -e "${!output}" ] || continue
             echo "${!output}"
         done)
diff --git a/pkgs/build-support/setup-hooks/move-docs.sh b/pkgs/build-support/setup-hooks/move-docs.sh
index e4460f98816da..833113aa0fc81 100644
--- a/pkgs/build-support/setup-hooks/move-docs.sh
+++ b/pkgs/build-support/setup-hooks/move-docs.sh
@@ -5,10 +5,17 @@
 preFixupHooks+=(_moveToShare)
 
 _moveToShare() {
-    forceShare=${forceShare:=man doc info}
+    if [ -n "$__structuredAttrs" ]; then
+        if [ -z "${forceShare-}" ]; then
+            forceShare=( man doc info )
+        fi
+    else
+        forceShare=( ${forceShare:-man doc info} )
+    fi
+
     if [[ -z "$out" ]]; then return; fi
 
-    for d in $forceShare; do
+    for d in "${forceShare[@]}"; do
         if [ -d "$out/$d" ]; then
             if [ -d "$out/share/$d" ]; then
                 echo "both $d/ and share/$d/ exist!"
@@ -20,4 +27,3 @@ _moveToShare() {
         fi
     done
 }
-
diff --git a/pkgs/build-support/setup-hooks/multiple-outputs.sh b/pkgs/build-support/setup-hooks/multiple-outputs.sh
index 881cf6c90f483..8a2fc2f915e91 100644
--- a/pkgs/build-support/setup-hooks/multiple-outputs.sh
+++ b/pkgs/build-support/setup-hooks/multiple-outputs.sh
@@ -47,7 +47,7 @@ _overrideFirst outputInfo "info" "$outputBin"
 
 # Add standard flags to put files into the desired outputs.
 _multioutConfig() {
-    if [ "$outputs" = "out" ] || [ -z "${setOutputFlags-1}" ]; then return; fi;
+    if [ "$(getAllOutputNames)" = "out" ] || [ -z "${setOutputFlags-1}" ]; then return; fi;
 
     # try to detect share/doc/${shareDocName}
     # Note: sadly, $configureScript detection comes later in configurePhase,
@@ -66,19 +66,17 @@ _multioutConfig() {
         fi
     fi
 
-    configureFlags="\
-        --bindir=${!outputBin}/bin --sbindir=${!outputBin}/sbin \
-        --includedir=${!outputInclude}/include --oldincludedir=${!outputInclude}/include \
-        --mandir=${!outputMan}/share/man --infodir=${!outputInfo}/share/info \
-        --docdir=${!outputDoc}/share/doc/${shareDocName} \
-        --libdir=${!outputLib}/lib --libexecdir=${!outputLib}/libexec \
-        --localedir=${!outputLib}/share/locale \
-        $configureFlags"
-
-    installFlags="\
-        pkgconfigdir=${!outputDev}/lib/pkgconfig \
-        m4datadir=${!outputDev}/share/aclocal aclocaldir=${!outputDev}/share/aclocal \
-        $installFlags"
+    prependToVar configureFlags \
+        --bindir="${!outputBin}"/bin --sbindir="${!outputBin}"/sbin \
+        --includedir="${!outputInclude}"/include --oldincludedir="${!outputInclude}"/include \
+        --mandir="${!outputMan}"/share/man --infodir="${!outputInfo}"/share/info \
+        --docdir="${!outputDoc}"/share/doc/"${shareDocName}" \
+        --libdir="${!outputLib}"/lib --libexecdir="${!outputLib}"/libexec \
+        --localedir="${!outputLib}"/share/locale
+
+    prependToVar installFlags \
+        pkgconfigdir="${!outputDev}"/lib/pkgconfig \
+        m4datadir="${!outputDev}"/share/aclocal aclocaldir="${!outputDev}"/share/aclocal
 }
 
 
@@ -94,7 +92,7 @@ moveToOutput() {
     local patt="$1"
     local dstOut="$2"
     local output
-    for output in $outputs; do
+    for output in $(getAllOutputNames); do
         if [ "${!output}" = "$dstOut" ]; then continue; fi
         local srcPath
         for srcPath in "${!output}"/$patt; do
@@ -149,7 +147,7 @@ _multioutDocs() {
 
 # Move development-only stuff to the desired outputs.
 _multioutDevs() {
-    if [ "$outputs" = "out" ] || [ -z "${moveToDev-1}" ]; then return; fi;
+    if [ "$(getAllOutputNames)" = "out" ] || [ -z "${moveToDev-1}" ]; then return; fi;
     moveToOutput include "${!outputInclude}"
     # these files are sometimes provided even without using the corresponding tool
     moveToOutput lib/pkgconfig "${!outputDev}"
@@ -166,10 +164,10 @@ _multioutDevs() {
 
 # Make the "dev" propagate other outputs needed for development.
 _multioutPropagateDev() {
-    if [ "$outputs" = "out" ]; then return; fi;
+    if [ "$(getAllOutputNames)" = "out" ]; then return; fi;
 
     local outputFirst
-    for outputFirst in $outputs; do
+    for outputFirst in $(getAllOutputNames); do
         break
     done
     local propagaterOutput="$outputDev"
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
index a450ecd7f963a..77322b245b277 100644
--- a/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
@@ -70,7 +70,7 @@ patchPpdFileCommands () {
     # * outputs of current build before buildInputs
     # * `/lib/cups/filter' before `/bin`
     # * add HOST_PATH at end, so we don't miss anything
-    for path in $outputs; do
+    for path in $(getAllOutputNames); do
         addToSearchPath cupspath "${!path}/lib/cups/filter"
         addToSearchPath cupspath "${!path}/bin"
     done
diff --git a/pkgs/build-support/setup-hooks/strip.sh b/pkgs/build-support/setup-hooks/strip.sh
index 9bd7b24cab545..104b5515b3db6 100644
--- a/pkgs/build-support/setup-hooks/strip.sh
+++ b/pkgs/build-support/setup-hooks/strip.sh
@@ -12,11 +12,20 @@ _doStrip() {
     local -ra stripCmds=(STRIP STRIP_FOR_TARGET)
     local -ra ranlibCmds=(RANLIB RANLIB_FOR_TARGET)
 
+    # TODO(structured-attrs): This doesn't work correctly if one of
+    #   the items in strip*List or strip*Flags contains a space,
+    #   even with structured attrs enabled.  This is OK for now
+    #   because very few packages set any of these, and it doesn't
+    #   affect any of them.
+    #
+    #   After __structuredAttrs = true is universal, come back and
+    #   push arrays all the way through this logic.
+
     # Strip only host paths by default. Leave targets as is.
-    stripDebugList=${stripDebugList:-lib lib32 lib64 libexec bin sbin}
-    stripDebugListTarget=${stripDebugListTarget:-}
-    stripAllList=${stripAllList:-}
-    stripAllListTarget=${stripAllListTarget:-}
+    stripDebugList=${stripDebugList[*]:-lib lib32 lib64 libexec bin sbin}
+    stripDebugListTarget=${stripDebugListTarget[*]:-}
+    stripAllList=${stripAllList[*]:-}
+    stripAllListTarget=${stripAllListTarget[*]:-}
 
     local i
     for i in ${!stripCmds[@]}; do
@@ -30,8 +39,8 @@ _doStrip() {
         if [[ "${dontStrip-}" || "${flag-}" ]] || ! type -f "${stripCmd-}" 2>/dev/null
         then continue; fi
 
-        stripDirs "$stripCmd" "$ranlibCmd" "$debugDirList" "${stripDebugFlags:--S}"
-        stripDirs "$stripCmd" "$ranlibCmd" "$allDirList" "${stripAllFlags:--s}"
+        stripDirs "$stripCmd" "$ranlibCmd" "$debugDirList" "${stripDebugFlags[*]:--S}"
+        stripDirs "$stripCmd" "$ranlibCmd" "$allDirList" "${stripAllFlags[*]:--s}"
     done
 }
 
diff --git a/pkgs/build-support/setup-hooks/win-dll-link.sh b/pkgs/build-support/setup-hooks/win-dll-link.sh
index 6130f32bef86c..ca4cbb349b6c0 100644
--- a/pkgs/build-support/setup-hooks/win-dll-link.sh
+++ b/pkgs/build-support/setup-hooks/win-dll-link.sh
@@ -15,7 +15,7 @@ _linkDLLs() {
     #   prefix $PATH by currently-built outputs
     local DLLPATH=""
     local outName
-    for outName in $outputs; do
+    for outName in $(getAllOutputNames); do
         addToSearchPath DLLPATH "${!outName}/bin"
     done
     DLLPATH="$DLLPATH:$PATH"
diff --git a/pkgs/build-support/testers/expect-failure.sh b/pkgs/build-support/testers/expect-failure.sh
index 0e1bbe9a678c0..052ee85271766 100644
--- a/pkgs/build-support/testers/expect-failure.sh
+++ b/pkgs/build-support/testers/expect-failure.sh
@@ -35,7 +35,7 @@ echo "testBuildFailure: Original builder produced exit code: $r"
 # -----------------------------------------
 # Write the build log to the default output
 
-outs=( $outputs )
+outs=( $(getAllOutputNames) )
 defOut=${outs[0]}
 defOutPath=${!defOut}
 
diff --git a/pkgs/data/icons/catppuccin-cursors/default.nix b/pkgs/data/icons/catppuccin-cursors/default.nix
index d9eccc96a0291..4559705c169b2 100644
--- a/pkgs/data/icons/catppuccin-cursors/default.nix
+++ b/pkgs/data/icons/catppuccin-cursors/default.nix
@@ -50,7 +50,7 @@ stdenvNoCC.mkDerivation {
   installPhase = ''
     runHook preInstall
 
-    for output in $outputs; do
+    for output in $(getAllOutputNames); do
       if [ "$output" != "out" ]; then
         local outputDir="''${!output}"
         local iconsDir="$outputDir"/share/icons
diff --git a/pkgs/data/icons/comixcursors/default.nix b/pkgs/data/icons/comixcursors/default.nix
index b63877b282098..1c4fdc195180b 100644
--- a/pkgs/data/icons/comixcursors/default.nix
+++ b/pkgs/data/icons/comixcursors/default.nix
@@ -52,7 +52,7 @@ stdenvNoCC.mkDerivation rec {
   '';
 
   installPhase = ''
-    for outputName in $outputs ; do
+    for outputName in $(getAllOutputNames) ; do
       if [ $outputName != out ]; then
         local outputDir=''${!outputName};
         local iconsDir=$outputDir/share/icons
diff --git a/pkgs/desktops/gnome/core/gdm/default.nix b/pkgs/desktops/gnome/core/gdm/default.nix
index f1fbe7e49d305..a2265387e1ef7 100644
--- a/pkgs/desktops/gnome/core/gdm/default.nix
+++ b/pkgs/desktops/gnome/core/gdm/default.nix
@@ -143,7 +143,7 @@ stdenv.mkDerivation rec {
     # We use rsync to merge the directories.
     rsync --archive "${DESTDIR}/etc" "$out"
     rm --recursive "${DESTDIR}/etc"
-    for o in $outputs; do
+    for o in $(getAllOutputNames); do
         if [[ "$o" = "debug" ]]; then continue; fi
         rsync --archive "${DESTDIR}/''${!o}" "$(dirname "''${!o}")"
         rm --recursive "${DESTDIR}/''${!o}"
diff --git a/pkgs/desktops/gnustep/make/builder.sh b/pkgs/desktops/gnustep/make/builder.sh
index 39bd77038281c..e5c277e796afd 100644
--- a/pkgs/desktops/gnustep/make/builder.sh
+++ b/pkgs/desktops/gnustep/make/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 preConfigure() {
diff --git a/pkgs/development/compilers/aspectj/builder.sh b/pkgs/development/compilers/aspectj/builder.sh
index 3b43937200423..7ea0a40d37484 100755
--- a/pkgs/development/compilers/aspectj/builder.sh
+++ b/pkgs/development/compilers/aspectj/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 export JAVA_HOME=$jre
diff --git a/pkgs/development/compilers/chicken/4/fetchegg/builder.sh b/pkgs/development/compilers/chicken/4/fetchegg/builder.sh
index 2046610630905..5f41a36263a9c 100644
--- a/pkgs/development/compilers/chicken/4/fetchegg/builder.sh
+++ b/pkgs/development/compilers/chicken/4/fetchegg/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 header "exporting egg ${eggName} (version $version) into $out"
diff --git a/pkgs/development/compilers/chicken/5/fetchegg/builder.sh b/pkgs/development/compilers/chicken/5/fetchegg/builder.sh
index d9adf510f22d5..f02e017577873 100644
--- a/pkgs/development/compilers/chicken/5/fetchegg/builder.sh
+++ b/pkgs/development/compilers/chicken/5/fetchegg/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 header "exporting egg ${eggName} (version $version) into $out"
diff --git a/pkgs/development/compilers/fpc/binary-builder-darwin.sh b/pkgs/development/compilers/fpc/binary-builder-darwin.sh
index f9bdf18e7d682..39db0518281da 100755
--- a/pkgs/development/compilers/fpc/binary-builder-darwin.sh
+++ b/pkgs/development/compilers/fpc/binary-builder-darwin.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 pkgdir=$(pwd)/pkg
diff --git a/pkgs/development/compilers/fpc/binary-builder.sh b/pkgs/development/compilers/fpc/binary-builder.sh
index 4308c1ed211ae..c471378c275f5 100755
--- a/pkgs/development/compilers/fpc/binary-builder.sh
+++ b/pkgs/development/compilers/fpc/binary-builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 tar xf $src
diff --git a/pkgs/development/compilers/gcc/builder.sh b/pkgs/development/compilers/gcc/builder.sh
index c73e9e0c10759..113bd83ea53fc 100644
--- a/pkgs/development/compilers/gcc/builder.sh
+++ b/pkgs/development/compilers/gcc/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 
diff --git a/pkgs/development/compilers/ios-cross-compile/9.2_builder.sh b/pkgs/development/compilers/ios-cross-compile/9.2_builder.sh
index 68ba3ed3a920a..47459664af0a5 100644
--- a/pkgs/development/compilers/ios-cross-compile/9.2_builder.sh
+++ b/pkgs/development/compilers/ios-cross-compile/9.2_builder.sh
@@ -1,4 +1,5 @@
 # -*- shell-script -*-
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 function extract
diff --git a/pkgs/development/compilers/ocaml/builder.sh b/pkgs/development/compilers/ocaml/builder.sh
index a1807682d8674..88acc0654cf2b 100644
--- a/pkgs/development/compilers/ocaml/builder.sh
+++ b/pkgs/development/compilers/ocaml/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 configureFlags="-prefix $out $configureFlags"
diff --git a/pkgs/development/compilers/openjdk/11.nix b/pkgs/development/compilers/openjdk/11.nix
index 820469ab8f156..850902cfd8043 100644
--- a/pkgs/development/compilers/openjdk/11.nix
+++ b/pkgs/development/compilers/openjdk/11.nix
@@ -131,12 +131,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/12.nix b/pkgs/development/compilers/openjdk/12.nix
index a8de9fe43ffc6..bd3defbd6b98c 100644
--- a/pkgs/development/compilers/openjdk/12.nix
+++ b/pkgs/development/compilers/openjdk/12.nix
@@ -135,12 +135,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/13.nix b/pkgs/development/compilers/openjdk/13.nix
index 5b7e87b0ef3ac..40dc753f7955c 100644
--- a/pkgs/development/compilers/openjdk/13.nix
+++ b/pkgs/development/compilers/openjdk/13.nix
@@ -135,12 +135,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/14.nix b/pkgs/development/compilers/openjdk/14.nix
index 1381b0995a76c..047e1a3908191 100644
--- a/pkgs/development/compilers/openjdk/14.nix
+++ b/pkgs/development/compilers/openjdk/14.nix
@@ -131,12 +131,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/15.nix b/pkgs/development/compilers/openjdk/15.nix
index 6ea1d0b1dd31c..c33e937f9f24b 100644
--- a/pkgs/development/compilers/openjdk/15.nix
+++ b/pkgs/development/compilers/openjdk/15.nix
@@ -131,12 +131,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/16.nix b/pkgs/development/compilers/openjdk/16.nix
index 0e1911bb1a703..461cd724144ed 100644
--- a/pkgs/development/compilers/openjdk/16.nix
+++ b/pkgs/development/compilers/openjdk/16.nix
@@ -138,12 +138,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/17.nix b/pkgs/development/compilers/openjdk/17.nix
index bc92b1393fdb3..6d8087d7e948d 100644
--- a/pkgs/development/compilers/openjdk/17.nix
+++ b/pkgs/development/compilers/openjdk/17.nix
@@ -149,12 +149,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort -u | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/18.nix b/pkgs/development/compilers/openjdk/18.nix
index 5be60eb948754..fd620aaaf9a55 100644
--- a/pkgs/development/compilers/openjdk/18.nix
+++ b/pkgs/development/compilers/openjdk/18.nix
@@ -140,12 +140,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort -u | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/19.nix b/pkgs/development/compilers/openjdk/19.nix
index 11b2fa60c7332..9537b0d3ce52a 100644
--- a/pkgs/development/compilers/openjdk/19.nix
+++ b/pkgs/development/compilers/openjdk/19.nix
@@ -140,12 +140,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort -u | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/compilers/openjdk/8.nix b/pkgs/development/compilers/openjdk/8.nix
index c232b1f01f16d..5558a77ad90b6 100644
--- a/pkgs/development/compilers/openjdk/8.nix
+++ b/pkgs/development/compilers/openjdk/8.nix
@@ -187,12 +187,12 @@ let
     postFixup = ''
       # Build the set of output library directories to rpath against
       LIBDIRS=""
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         LIBDIRS="$(find $(eval echo \$$output) -name \*.so\* -exec dirname {} \+ | sort | uniq | tr '\n' ':'):$LIBDIRS"
       done
       # Add the local library paths to remove dependencies on the bootstrap
-      for output in $outputs; do
+      for output in $(getAllOutputNames); do
         if [ "$output" = debug ]; then continue; fi
         OUTPUTDIR=$(eval echo \$$output)
         BINLIBS=$(find $OUTPUTDIR/bin/ -type f; find $OUTPUTDIR -name \*.so\*)
diff --git a/pkgs/development/interpreters/python/cpython/default.nix b/pkgs/development/interpreters/python/cpython/default.nix
index 5dca71f7eefde..067c030975e0c 100644
--- a/pkgs/development/interpreters/python/cpython/default.nix
+++ b/pkgs/development/interpreters/python/cpython/default.nix
@@ -281,15 +281,17 @@ in with passthru; stdenv.mkDerivation {
     substituteInPlace "Lib/tkinter/tix.py" --replace "os.environ.get('TIX_LIBRARY')" "os.environ.get('TIX_LIBRARY') or '${tix}/lib'"
   '';
 
-  CPPFLAGS = concatStringsSep " " (map (p: "-I${getDev p}/include") buildInputs);
-  LDFLAGS = concatStringsSep " " (map (p: "-L${getLib p}/lib") buildInputs);
-  LIBS = "${optionalString (!stdenv.isDarwin) "-lcrypt"}";
-  NIX_LDFLAGS = lib.optionalString (stdenv.cc.isGNU && !stdenv.hostPlatform.isStatic) ({
-    "glibc" = "-lgcc_s";
-    "musl" = "-lgcc_eh";
-  }."${stdenv.hostPlatform.libc}" or "");
-  # Determinism: We fix the hashes of str, bytes and datetime objects.
-  PYTHONHASHSEED=0;
+  env = {
+    CPPFLAGS = concatStringsSep " " (map (p: "-I${getDev p}/include") buildInputs);
+    LDFLAGS = concatStringsSep " " (map (p: "-L${getLib p}/lib") buildInputs);
+    LIBS = "${optionalString (!stdenv.isDarwin) "-lcrypt"}";
+    NIX_LDFLAGS = lib.optionalString (stdenv.cc.isGNU && !stdenv.hostPlatform.isStatic) ({
+      "glibc" = "-lgcc_s";
+      "musl" = "-lgcc_eh";
+    }."${stdenv.hostPlatform.libc}" or "");
+    # Determinism: We fix the hashes of str, bytes and datetime objects.
+    PYTHONHASHSEED=0;
+  };
 
   configureFlags = [
     "--without-ensurepip"
diff --git a/pkgs/development/interpreters/python/setup-hook.nix b/pkgs/development/interpreters/python/setup-hook.nix
index 29ce079317f03..8cfb9dd46784d 100644
--- a/pkgs/development/interpreters/python/setup-hook.nix
+++ b/pkgs/development/interpreters/python/setup-hook.nix
@@ -6,7 +6,9 @@ let
   hook = ./setup-hook.sh;
 in runCommand "python-setup-hook.sh" {
   strictDeps = true;
-  inherit sitePackages;
+  env = {
+    inherit sitePackages;
+  };
 } ''
   cp ${hook} hook.sh
   substituteAllInPlace hook.sh
diff --git a/pkgs/development/libraries/gettext/default.nix b/pkgs/development/libraries/gettext/default.nix
index a1270af259cda..ae5b9f48293a3 100644
--- a/pkgs/development/libraries/gettext/default.nix
+++ b/pkgs/development/libraries/gettext/default.nix
@@ -60,7 +60,9 @@ stdenv.mkDerivation rec {
     ../../../build-support/setup-hooks/role.bash
     ./gettext-setup-hook.sh
   ];
-  gettextNeedsLdflags = stdenv.hostPlatform.libc != "glibc" && !stdenv.hostPlatform.isMusl;
+  env = {
+    gettextNeedsLdflags = stdenv.hostPlatform.libc != "glibc" && !stdenv.hostPlatform.isMusl;
+  };
 
   enableParallelBuilding = true;
   enableParallelChecking = false; # fails sometimes
diff --git a/pkgs/development/libraries/glibc/common.nix b/pkgs/development/libraries/glibc/common.nix
index 61c0c92d5280a..889ca7bf48fd6 100644
--- a/pkgs/development/libraries/glibc/common.nix
+++ b/pkgs/development/libraries/glibc/common.nix
@@ -54,9 +54,6 @@ assert withGd -> gd != null && libpng != null;
 
 stdenv.mkDerivation ({
   version = version + patchSuffix;
-  linuxHeaders = if withLinuxHeaders then linuxHeaders else null;
-
-  inherit (stdenv) is64bit;
 
   enableParallelBuilding = true;
 
@@ -175,10 +172,14 @@ stdenv.mkDerivation ({
   nativeBuildInputs = [ bison python3Minimal ] ++ extraNativeBuildInputs;
   buildInputs = [ linuxHeaders ] ++ lib.optionals withGd [ gd libpng ] ++ extraBuildInputs;
 
-  # Needed to install share/zoneinfo/zone.tab.  Set to impure /bin/sh to
-  # prevent a retained dependency on the bootstrap tools in the stdenv-linux
-  # bootstrap.
-  BASH_SHELL = "/bin/sh";
+  env = {
+    linuxHeaders = if withLinuxHeaders then linuxHeaders else "";
+    inherit (stdenv) is64bit;
+    # Needed to install share/zoneinfo/zone.tab.  Set to impure /bin/sh to
+    # prevent a retained dependency on the bootstrap tools in the stdenv-linux
+    # bootstrap.
+    BASH_SHELL = "/bin/sh";
+  };
 
   # Used by libgcc, elf-header, and others to determine ABI
   passthru = { inherit version; minorRelease = version; };
diff --git a/pkgs/development/libraries/glibc/locales-builder.sh b/pkgs/development/libraries/glibc/locales-builder.sh
index d732e208fa22a..d91f936c937b1 100644
--- a/pkgs/development/libraries/glibc/locales-builder.sh
+++ b/pkgs/development/libraries/glibc/locales-builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 # Glibc cannot have itself in its RPATH.
 export NIX_NO_SELF_RPATH=1
 
diff --git a/pkgs/development/libraries/gtk-sharp/builder.sh b/pkgs/development/libraries/gtk-sharp/builder.sh
index 4b8f757540b55..73914495d6d41 100644
--- a/pkgs/development/libraries/gtk-sharp/builder.sh
+++ b/pkgs/development/libraries/gtk-sharp/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 genericBuild
diff --git a/pkgs/development/libraries/polkit/default.nix b/pkgs/development/libraries/polkit/default.nix
index a0344d68a12b5..6ab7a4bb9c3a7 100644
--- a/pkgs/development/libraries/polkit/default.nix
+++ b/pkgs/development/libraries/polkit/default.nix
@@ -167,7 +167,7 @@ stdenv.mkDerivation rec {
     rsync --archive "${DESTDIR}${system}"/* "$out"
     rm --recursive "${DESTDIR}${system}"/*
     rmdir --parents --ignore-fail-on-non-empty "${DESTDIR}${system}"
-    for o in $outputs; do
+    for o in $(getAllOutputNames); do
         rsync --archive "${DESTDIR}/''${!o}" "$(dirname "''${!o}")"
         rm --recursive "${DESTDIR}/''${!o}"
     done
diff --git a/pkgs/development/libraries/wtk/builder.sh b/pkgs/development/libraries/wtk/builder.sh
index 86f2719537cd0..c3ad173b09338 100644
--- a/pkgs/development/libraries/wtk/builder.sh
+++ b/pkgs/development/libraries/wtk/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 mkdir unzipped
diff --git a/pkgs/development/libraries/xapian/default.nix b/pkgs/development/libraries/xapian/default.nix
index 700fa1d13dd12..3c0a555dd9780 100644
--- a/pkgs/development/libraries/xapian/default.nix
+++ b/pkgs/development/libraries/xapian/default.nix
@@ -26,7 +26,7 @@ let
     nativeBuildInputs = [ autoreconfHook ];
 
     doCheck = true;
-    AUTOMATED_TESTING = true; # https://trac.xapian.org/changeset/8be35f5e1/git
+    env.AUTOMATED_TESTING = true; # https://trac.xapian.org/changeset/8be35f5e1/git
 
     patches = lib.optionals stdenv.isDarwin [ ./skip-flaky-darwin-test.patch ];
 
diff --git a/pkgs/development/nim-packages/fetch-nimble/builder.sh b/pkgs/development/nim-packages/fetch-nimble/builder.sh
index 693ab339408ed..bc2f9bfc94f1b 100644
--- a/pkgs/development/nim-packages/fetch-nimble/builder.sh
+++ b/pkgs/development/nim-packages/fetch-nimble/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 export HOME=$NIX_BUILD_TOP
 
diff --git a/pkgs/development/perl-modules/generic/builder.sh b/pkgs/development/perl-modules/generic/builder.sh
index 9b42401fc4dcd..110094ad8a494 100644
--- a/pkgs/development/perl-modules/generic/builder.sh
+++ b/pkgs/development/perl-modules/generic/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 PERL5LIB="$PERL5LIB${PERL5LIB:+:}$out/lib/perl5/site_perl"
diff --git a/pkgs/development/perl-modules/generic/default.nix b/pkgs/development/perl-modules/generic/default.nix
index 2d1c550d3168c..3dca6550ad688 100644
--- a/pkgs/development/perl-modules/generic/default.nix
+++ b/pkgs/development/perl-modules/generic/default.nix
@@ -24,6 +24,8 @@
 # https://metacpan.org/pod/release/XSAWYERX/perl-5.26.0/pod/perldelta.pod#Removal-of-the-current-directory-%28%22.%22%29-from-@INC
 , PERL_USE_UNSAFE_INC ? "1"
 
+, env ? {}
+
 , ...
 }@attrs:
 
@@ -43,10 +45,11 @@ lib.throwIf (attrs ? name) "buildPerlPackage: `name` (\"${attrs.name}\") is depr
     buildInputs = buildInputs ++ [ perl ];
     nativeBuildInputs = nativeBuildInputs ++ [ (perl.mini or perl) ];
 
-    fullperl = buildPerl;
-
     inherit outputs src doCheck checkTarget enableParallelBuilding;
-    inherit PERL_AUTOINSTALL AUTOMATED_TESTING PERL_USE_UNSAFE_INC;
+    env = {
+      inherit PERL_AUTOINSTALL AUTOMATED_TESTING PERL_USE_UNSAFE_INC;
+      fullperl = buildPerl;
+    } // env;
 
     meta = defaultMeta // (attrs.meta or { });
   });
diff --git a/pkgs/development/tools/build-managers/apache-maven/builder.sh b/pkgs/development/tools/build-managers/apache-maven/builder.sh
index dcc38b9ec74a1..96fe8ebfac295 100644
--- a/pkgs/development/tools/build-managers/apache-maven/builder.sh
+++ b/pkgs/development/tools/build-managers/apache-maven/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 unpackPhase
diff --git a/pkgs/development/tools/build-managers/boot/builder.sh b/pkgs/development/tools/build-managers/boot/builder.sh
index c1481dc6a1445..e007cbac9582d 100644
--- a/pkgs/development/tools/build-managers/boot/builder.sh
+++ b/pkgs/development/tools/build-managers/boot/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 boot_bin=$out/bin/boot
diff --git a/pkgs/development/tools/misc/automake/builder.sh b/pkgs/development/tools/misc/automake/builder.sh
index e54a2acca67e8..0cb1d5d61e33b 100644
--- a/pkgs/development/tools/misc/automake/builder.sh
+++ b/pkgs/development/tools/misc/automake/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 # Wrap the given `aclocal' program, appending extra `-I' flags
diff --git a/pkgs/development/tools/parsing/antlr/builder.sh b/pkgs/development/tools/parsing/antlr/builder.sh
index b8e7791b6fc9b..55259b9321249 100644
--- a/pkgs/development/tools/parsing/antlr/builder.sh
+++ b/pkgs/development/tools/parsing/antlr/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 tar zxvf $src
diff --git a/pkgs/misc/cups/drivers/samsung/4.00.39/builder.sh b/pkgs/misc/cups/drivers/samsung/4.00.39/builder.sh
index f750df6e50639..bdb522744182e 100644
--- a/pkgs/misc/cups/drivers/samsung/4.00.39/builder.sh
+++ b/pkgs/misc/cups/drivers/samsung/4.00.39/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 arch=$(uname -m)
diff --git a/pkgs/os-specific/darwin/signing-utils/auto-sign-hook.sh b/pkgs/os-specific/darwin/signing-utils/auto-sign-hook.sh
index cca65661f8a9c..6a254cd821236 100644
--- a/pkgs/os-specific/darwin/signing-utils/auto-sign-hook.sh
+++ b/pkgs/os-specific/darwin/signing-utils/auto-sign-hook.sh
@@ -25,7 +25,7 @@ signDarwinBinariesIn() {
 signDarwinBinariesInAllOutputs() {
   local output
 
-  for output in $outputs; do
+  for output in $(getAllOutputNames); do
      signDarwinBinariesIn "${!output}"
   done
 }
diff --git a/pkgs/os-specific/linux/nvidia-x11/builder.sh b/pkgs/os-specific/linux/nvidia-x11/builder.sh
index eadf88fd11680..1cf1400f996b7 100755
--- a/pkgs/os-specific/linux/nvidia-x11/builder.sh
+++ b/pkgs/os-specific/linux/nvidia-x11/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 unpackManually() {
diff --git a/pkgs/os-specific/linux/opengl/xorg-sys/builder.sh b/pkgs/os-specific/linux/opengl/xorg-sys/builder.sh
index cd21899e60e7d..34f9b15794557 100644
--- a/pkgs/os-specific/linux/opengl/xorg-sys/builder.sh
+++ b/pkgs/os-specific/linux/opengl/xorg-sys/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/lib
diff --git a/pkgs/servers/http/tomcat/axis2/builder.sh b/pkgs/servers/http/tomcat/axis2/builder.sh
index 2e36367e9dcf5..d334ab6f927f4 100644
--- a/pkgs/servers/http/tomcat/axis2/builder.sh
+++ b/pkgs/servers/http/tomcat/axis2/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 source $stdenv/setup
 
 unzip $src
diff --git a/pkgs/servers/monitoring/munin/default.nix b/pkgs/servers/monitoring/munin/default.nix
index 0bfc868167cb6..2016bbe9ed3bb 100644
--- a/pkgs/servers/monitoring/munin/default.nix
+++ b/pkgs/servers/monitoring/munin/default.nix
@@ -55,7 +55,7 @@ stdenv.mkDerivation rec {
   ];
 
   # needs to find a local perl module during build
-  PERL_USE_UNSAFE_INC = "1";
+  env.PERL_USE_UNSAFE_INC = "1";
 
   # TODO: tests are failing https://munin-monitoring.org/ticket/1390#comment:1
   # NOTE: important, test command always exits with 0, think of a way to abort the build once tests pass
diff --git a/pkgs/servers/x11/xorg/builder.sh b/pkgs/servers/x11/xorg/builder.sh
index 5a832cb14d539..9ee81091584b5 100644
--- a/pkgs/servers/x11/xorg/builder.sh
+++ b/pkgs/servers/x11/xorg/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 # This is the builder for all X.org components.
 source $stdenv/setup
 
diff --git a/pkgs/stdenv/darwin/default.nix b/pkgs/stdenv/darwin/default.nix
index 9a7cd9aa9dee5..76c44870f4ad6 100644
--- a/pkgs/stdenv/darwin/default.nix
+++ b/pkgs/stdenv/darwin/default.nix
@@ -262,11 +262,12 @@ rec {
           ln -s ${bootstrapTools}/bin/rewrite-tbd $out/bin
         '';
 
-        binutils-unwrapped = { name = "bootstrap-stage0-binutils"; outPath = bootstrapTools; };
+        binutils-unwrapped = bootstrapTools // {
+          name = "bootstrap-stage0-binutils";
+        };
 
-        cctools = {
+        cctools = bootstrapTools // {
           name = "bootstrap-stage0-cctools";
-          outPath = bootstrapTools;
           targetPrefix = "";
         };
 
diff --git a/pkgs/stdenv/generic/default-builder.sh b/pkgs/stdenv/generic/default-builder.sh
index 273fc55c75523..8c6fec7873b6e 100644
--- a/pkgs/stdenv/generic/default-builder.sh
+++ b/pkgs/stdenv/generic/default-builder.sh
@@ -1,2 +1,6 @@
+if [ -f .attrs.sh ]; then
+    . .attrs.sh
+fi
+
 source $stdenv/setup
 genericBuild
diff --git a/pkgs/stdenv/generic/make-derivation.nix b/pkgs/stdenv/generic/make-derivation.nix
index 510537aac9f39..12aa25ac307c1 100644
--- a/pkgs/stdenv/generic/make-derivation.nix
+++ b/pkgs/stdenv/generic/make-derivation.nix
@@ -154,6 +154,12 @@ let
   (! attrs ? outputHash) # Fixed-output drvs can't be content addressed too
   && config.contentAddressedByDefault
 
+# Experimental.  For simple packages mostly just works,
+# but for anything complex, be prepared to debug if enabling.
+, __structuredAttrs ? config.structuredAttrsByDefault or false
+
+, env ? { }
+
 , ... } @ attrs:
 
 let
@@ -259,13 +265,16 @@ else let
     lib.unique (lib.concatMap (input: input.__propagatedImpureHostDeps or [])
       (lib.concatLists propagatedDependencies));
 
+  envIsExportable = lib.isAttrs env && !lib.isDerivation env;
+
   derivationArg =
     (removeAttrs attrs
-      ["meta" "passthru" "pos"
+      (["meta" "passthru" "pos"
        "checkInputs" "installCheckInputs"
        "__darwinAllowLocalNetworking"
        "__impureHostDeps" "__propagatedImpureHostDeps"
-       "sandboxProfile" "propagatedSandboxProfile"])
+       "sandboxProfile" "propagatedSandboxProfile"]
+       ++ lib.optionals envIsExportable [ "env" ]))
     // (lib.optionalAttrs (attrs ? name || (attrs ? pname && attrs ? version)) {
       name =
         let
@@ -289,7 +298,7 @@ else let
           then attrs.name + hostSuffix
           else "${attrs.pname}${staticMarker}${hostSuffix}-${attrs.version}"
         );
-    }) // {
+    }) // lib.optionalAttrs (envIsExportable && __structuredAttrs) { env = checkedEnv; } // {
       builder = attrs.realBuilder or stdenv.shell;
       args = attrs.args or ["-e" (attrs.builder or ./default-builder.sh)];
       inherit stdenv;
@@ -304,8 +313,7 @@ else let
 
       userHook = config.stdenv.userHook or null;
       __ignoreNulls = true;
-
-      inherit strictDeps;
+      inherit __structuredAttrs strictDeps;
 
       depsBuildBuild              = lib.elemAt (lib.elemAt dependencies 0) 0;
       nativeBuildInputs           = lib.elemAt (lib.elemAt dependencies 0) 1;
@@ -473,6 +481,17 @@ else let
                    else true);
     };
 
+  checkedEnv =
+    let
+      overlappingNames = lib.intersectLists (lib.attrNames env) (lib.attrNames derivationArg);
+    in
+    assert lib.assertMsg (overlappingNames == [ ])
+      "The ‘env’ attribute set cannot contain any attributes passed to derivation. The following attributes are overlapping: ${lib.concatStringsSep ", " overlappingNames}";
+    lib.mapAttrs
+      (n: v: assert lib.assertMsg (lib.isString v || lib.isBool v || lib.isInt v || lib.isDerivation v)
+        "The ‘env’ attribute set can only contain derivation, string, boolean or integer attributes. The ‘${n}’ attribute is of type ${builtins.typeOf v}."; v)
+      env;
+
 in
 
 lib.extendDerivation
@@ -509,7 +528,7 @@ lib.extendDerivation
    # should be made available to Nix expressions using the
    # derivation (e.g., in assertions).
    passthru)
-  (derivation derivationArg);
+  (derivation (derivationArg // lib.optionalAttrs envIsExportable checkedEnv));
 
 in
   fnOrAttrs:
diff --git a/pkgs/stdenv/generic/setup.sh b/pkgs/stdenv/generic/setup.sh
index c6cdb6c3df750..5a64625e10d1e 100644
--- a/pkgs/stdenv/generic/setup.sh
+++ b/pkgs/stdenv/generic/setup.sh
@@ -15,8 +15,33 @@ if (( "${NIX_DEBUG:-0}" >= 6 )); then
     set -x
 fi
 
-: ${outputs:=out}
+if [ -f .attrs.sh ]; then
+    __structuredAttrs=1
+    echo "structuredAttrs is enabled"
+else
+    __structuredAttrs=
+fi
 
+if [ -n "$__structuredAttrs" ]; then
+    for outputName in "${!outputs[@]}"; do
+        # ex: out=/nix/store/...
+        export "$outputName=${outputs[$outputName]}"
+    done
+    # $NIX_ATTRS_JSON_FILE points to the wrong location in sandbox
+    # https://github.com/NixOS/nix/issues/6736
+    export NIX_ATTRS_JSON_FILE="$NIX_BUILD_TOP/.attrs.json"
+    export NIX_ATTRS_SH_FILE="$NIX_BUILD_TOP/.attrs.sh"
+else
+    : ${outputs:=out}
+fi
+
+getAllOutputNames() {
+    if [ -n "$__structuredAttrs" ]; then
+        echo "${!outputs[*]}"
+    else
+        echo "$outputs"
+    fi
+}
 
 ######################################################################
 # Hook handling.
@@ -175,6 +200,109 @@ addToSearchPath() {
     addToSearchPathWithCustomDelimiter ":" "$@"
 }
 
+# Prepend elements to variable "$1", which may come from an attr.
+#
+# This is useful in generic setup code, which must (for now) support
+# both derivations with and without __structuredAttrs true, so the
+# variable may be an array or a space-separated string.
+#
+# Expressions for individual packages should simply switch to array
+# syntax when they switch to setting __structuredAttrs = true.
+prependToVar() {
+    local -n nameref="$1"
+
+    useArray=
+    if [ -n "$__structuredAttrs" ]; then
+        useArray=true
+    else
+        useArray=false
+    fi
+
+    # check if variable already exist and if it does then do extra checks
+    if declare -p "$1" 2> /dev/null | grep -q '^'; then
+        type="$(declare -p "$1")"
+        if [[ "$type" =~ "declare -A" ]]; then
+            echo "prependToVar(): ERROR: trying to use prependToVar on an associative array." >&2
+            return 1
+        elif [[ "$type" =~ "declare -a" ]]; then
+            useArray=true
+        else
+            useArray=false
+        fi
+    fi
+
+    shift
+
+    if $useArray; then
+        nameref=( "$@" ${nameref+"${nameref[@]}"} )
+    else
+        nameref="$* ${nameref-}"
+    fi
+}
+
+# Same as above
+appendToVar() {
+    local -n nameref="$1"
+
+    useArray=
+    if [ -n "$__structuredAttrs" ]; then
+        useArray=true
+    else
+        useArray=false
+    fi
+
+    # check if variable already exist and if it does then do extra checks
+    if declare -p "$1" 2> /dev/null | grep -q '^'; then
+        type="$(declare -p "$1")"
+        if [[ "$type" =~ "declare -A" ]]; then
+            echo "appendToVar(): ERROR: trying to use appendToVar on an associative array, use variable+=([\"X\"]=\"Y\") instead." >&2
+            return 1
+        elif [[ "$type" =~ "declare -a" ]]; then
+            useArray=true
+        else
+            useArray=false
+        fi
+    fi
+
+    shift
+
+    if $useArray; then
+        nameref=( ${nameref+"${nameref[@]}"} "$@" )
+    else
+        nameref="${nameref-} $*"
+    fi
+}
+
+# Accumulate into `flagsArray` the flags from the named variables.
+#
+# If __structuredAttrs, the variables are all treated as arrays
+# and simply concatenated onto `flagsArray`.
+#
+# If not __structuredAttrs, then:
+#   * Each variable is treated as a string, and split on whitespace;
+#   * except variables whose names end in "Array", which are treated
+#     as arrays.
+_accumFlagsArray() {
+    local name
+    if [ -n "$__structuredAttrs" ]; then
+        for name in "$@"; do
+            local -n nameref="$name"
+            flagsArray+=( ${nameref+"${nameref[@]}"} )
+        done
+    else
+        for name in "$@"; do
+            local -n nameref="$name"
+            case "$name" in
+                *Array)
+                    flagsArray+=( ${nameref+"${nameref[@]}"} ) ;;
+                *)
+                    flagsArray+=( ${nameref-} ) ;;
+            esac
+        done
+    fi
+
+}
+
 # Add $1/lib* into rpaths.
 # The function is used in multiple-outputs.sh hook,
 # so it is defined here but tried after the hook.
@@ -255,6 +383,11 @@ printWords() {
 ######################################################################
 # Initialisation.
 
+# export all vars that should be in the ENV
+for envVar in "${!env[@]}"; do
+    declare -x "${envVar}=${env[${envVar}]}"
+done
+
 
 # Set a fallback default value for SOURCE_DATE_EPOCH, used by some build tools
 # to provide a deterministic substitute for the "current" time. Note that
@@ -469,6 +602,10 @@ findInputs() {
     done
 }
 
+# The way we handle deps* and *Inputs works with structured attrs
+# either enabled or disabled.  For this it's convenient that the items
+# in each list must be store paths, and therefore space-free.
+
 # Make sure all are at least defined as empty
 : ${depsBuildBuild=} ${depsBuildBuildPropagated=}
 : ${nativeBuildInputs=} ${propagatedNativeBuildInputs=} ${defaultNativeBuildInputs=}
@@ -477,29 +614,29 @@ findInputs() {
 : ${buildInputs=} ${propagatedBuildInputs=} ${defaultBuildInputs=}
 : ${depsTargetTarget=} ${depsTargetTargetPropagated=}
 
-for pkg in $depsBuildBuild $depsBuildBuildPropagated; do
+for pkg in ${depsBuildBuild[@]} ${depsBuildBuildPropagated[@]}; do
     findInputs "$pkg" -1 -1
 done
-for pkg in $nativeBuildInputs $propagatedNativeBuildInputs; do
+for pkg in ${nativeBuildInputs[@]} ${propagatedNativeBuildInputs[@]}; do
     findInputs "$pkg" -1  0
 done
-for pkg in $depsBuildTarget $depsBuildTargetPropagated; do
+for pkg in ${depsBuildTarget[@]} ${depsBuildTargetPropagated[@]}; do
     findInputs "$pkg" -1  1
 done
-for pkg in $depsHostHost $depsHostHostPropagated; do
+for pkg in ${depsHostHost[@]} ${depsHostHostPropagated[@]}; do
     findInputs "$pkg"  0  0
 done
-for pkg in $buildInputs $propagatedBuildInputs ; do
+for pkg in ${buildInputs[@]} ${propagatedBuildInputs[@]} ; do
     findInputs "$pkg"  0  1
 done
-for pkg in $depsTargetTarget $depsTargetTargetPropagated; do
+for pkg in ${depsTargetTarget[@]} ${depsTargetTargetPropagated[@]}; do
     findInputs "$pkg"  1  1
 done
 # Default inputs must be processed last
-for pkg in $defaultNativeBuildInputs; do
+for pkg in ${defaultNativeBuildInputs[@]}; do
     findInputs "$pkg" -1  0
 done
-for pkg in $defaultBuildInputs; do
+for pkg in ${defaultBuildInputs[@]}; do
     findInputs "$pkg"  0  1
 done
 
@@ -787,6 +924,10 @@ substituteInPlace() {
 }
 
 _allFlags() {
+    # export some local variables for the awk below
+    # so some substitutions such as name don't have to be in the env attrset
+    # when __structuredAttrs is enabled
+    export system pname name version
     for varName in $(awk 'BEGIN { for (v in ENVIRON) if (v ~ /^[a-z][a-zA-Z0-9_]*$/) print v }'); do
         if (( "${NIX_DEBUG:-0}" >= 1 )); then
             printf "@%s@ -> %q\n" "${varName}" "${!varName}"
@@ -909,6 +1050,13 @@ unpackPhase() {
         srcs="$src"
     fi
 
+    local -a srcsArray
+    if [ -n "$__structuredAttrs" ]; then
+        srcsArray=( "${srcs[@]}" )
+    else
+        srcsArray=( $srcs )
+    fi
+
     # To determine the source directory created by unpacking the
     # source archives, we record the contents of the current
     # directory, then look below which directory got added.  Yeah,
@@ -921,7 +1069,7 @@ unpackPhase() {
     done
 
     # Unpack all source archives.
-    for i in $srcs; do
+    for i in "${srcsArray[@]}"; do
         unpackFile "$i"
     done
 
@@ -971,7 +1119,14 @@ unpackPhase() {
 patchPhase() {
     runHook prePatch
 
-    for i in ${patches:-}; do
+    local -a patchesArray
+    if [ -n "$__structuredAttrs" ]; then
+        patchesArray=( ${patches:+"${patches[@]}"} )
+    else
+        patchesArray=( ${patches:-} )
+    fi
+
+    for i in "${patchesArray[@]}"; do
         header "applying patch $i" 3
         local uncompress=cat
         case "$i" in
@@ -988,9 +1143,17 @@ patchPhase() {
                 uncompress="lzma -d"
                 ;;
         esac
+
+        local -a flagsArray
+        if [ -n "$__structuredAttrs" ]; then
+            flagsArray=( "${patchFlags[@]:--p1}" )
+        else
+            # shellcheck disable=SC2086
+            flagsArray=( ${patchFlags:--p1} )
+        fi
         # "2>&1" is a hack to make patch fail if the decompressor fails (nonexistent patch, etc.)
         # shellcheck disable=SC2086
-        $uncompress < "$i" 2>&1 | patch ${patchFlags:--p1}
+        $uncompress < "$i" 2>&1 | patch "${flagsArray[@]}"
     done
 
     runHook postPatch
@@ -1018,7 +1181,6 @@ configurePhase() {
 
     # set to empty if unset
     : ${configureScript=}
-    : ${configureFlags=}
 
     if [[ -z "$configureScript" && -x ./configure ]]; then
         configureScript=./configure
@@ -1049,31 +1211,29 @@ configurePhase() {
     fi
 
     if [[ -z "${dontAddPrefix:-}" && -n "$prefix" ]]; then
-        configureFlags="${prefixKey:---prefix=}$prefix $configureFlags"
+        prependToVar configureFlags "${prefixKey:---prefix=}$prefix"
     fi
 
     if [[ -f "$configureScript" ]]; then
         # Add --disable-dependency-tracking to speed up some builds.
         if [ -z "${dontAddDisableDepTrack:-}" ]; then
             if grep -q dependency-tracking "$configureScript"; then
-                configureFlags="--disable-dependency-tracking $configureFlags"
+                prependToVar configureFlags --disable-dependency-tracking
             fi
         fi
 
         # By default, disable static builds.
         if [ -z "${dontDisableStatic:-}" ]; then
             if grep -q enable-static "$configureScript"; then
-                configureFlags="--disable-static $configureFlags"
+                prependToVar configureFlags --disable-static
             fi
         fi
     fi
 
     if [ -n "$configureScript" ]; then
-        # Old bash empty array hack
-        # shellcheck disable=SC2086
-        local flagsArray=(
-            $configureFlags "${configureFlagsArray[@]}"
-        )
+        local -a flagsArray
+        _accumFlagsArray configureFlags configureFlagsArray
+
         echoCmd 'configure flags' "${flagsArray[@]}"
         # shellcheck disable=SC2086
         $configureScript "${flagsArray[@]}"
@@ -1089,22 +1249,17 @@ configurePhase() {
 buildPhase() {
     runHook preBuild
 
-    # set to empty if unset
-    : ${makeFlags=}
-
-    if [[ -z "$makeFlags" && -z "${makefile:-}" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
+    if [[ -z "${makeFlags-}" && -z "${makefile:-}" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
         echo "no Makefile, doing nothing"
     else
         foundMakefile=1
 
-        # Old bash empty array hack
         # shellcheck disable=SC2086
         local flagsArray=(
             ${enableParallelBuilding:+-j${NIX_BUILD_CORES}}
             SHELL=$SHELL
-            $makeFlags "${makeFlagsArray[@]}"
-            $buildFlags "${buildFlagsArray[@]}"
         )
+        _accumFlagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray
 
         echoCmd 'build flags' "${flagsArray[@]}"
         make ${makefile:+-f $makefile} "${flagsArray[@]}"
@@ -1141,11 +1296,17 @@ checkPhase() {
         local flagsArray=(
             ${enableParallelChecking:+-j${NIX_BUILD_CORES}}
             SHELL=$SHELL
-            $makeFlags "${makeFlagsArray[@]}"
-            ${checkFlags:-VERBOSE=y} "${checkFlagsArray[@]}"
-            ${checkTarget}
         )
 
+        _accumFlagsArray makeFlags makeFlagsArray
+        if [ -n "$__structuredAttrs" ]; then
+            flagsArray+=( "${checkFlags[@]:-VERBOSE=y}" )
+        else
+            flagsArray+=( ${checkFlags:-VERBOSE=y} )
+        fi
+        _accumFlagsArray checkFlagsArray
+        flagsArray+=( ${checkTarget} )
+
         echoCmd 'check flags' "${flagsArray[@]}"
         make ${makefile:+-f $makefile} "${flagsArray[@]}"
 
@@ -1163,14 +1324,16 @@ installPhase() {
         mkdir -p "$prefix"
     fi
 
-    # Old bash empty array hack
     # shellcheck disable=SC2086
     local flagsArray=(
         SHELL=$SHELL
-        $makeFlags "${makeFlagsArray[@]}"
-        $installFlags "${installFlagsArray[@]}"
-        ${installTargets:-install}
     )
+    _accumFlagsArray makeFlags makeFlagsArray installFlags installFlagsArray
+    if [ -n "$__structuredAttrs" ]; then
+        flagsArray+=( "${installTargets[@]:-install}" )
+    else
+        flagsArray+=( ${installTargets:-install} )
+    fi
 
     echoCmd 'install flags' "${flagsArray[@]}"
     make ${makefile:+-f $makefile} "${flagsArray[@]}"
@@ -1186,7 +1349,7 @@ installPhase() {
 fixupPhase() {
     # Make sure everything is writable so "strip" et al. work.
     local output
-    for output in $outputs; do
+    for output in $(getAllOutputNames); do
         if [ -e "${!output}" ]; then chmod -R u+w "${!output}"; fi
     done
 
@@ -1194,7 +1357,7 @@ fixupPhase() {
 
     # Apply fixup to each output.
     local output
-    for output in $outputs; do
+    for output in $(getAllOutputNames); do
         prefix="${!output}" runHook fixupOutput
     done
 
@@ -1239,7 +1402,10 @@ fixupPhase() {
     if [ -n "${setupHooks:-}" ]; then
         mkdir -p "${!outputDev}/nix-support"
         local hook
-        for hook in $setupHooks; do
+        # have to use ${setupHooks[@]} without quotes because it needs to support setupHooks being a array or a whitespace separated string
+        # # values of setupHooks won't have spaces so it won't cause problems
+        # shellcheck disable=2068
+        for hook in ${setupHooks[@]}; do
             local content
             consumeEntire content < "$hook"
             substituteAllStream content "file '$hook'" >> "${!outputDev}/nix-support/setup-hook"
@@ -1275,11 +1441,12 @@ installCheckPhase() {
         local flagsArray=(
             ${enableParallelChecking:+-j${NIX_BUILD_CORES}}
             SHELL=$SHELL
-            $makeFlags "${makeFlagsArray[@]}"
-            $installCheckFlags "${installCheckFlagsArray[@]}"
-            ${installCheckTarget:-installcheck}
         )
 
+        _accumFlagsArray makeFlags makeFlagsArray \
+          installCheckFlags installCheckFlagsArray
+        flagsArray+=( ${installCheckTarget:-installcheck} )
+
         echoCmd 'installcheck flags' "${flagsArray[@]}"
         make ${makefile:+-f $makefile} "${flagsArray[@]}"
         unset flagsArray
@@ -1292,11 +1459,9 @@ installCheckPhase() {
 distPhase() {
     runHook preDist
 
-    # Old bash empty array hack
-    # shellcheck disable=SC2086
-    local flagsArray=(
-        $distFlags "${distFlagsArray[@]}" ${distTarget:-dist}
-    )
+    local flagsArray=()
+    _accumFlagsArray distFlags distFlagsArray
+    flagsArray+=( ${distTarget:-dist} )
 
     echo 'dist flags: %q' "${flagsArray[@]}"
     make ${makefile:+-f $makefile} "${flagsArray[@]}"
@@ -1307,7 +1472,7 @@ distPhase() {
         # Note: don't quote $tarballs, since we explicitly permit
         # wildcards in there.
         # shellcheck disable=SC2086
-        cp -pvd ${tarballs:-*.tar.gz} "$out/tarballs"
+        cp -pvd ${tarballs[*]:-*.tar.gz} "$out/tarballs"
     fi
 
     runHook postDist
@@ -1357,14 +1522,18 @@ genericBuild() {
         return
     fi
 
-    if [ -z "${phases:-}" ]; then
-        phases="${prePhases:-} unpackPhase patchPhase ${preConfigurePhases:-} \
-            configurePhase ${preBuildPhases:-} buildPhase checkPhase \
-            ${preInstallPhases:-} installPhase ${preFixupPhases:-} fixupPhase installCheckPhase \
-            ${preDistPhases:-} distPhase ${postPhases:-}";
+    if [ -z "${phases[*]:-}" ]; then
+        phases="${prePhases[*]:-} unpackPhase patchPhase ${preConfigurePhases[*]:-} \
+            configurePhase ${preBuildPhases[*]:-} buildPhase checkPhase \
+            ${preInstallPhases[*]:-} installPhase ${preFixupPhases[*]:-} fixupPhase installCheckPhase \
+            ${preDistPhases[*]:-} distPhase ${postPhases[*]:-}";
     fi
 
-    for curPhase in $phases; do
+    # The use of ${phases[*]} gives the correct behavior both with and
+    # without structured attrs.  This relies on the fact that each
+    # phase name is space-free, which it must be because it's the name
+    # of either a shell variable or a shell function.
+    for curPhase in ${phases[*]}; do
         if [[ "$curPhase" = unpackPhase && -n "${dontUnpack:-}" ]]; then continue; fi
         if [[ "$curPhase" = patchPhase && -n "${dontPatch:-}" ]]; then continue; fi
         if [[ "$curPhase" = configurePhase && -n "${dontConfigure:-}" ]]; then continue; fi
@@ -1414,6 +1583,7 @@ runHook userHook
 
 dumpVars
 
+
 # Restore the original options for nix-shell
 [[ $__nixpkgs_setup_set_original == *e* ]] || set +e
 [[ $__nixpkgs_setup_set_original == *u* ]] || set +u
diff --git a/pkgs/stdenv/linux/default.nix b/pkgs/stdenv/linux/default.nix
index dbaff342fb1af..6a1211f9cc48f 100644
--- a/pkgs/stdenv/linux/default.nix
+++ b/pkgs/stdenv/linux/default.nix
@@ -249,7 +249,7 @@ in
         # Apparently iconv won't work with bootstrap glibc, but it will be used
         # with glibc built later where we keep *this* build of libunistring,
         # so we need to trick it into supporting libiconv.
-        am_cv_func_iconv_works = "yes";
+        env = attrs.env or {} // { am_cv_func_iconv_works = "yes"; };
       });
       libidn2 = super.libidn2.overrideAttrs (attrs: {
         postFixup = attrs.postFixup or "" + ''
diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix
index b687092f51486..9cf5487372d04 100644
--- a/pkgs/test/default.nix
+++ b/pkgs/test/default.nix
@@ -21,6 +21,7 @@ with pkgs;
   cc-wrapper-clang-9 = callPackage ./cc-wrapper { stdenv = llvmPackages_9.stdenv; };
   cc-wrapper-libcxx-9 = callPackage ./cc-wrapper { stdenv = llvmPackages_9.libcxxStdenv; };
   stdenv-inputs = callPackage ./stdenv-inputs { };
+  stdenv = callPackage ./stdenv { };
 
   config = callPackage ./config.nix { };
 
diff --git a/pkgs/test/simple/builder.sh b/pkgs/test/simple/builder.sh
index 65f7e4c11ba1b..908faec3c3888 100644
--- a/pkgs/test/simple/builder.sh
+++ b/pkgs/test/simple/builder.sh
@@ -1,3 +1,4 @@
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 set -x
 
 export NIX_DEBUG=1
diff --git a/pkgs/test/stdenv/default.nix b/pkgs/test/stdenv/default.nix
new file mode 100644
index 0000000000000..08e8eed118f98
--- /dev/null
+++ b/pkgs/test/stdenv/default.nix
@@ -0,0 +1,159 @@
+# To run these tests:
+# nix-build -A tests.stdenv
+
+{ stdenv
+, pkgs
+, lib
+,
+}:
+
+let
+  # use a early stdenv so when hacking on stdenv this test can be run quickly
+  bootStdenv = stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv;
+  pkgsStructured = import pkgs.path { config = { structuredAttrsByDefault = true; }; inherit (stdenv.hostPlatform) system; };
+  bootStdenvStructuredAttrsByDefault = pkgsStructured.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv.__bootPackages.stdenv;
+
+
+  ccWrapperSubstitutionsTest = { name, stdenv', extraAttrs ? { } }:
+
+    stdenv'.cc.overrideAttrs (previousAttrs: ({
+      inherit name;
+
+      postFixup = previousAttrs.postFixup + ''
+        declare -p wrapperName
+        echo "env.wrapperName = $wrapperName"
+        [[ $wrapperName == "CC_WRAPPER" ]] || (echo "'\$wrapperName' was not 'CC_WRAPPER'" && false)
+        declare -p suffixSalt
+        echo "env.suffixSalt = $suffixSalt"
+        [[ $suffixSalt == "${stdenv'.cc.suffixSalt}" ]] || (echo "'\$suffxSalt' was not '${stdenv'.cc.suffixSalt}'" && false)
+
+        grep -q "@out@" $out/bin/cc || echo "@out@ in $out/bin/cc was substituted"
+        grep -q "@suffixSalt@" $out/bin/cc && (echo "$out/bin/cc contains unsubstituted variables" && false)
+
+        touch $out
+      '';
+    } // extraAttrs));
+
+  testEnvAttrset = { name, stdenv', extraAttrs ? { } }:
+    stdenv'.mkDerivation
+      ({
+        inherit name;
+        env = {
+          string = "testing-string";
+        };
+
+        passAsFile = [ "buildCommand" ];
+        buildCommand = ''
+          declare -p string
+          echo "env.string = $string"
+          [[ $string == "testing-string" ]] || (echo "'\$string' was not 'testing-string'" && false)
+          touch $out
+        '';
+      } // extraAttrs);
+
+  testPrependAndAppendToVar = { name, stdenv', extraAttrs ? { } }:
+    stdenv'.mkDerivation
+      ({
+        inherit name;
+        env = {
+          string = "testing-string";
+        };
+
+        passAsFile = [ "buildCommand" ] ++ lib.optionals (extraAttrs ? extraTest) [ "extraTest" ];
+        buildCommand = ''
+          declare -p string
+          appendToVar string hello
+          # test that quoted strings work
+          prependToVar string "world"
+          declare -p string
+
+          declare -A associativeArray=(["X"]="Y")
+          [[ $(appendToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false)
+          [[ $(prependToVar associativeArray "fail" 2>&1) =~ "trying to use" ]] || (echo "prependToVar did not catch prepending associativeArray" && false)
+
+          [[ $string == "world testing-string hello" ]] || (echo "'\$string' was not 'world testing-string hello'" && false)
+
+          # test appending to a unset variable
+          appendToVar nonExistant created hello
+          typeset -p nonExistant
+          if [[ -n $__structuredAttrs ]]; then
+            [[ "''${nonExistant[@]}" == "created hello" ]]
+          else
+            # there's a extra " " in front here and a extra " " in the end of prependToVar
+            # shouldn't matter because these functions will mostly be used for $*Flags and the Flag variable will in most cases already exit
+            [[ "$nonExistant" == " created hello" ]]
+          fi
+
+          eval "$extraTest"
+
+          touch $out
+        '';
+      } // extraAttrs);
+
+in
+
+{
+  test-env-attrset = testEnvAttrset { name = "test-env-attrset"; stdenv' = bootStdenv; };
+
+  test-prepend-append-to-var = testPrependAndAppendToVar {
+    name = "test-prepend-append-to-var";
+    stdenv' = bootStdenv;
+  };
+
+  test-structured-env-attrset = testEnvAttrset {
+    name = "test-structured-env-attrset";
+    stdenv' = bootStdenv;
+    extraAttrs = { __structuredAttrs = true; };
+  };
+
+  test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
+    name = "test-cc-wrapper-substitutions";
+    stdenv' = bootStdenv;
+  };
+
+  structuredAttrsByDefault = lib.recurseIntoAttrs {
+    test-cc-wrapper-substitutions = ccWrapperSubstitutionsTest {
+      name = "test-cc-wrapper-substitutions-structuredAttrsByDefault";
+      stdenv' = bootStdenvStructuredAttrsByDefault;
+    };
+
+    test-structured-env-attrset = testEnvAttrset {
+      name = "test-structured-env-attrset-structuredAttrsByDefault";
+      stdenv' = bootStdenvStructuredAttrsByDefault;
+    };
+
+    test-prepend-append-to-var = testPrependAndAppendToVar {
+      name = "test-prepend-append-to-var-structuredAttrsByDefault";
+      stdenv' = bootStdenvStructuredAttrsByDefault;
+      extraAttrs = {
+        # will be a bash indexed array in attrs.sh
+        # declare -a list=('a' 'b' )
+        # and a json array in attrs.json
+        # "list":["a","b"]
+        list = [ "a" "b" ];
+        # will be a bash associative array(dictionary) in attrs.sh
+        # declare -A array=(['a']='1' ['b']='2' )
+        # and a json object in attrs.json
+        # {"array":{"a":"1","b":"2"}
+        array = { a = "1"; b = "2"; };
+        extraTest = ''
+          declare -p array
+          array+=(["c"]="3")
+          declare -p array
+
+          [[ "''${array[c]}" == "3" ]] || (echo "c element of '\$array' was not '3'" && false)
+
+          declare -p list
+          prependToVar list hello
+          # test that quoted strings work
+          appendToVar list "world"
+          declare -p list
+
+          [[ "''${list[0]}" == "hello" ]] || (echo "first element of '\$list' was not 'hello'" && false)
+          [[ "''${list[1]}" == "a" ]] || (echo "first element of '\$list' was not 'a'" && false)
+          [[ "''${list[-1]}" == "world" ]] || (echo "last element of '\$list' was not 'world'" && false)
+        '';
+      };
+    };
+  };
+}
diff --git a/pkgs/tools/typesetting/lout/builder.sh b/pkgs/tools/typesetting/lout/builder.sh
index eab37c3c68fb5..cd513337f6f36 100755
--- a/pkgs/tools/typesetting/lout/builder.sh
+++ b/pkgs/tools/typesetting/lout/builder.sh
@@ -1,6 +1,7 @@
 # Prepare a makefile specifying the appropriate output directories.
 #
 # Written by Ludovic Courtès <ludo@gnu.org>.
+if [ -e .attrs.sh ]; then source .attrs.sh; fi
 
 source "$stdenv/setup" || exit 1
 
diff --git a/pkgs/tools/typesetting/tex/texlive/bin.nix b/pkgs/tools/typesetting/tex/texlive/bin.nix
index 6c7d8880a3167..d874f443f4a35 100644
--- a/pkgs/tools/typesetting/tex/texlive/bin.nix
+++ b/pkgs/tools/typesetting/tex/texlive/bin.nix
@@ -247,7 +247,7 @@ core-big = stdenv.mkDerivation { #TODO: upmendex
     "xetex"
   ];
   postInstall = ''
-    for output in $outputs; do
+    for output in $(getAllOutputNames); do
       mkdir -p "''${!output}/bin"
     done
 
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 7cf48224de2fa..f6dcf41dc429f 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -13966,9 +13966,11 @@ with pkgs;
 
   wrapNonDeterministicGcc = stdenv: ccWrapper:
     if ccWrapper.isGNU then ccWrapper.overrideAttrs(old: {
-      cc = old.cc.override {
-        reproducibleBuild = false;
-        profiledCompiler = with stdenv; (!isDarwin && hostPlatform.isx86);
+      env = old.env // {
+        cc = old.env.cc.override {
+          reproducibleBuild = false;
+          profiledCompiler = with stdenv; (!isDarwin && hostPlatform.isx86);
+        };
       };
     }) else ccWrapper;
 
diff --git a/pkgs/top-level/config.nix b/pkgs/top-level/config.nix
index a47655f114243..1de93a9f3fdea 100644
--- a/pkgs/top-level/config.nix
+++ b/pkgs/top-level/config.nix
@@ -47,6 +47,10 @@ let
       feature = "set `strictDeps` to true by default";
     };
 
+    structuredAttrsByDefault = mkMassRebuild {
+      feature = "set `__structuredAttrs` to true by default";
+    };
+
     enableParallelBuildingByDefault = mkMassRebuild {
       feature = "set `enableParallelBuilding` to true by default";
     };
diff --git a/pkgs/top-level/perl-packages.nix b/pkgs/top-level/perl-packages.nix
index c9879db716507..128564edea557 100644
--- a/pkgs/top-level/perl-packages.nix
+++ b/pkgs/top-level/perl-packages.nix
@@ -23029,7 +23029,7 @@ let
 
     # For some crazy reason Makefile.PL doesn't generate a Makefile if
     # AUTOMATED_TESTING is set.
-    AUTOMATED_TESTING = false;
+    env.AUTOMATED_TESTING = false;
 
     # Makefile.PL looks for ncurses in Glibc's prefix.
     preConfigure =
@@ -27058,7 +27058,7 @@ let
         hash = "sha256-gxxY8549/ebS3QORjSs8IgdBs2aD05Tu+9Bn70gu7gQ=";
       })
     ];
-    AUTOMATED_TESTING = false;
+    env.AUTOMATED_TESTING = false;
     nativeBuildInputs = [ pkgs.pkg-config ];
     buildInputs = [ pkgs.xorg.libxcb pkgs.xorg.xcbproto pkgs.xorg.xcbutil pkgs.xorg.xcbutilwm ExtUtilsDepends ExtUtilsPkgConfig TestDeep TestException XSObjectMagic ];
     propagatedBuildInputs = [ DataDump MouseXNativeTraits XMLDescent XMLSimple ];