summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/attrsets.nix23
-rw-r--r--lib/customisation.nix8
-rw-r--r--lib/debug.nix22
-rw-r--r--lib/default.nix9
-rw-r--r--lib/flake.nix5
-rw-r--r--lib/generators.nix24
-rw-r--r--lib/licenses.nix104
-rw-r--r--lib/lists.nix6
-rw-r--r--lib/meta.nix12
-rw-r--r--lib/modules.nix35
-rw-r--r--lib/sources.nix13
-rw-r--r--lib/strings.nix20
-rw-r--r--lib/systems/architectures.nix46
-rw-r--r--lib/systems/default.nix54
-rw-r--r--lib/systems/doubles.nix59
-rw-r--r--lib/systems/examples.nix104
-rw-r--r--lib/systems/parse.nix23
-rw-r--r--lib/systems/platforms.nix713
-rw-r--r--lib/tests/misc.nix67
-rwxr-xr-xlib/tests/modules.sh12
-rw-r--r--lib/tests/modules/declare-submoduleWith-modules.nix4
-rw-r--r--lib/tests/modules/functionTo/list-order.nix25
-rw-r--r--lib/tests/modules/functionTo/merging-attrs.nix27
-rw-r--r--lib/tests/modules/functionTo/merging-list.nix24
-rw-r--r--lib/tests/modules/functionTo/trivial.nix17
-rw-r--r--lib/tests/modules/functionTo/wrong-type.nix18
-rw-r--r--lib/tests/systems.nix8
-rw-r--r--lib/trivial.nix7
-rw-r--r--lib/types.nix40
29 files changed, 1022 insertions, 507 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix
index d91d7a0cd47..5c787940cb0 100644
--- a/lib/attrsets.nix
+++ b/lib/attrsets.nix
@@ -183,6 +183,24 @@ rec {
     else
       [];
 
+  /* Return the cartesian product of attribute set value combinations.
+
+    Example:
+      cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
+      => [
+           { a = 1; b = 10; }
+           { a = 1; b = 20; }
+           { a = 2; b = 10; }
+           { a = 2; b = 20; }
+         ]
+  */
+  cartesianProductOfSets = attrsOfLists:
+    lib.foldl' (listOfAttrs: attrName:
+      concatMap (attrs:
+        map (listValue: attrs // { ${attrName} = listValue; }) attrsOfLists.${attrName}
+      ) listOfAttrs
+    ) [{}] (attrNames attrsOfLists);
+
 
   /* Utility function that creates a {name, value} pair as expected by
      builtins.listToAttrs.
@@ -225,6 +243,10 @@ rec {
   /* Call a function for each attribute in the given set and return
      the result in a list.
 
+     Type:
+       mapAttrsToList ::
+         (String -> a -> b) -> AttrSet -> [b]
+
      Example:
        mapAttrsToList (name: value: name + value)
           { x = "a"; y = "b"; }
@@ -493,5 +515,4 @@ rec {
   zipWithNames = zipAttrsWithNames;
   zip = builtins.trace
     "lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith;
-
 }
diff --git a/lib/customisation.nix b/lib/customisation.nix
index 37a7951896b..c17cb0d0f8e 100644
--- a/lib/customisation.nix
+++ b/lib/customisation.nix
@@ -219,16 +219,17 @@ rec {
 
   /* Like the above, but aims to support cross compilation. It's still ugly, but
      hopefully it helps a little bit. */
-  makeScopeWithSplicing = splicePackages: newScope: otherSplices: keep: f:
+  makeScopeWithSplicing = splicePackages: newScope: otherSplices: keep: extra: f:
     let
-      spliced = splicePackages {
+      spliced0 = splicePackages {
         pkgsBuildBuild = otherSplices.selfBuildBuild;
         pkgsBuildHost = otherSplices.selfBuildHost;
         pkgsBuildTarget = otherSplices.selfBuildTarget;
         pkgsHostHost = otherSplices.selfHostHost;
         pkgsHostTarget = self; # Not `otherSplices.selfHostTarget`;
         pkgsTargetTarget = otherSplices.selfTargetTarget;
-      } // keep self;
+      };
+      spliced = extra spliced0 // spliced0 // keep self;
       self = f self // {
         newScope = scope: newScope (spliced // scope);
         callPackage = newScope spliced; # == self.newScope {};
@@ -239,6 +240,7 @@ rec {
           newScope
           otherSplices
           keep
+          extra
           (lib.fixedPoints.extends g f);
         packages = f;
       };
diff --git a/lib/debug.nix b/lib/debug.nix
index ea6aed60ab4..e3ca3352397 100644
--- a/lib/debug.nix
+++ b/lib/debug.nix
@@ -148,6 +148,28 @@ rec {
   /* A combination of `traceVal` and `traceSeqN`. */
   traceValSeqN = traceValSeqNFn id;
 
+  /* Trace the input and output of a function `f` named `name`,
+  both down to `depth`.
+
+  This is useful for adding around a function call,
+  to see the before/after of values as they are transformed.
+
+     Example:
+       traceFnSeqN 2 "id" (x: x) { a.b.c = 3; }
+       trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; }
+       => { a.b.c = 3; }
+  */
+  traceFnSeqN = depth: name: f: v:
+    let res = f v;
+    in lib.traceSeqN
+        (depth + 1)
+        {
+          fn = name;
+          from = v;
+          to = res;
+        }
+        res;
+
 
   # -- TESTING --
 
diff --git a/lib/default.nix b/lib/default.nix
index f985266ed93..ccae0bbc3ab 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -66,8 +66,9 @@ let
       stringLength sub substring tail trace;
     inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor
       bitNot boolToString mergeAttrs flip mapNullable inNixShell isFloat min max
-      importJSON importTOML warn info showWarnings nixpkgsVersion version mod compare
-      splitByAndCompare functionArgs setFunctionArgs isFunction toHexString toBaseDigits;
+      importJSON importTOML warn warnIf info showWarnings nixpkgsVersion version
+      mod compare splitByAndCompare functionArgs setFunctionArgs isFunction
+      toHexString toBaseDigits;
     inherit (self.fixedPoints) fix fix' converge extends composeExtensions
       composeManyExtensions makeExtensible makeExtensibleWithCustomName;
     inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
@@ -78,7 +79,7 @@ let
       zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
       recursiveUpdate matchAttrs overrideExisting getOutput getBin
       getLib getDev getMan chooseDevOutputs zipWithNames zip
-      recurseIntoAttrs dontRecurseIntoAttrs;
+      recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets;
     inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1
       concatMap flatten remove findSingle findFirst any all count
       optional optionals toList range partition zipListsWith zipLists
@@ -130,7 +131,7 @@ let
       assertMsg assertOneOf;
     inherit (self.debug) addErrorContextToAttrs traceIf traceVal traceValFn
       traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq
-      traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal
+      traceValSeqFn traceValSeqN traceValSeqNFn traceFnSeqN traceShowVal
       traceShowValMarked showVal traceCall traceCall2 traceCall3
       traceValIfNot runTests testAllTrue traceCallXml attrNamesToStr;
     inherit (self.misc) maybeEnv defaultMergeArg defaultMerge foldArgs
diff --git a/lib/flake.nix b/lib/flake.nix
new file mode 100644
index 00000000000..f05bd40960a
--- /dev/null
+++ b/lib/flake.nix
@@ -0,0 +1,5 @@
+{
+  description = "Library of low-level helper functions for nix expressions.";
+
+  outputs = { self }: { lib = import ./lib; };
+}
diff --git a/lib/generators.nix b/lib/generators.nix
index 501a23599f4..c8144db50ac 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -307,4 +307,28 @@ rec {
 ${expr "" v}
 </plist>'';
 
+  /* Translate a simple Nix expression to Dhall notation.
+   * Note that integers are translated to Integer and never
+   * the Natural type.
+  */
+  toDhall = { }@args: v:
+    with builtins;
+    let concatItems = lib.strings.concatStringsSep ", ";
+    in if isAttrs v then
+      "{ ${
+        concatItems (lib.attrsets.mapAttrsToList
+          (key: value: "${key} = ${toDhall args value}") v)
+      } }"
+    else if isList v then
+      "[ ${concatItems (map (toDhall args) v)} ]"
+    else if isInt v then
+      "${if v < 0 then "" else "+"}${toString v}"
+    else if isBool v then
+      (if v then "True" else "False")
+    else if isFunction v then
+      abort "generators.toDhall: cannot convert a function to Dhall"
+    else if isNull v then
+      abort "generators.toDhall: cannot convert a null to Dhall"
+    else
+      builtins.toJSON v;
 }
diff --git a/lib/licenses.nix b/lib/licenses.nix
index d47deccfec3..9baaba022bf 100644
--- a/lib/licenses.nix
+++ b/lib/licenses.nix
@@ -7,7 +7,7 @@ let
 
 in
 
-lib.mapAttrs (n: v: v // { shortName = n; }) {
+lib.mapAttrs (n: v: v // { shortName = n; }) ({
   /* License identifiers from spdx.org where possible.
    * If you cannot find your license here, then look for a similar license or
    * add it to this list. The URL mentioned above is a good source for inspiration.
@@ -18,6 +18,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "Abstyles License";
   };
 
+  afl20 = spdx {
+    spdxId = "AFL-2.0";
+    fullName = "Academic Free License v2.0";
+  };
+
   afl21 = spdx {
     spdxId = "AFL-2.1";
     fullName = "Academic Free License v2.1";
@@ -82,7 +87,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
 
   beerware = spdx {
     spdxId = "Beerware";
-    fullName = ''Beerware License'';
+    fullName = "Beerware License";
   };
 
   blueOak100 = spdx {
@@ -95,6 +100,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "BSD Zero Clause License";
   };
 
+  bsd1 = spdx {
+    spdxId = "BSD-1-Clause";
+    fullName = "BSD 1-Clause License";
+  };
+
   bsd2 = spdx {
     spdxId = "BSD-2-Clause";
     fullName = ''BSD 2-clause "Simplified" License'';
@@ -102,7 +112,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
 
   bsd2Patent = spdx {
     spdxId = "BSD-2-Clause-Patent";
-    fullName = ''BSD-2-Clause Plus Patent License'';
+    fullName = "BSD-2-Clause Plus Patent License";
   };
 
   bsd3 = spdx {
@@ -115,6 +125,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = ''BSD 4-clause "Original" or "Old" License'';
   };
 
+  bsdOriginalUC = spdx {
+    spdxId = "BSD-4-Clause-UC";
+    fullName = "BSD 4-Clause University of California-Specific";
+  };
+
   bsdProtection = spdx {
     spdxId = "BSD-Protection";
     fullName = "BSD Protection License";
@@ -296,6 +311,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "GNU Free Documentation License v1.1 only";
   };
 
+  fdl11Plus = spdx {
+    spdxId = "GFDL-1.1-or-later";
+    fullName = "GNU Free Documentation License v1.1 or later";
+  };
+
   fdl12Only = spdx {
     spdxId = "GFDL-1.2-only";
     fullName = "GNU Free Documentation License v1.2 only";
@@ -588,6 +608,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     free = false;
   };
 
+  odbl = spdx {
+    spdxId = "ODbL-1.0";
+    fullName = "Open Data Commons Open Database License v1.0";
+  };
+
   ofl = spdx {
     spdxId = "OFL-1.1";
     fullName = "SIL Open Font License 1.1";
@@ -618,6 +643,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     fullName = "Open Software License 3.0";
   };
 
+  parity70 = spdx {
+    spdxId = "Parity-7.0.0";
+    fullName = "Parity Public License 7.0.0";
+    url = "https://paritylicense.com/versions/7.0.0.html";
+  };
+
   php301 = spdx {
     spdxId = "PHP-3.01";
     fullName = "PHP License v3.01";
@@ -735,6 +766,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
     # channel and NixOS images.
   };
 
+  unicode-dfs-2015 = spdx {
+    spdxId = "Unicode-DFS-2015";
+    fullName = "Unicode License Agreement - Data Files and Software (2015)";
+  };
+
   unicode-dfs-2016 = spdx {
     spdxId = "Unicode-DFS-2016";
     fullName = "Unicode License Agreement - Data Files and Software (2016)";
@@ -812,14 +848,54 @@ lib.mapAttrs (n: v: v // { shortName = n; }) {
   };
 } // {
   # TODO: remove legacy aliases
-  agpl3 = lib.licenses.agpl3Only;
-  fdl11 = lib.licenses.fdl11Only;
-  fdl12 = lib.licenses.fdl12Only;
-  fdl13 = lib.licenses.fdl13Only;
-  gpl1 = lib.licenses.gpl1Only;
-  gpl2 = lib.licenses.gpl2Only;
-  gpl3 = lib.licenses.gpl3Only;
-  lgpl2 = lib.licenses.lgpl2Only;
-  lgpl21 = lib.licenses.lgpl21Only;
-  lgpl3 = lib.licenses.lgpl3Only;
-}
+  agpl3 = spdx {
+    spdxId = "AGPL-3.0";
+    fullName = "GNU Affero General Public License v3.0";
+    deprecated = true;
+  };
+  fdl11 = spdx {
+    spdxId = "GFDL-1.1";
+    fullName = "GNU Free Documentation License v1.1";
+    deprecated = true;
+  };
+  fdl12 = spdx {
+    spdxId = "GFDL-1.2";
+    fullName = "GNU Free Documentation License v1.2";
+    deprecated = true;
+  };
+  fdl13 = spdx {
+    spdxId = "GFDL-1.3";
+    fullName = "GNU Free Documentation License v1.3";
+    deprecated = true;
+  };
+  gpl1 = spdx {
+    spdxId = "GPL-1.0";
+    fullName = "GNU General Public License v1.0";
+    deprecated = true;
+  };
+  gpl2 = spdx {
+    spdxId = "GPL-2.0";
+    fullName = "GNU General Public License v2.0";
+    deprecated = true;
+  };
+  gpl3 = spdx {
+    spdxId = "GPL-3.0";
+    fullName = "GNU General Public License v3.0";
+    deprecated = true;
+  };
+  lgpl2 = spdx {
+    spdxId = "LGPL-2.0";
+    fullName = "GNU Library General Public License v2";
+    deprecated = true;
+  };
+  lgpl21 = spdx {
+    spdxId = "LGPL-2.1";
+    fullName = "GNU Lesser General Public License v2.1";
+    deprecated = true;
+  };
+  lgpl3 = spdx {
+    spdxId = "LGPL-3.0";
+    fullName = "GNU Lesser General Public License v3.0";
+    deprecated = true;
+  };
+})
diff --git a/lib/lists.nix b/lib/lists.nix
index 06cee2eb112..e469f3ef265 100644
--- a/lib/lists.nix
+++ b/lib/lists.nix
@@ -241,7 +241,7 @@ rec {
 
   /* Return a singleton list or an empty list, depending on a boolean
      value.  Useful when building lists with optional elements
-     (e.g. `++ optional (system == "i686-linux") flashplayer').
+     (e.g. `++ optional (system == "i686-linux") firefox').
 
      Type: optional :: bool -> a -> [a]
 
@@ -629,7 +629,9 @@ rec {
       crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]]
       => [ "13" "14" "23" "24" ]
   */
-  crossLists = f: foldl (fs: args: concatMap (f: map f args) fs) [f];
+  crossLists = builtins.trace
+    "lib.crossLists is deprecated, use lib.cartesianProductOfSets instead"
+    (f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
 
 
   /* Remove duplicate elements from the list. O(n^2) complexity.
diff --git a/lib/meta.nix b/lib/meta.nix
index 2e83c4247dd..bc04394dcf0 100644
--- a/lib/meta.nix
+++ b/lib/meta.nix
@@ -87,4 +87,16 @@ rec {
         then { system = elem; }
         else { parsed = elem; };
     in lib.matchAttrs pattern platform;
+
+  /* Check if a package is available on a given platform.
+
+     A package is available on a platform if both
+
+       1. One of `meta.platforms` pattern matches the given platform.
+
+       2. None of `meta.badPlatforms` pattern matches the given platform.
+  */
+  availableOn = platform: pkg:
+    lib.any (platformMatch platform) pkg.meta.platforms &&
+    lib.all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []);
 }
diff --git a/lib/modules.nix b/lib/modules.nix
index 3f2bfd478b0..99b9a8a31ea 100644
--- a/lib/modules.nix
+++ b/lib/modules.nix
@@ -23,6 +23,7 @@ let
     isAttrs
     isBool
     isFunction
+    isList
     isString
     length
     mapAttrs
@@ -37,7 +38,7 @@ let
     setAttrByPath
     toList
     types
-    warn
+    warnIf
     ;
   inherit (lib.options)
     isOption
@@ -127,7 +128,7 @@ rec {
         let collected = collectModules
           (specialArgs.modulesPath or "")
           (modules ++ [ internalModule ])
-          ({ inherit lib options config; } // specialArgs);
+          ({ inherit lib options config specialArgs; } // specialArgs);
         in mergeModules prefix (reverseList collected);
 
       options = merged.matchedOptions;
@@ -188,6 +189,9 @@ rec {
       loadModule = args: fallbackFile: fallbackKey: m:
         if isFunction m || isAttrs m then
           unifyModuleSyntax fallbackFile fallbackKey (applyIfFunction fallbackKey m args)
+        else if isList m then
+          let defs = [{ file = fallbackFile; value = m; }]; in
+          throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
         else unifyModuleSyntax (toString m) (toString m) (applyIfFunction (toString m) (import m) args);
 
       /*
@@ -295,13 +299,11 @@ rec {
       # a module will resolve strictly the attributes used as argument but
       # not their values.  The values are forwarding the result of the
       # evaluation of the option.
-      requiredArgs = builtins.attrNames (lib.functionArgs f);
       context = name: ''while evaluating the module argument `${name}' in "${key}":'';
-      extraArgs = builtins.listToAttrs (map (name: {
-        inherit name;
-        value = builtins.addErrorContext (context name)
-          (args.${name} or config._module.args.${name});
-      }) requiredArgs);
+      extraArgs = builtins.mapAttrs (name: _:
+        builtins.addErrorContext (context name)
+          (args.${name} or config._module.args.${name})
+      ) (lib.functionArgs f);
 
       # Note: we append in the opposite order such that we can add an error
       # context on the explicited arguments of "args" too. This update
@@ -361,6 +363,17 @@ rec {
       */
       byName = attr: f: modules:
         foldl' (acc: module:
+              if !(builtins.isAttrs module.${attr}) then
+                throw ''
+                  You're trying to declare a value of type `${builtins.typeOf module.${attr}}'
+                  rather than an attribute-set for the option
+                  `${builtins.concatStringsSep "." prefix}'!
+
+                  This usually happens if `${builtins.concatStringsSep "." prefix}' has option
+                  definitions inside that are not matched. Please check how to properly define
+                  this option by e.g. referring to `man 5 configuration.nix'!
+                ''
+              else
                 acc // (mapAttrs (n: v:
                                    (acc.${n} or []) ++ f module v
                                  ) module.${attr}
@@ -505,8 +518,8 @@ rec {
       value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue;
 
       warnDeprecation =
-        if opt.type.deprecationMessage == null then id
-        else warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
+        warnIf (opt.type.deprecationMessage != null)
+          "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}";
 
     in warnDeprecation opt //
       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
@@ -895,7 +908,7 @@ rec {
       fromOpt = getAttrFromPath from options;
       toOf = attrByPath to
         (abort "Renaming error: option `${showOption to}' does not exist.");
-      toType = let opt = attrByPath to {} options; in opt.type or null;
+      toType = let opt = attrByPath to {} options; in opt.type or (types.submodule {});
     in
     {
       options = setAttrByPath from (mkOption {
diff --git a/lib/sources.nix b/lib/sources.nix
index 1a3afcae67d..1a821f55056 100644
--- a/lib/sources.nix
+++ b/lib/sources.nix
@@ -138,12 +138,13 @@ rec {
              in if m == null
                 then throw ("File contains no gitdir reference: " + path)
                 else
-                  let gitDir     = absolutePath (dirOf path) (lib.head m);
-                      commonDir' = if pathIsRegularFile "${gitDir}/commondir"
-                                   then lib.fileContents "${gitDir}/commondir"
-                                   else gitDir;
-                      commonDir  = absolutePath gitDir commonDir';
-                      refFile    = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
+                  let gitDir      = absolutePath (dirOf path) (lib.head m);
+                      commonDir'' = if pathIsRegularFile "${gitDir}/commondir"
+                                    then lib.fileContents "${gitDir}/commondir"
+                                    else gitDir;
+                      commonDir'  = lib.removeSuffix "/" commonDir'';
+                      commonDir   = absolutePath gitDir commonDir';
+                      refFile     = lib.removePrefix "${commonDir}/" "${gitDir}/${file}";
                   in readCommitFromFile refFile commonDir
 
            else if pathIsRegularFile fileName
diff --git a/lib/strings.nix b/lib/strings.nix
index fbb48dec92a..49fa0196a0b 100644
--- a/lib/strings.nix
+++ b/lib/strings.nix
@@ -569,9 +569,9 @@ rec {
      standard GNU Autoconf scripts.
 
      Example:
-       enableFeature true "shared" "foo"
+       enableFeatureAs true "shared" "foo"
        => "--enable-shared=foo"
-       enableFeature false "shared" (throw "ignored")
+       enableFeatureAs false "shared" (throw "ignored")
        => "--disable-shared"
   */
   enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}";
@@ -593,9 +593,9 @@ rec {
      standard GNU Autoconf scripts.
 
      Example:
-       with_Feature true "shared" "foo"
+       withFeatureAs true "shared" "foo"
        => "--with-shared=foo"
-       with_Feature false "shared" (throw "ignored")
+       withFeatureAs false "shared" (throw "ignored")
        => "--without-shared"
   */
   withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}";
@@ -606,7 +606,7 @@ rec {
      This function will fail if the input string is longer than the
      requested length.
 
-     Type: fixedWidthString :: int -> string -> string
+     Type: fixedWidthString :: int -> string -> string -> string
 
      Example:
        fixedWidthString 5 "0" (toString 15)
@@ -644,8 +644,8 @@ rec {
   floatToString = float: let
     result = toString float;
     precise = float == fromJSON result;
-  in if precise then result
-    else lib.warn "Imprecise conversion from float to string ${result}" result;
+  in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
+    result;
 
   /* Check whether a value can be coerced to a string */
   isCoercibleToString = x:
@@ -659,7 +659,7 @@ rec {
      Example:
        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
        => false
-       isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/"
+       isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
        => true
        isStorePath pkgs.python
        => true
@@ -667,14 +667,14 @@ rec {
        => false
   */
   isStorePath = x:
-    if isCoercibleToString x then
+    if !(isList x) && isCoercibleToString x then
       let str = toString x; in
       substring 0 1 str == "/"
       && dirOf str == storeDir
     else
       false;
 
-  /* Parse a string string as an int.
+  /* Parse a string as an int.
 
      Type: string -> int
 
diff --git a/lib/systems/architectures.nix b/lib/systems/architectures.nix
index bfecaec1ae8..ddc320d24e0 100644
--- a/lib/systems/architectures.nix
+++ b/lib/systems/architectures.nix
@@ -1,7 +1,7 @@
 { lib }:
 
 rec {
-  # platform.gcc.arch to its features (as in /proc/cpuinfo)
+  # gcc.arch to its features (as in /proc/cpuinfo)
   features = {
     default        = [ ];
     # x86_64 Intel
@@ -12,6 +12,12 @@ rec {
     broadwell      = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2"          "fma"        ];
     skylake        = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2"          "fma"        ];
     skylake-avx512 = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    cannonlake     = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    icelake-client = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    icelake-server = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    cascadelake    = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    cooperlake     = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
+    tigerlake      = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2" "avx512" "fma"        ];
     # x86_64 AMD
     btver1         = [ "sse3" "ssse3" "sse4_1" "sse4_2"                                                  ];
     btver2         = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx"                              ];
@@ -21,6 +27,7 @@ rec {
     bdver4         = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2"          "fma" "fma4" ];
     znver1         = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2"          "fma"        ];
     znver2         = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2"          "fma"        ];
+    znver3         = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2"          "fma"        ];
     # other
     armv5te        = [ ];
     armv6          = [ ];
@@ -41,15 +48,38 @@ rec {
     broadwell      = [ "haswell"     ] ++ inferiors.haswell;
     skylake        = [ "broadwell"   ] ++ inferiors.broadwell;
     skylake-avx512 = [ "skylake"     ] ++ inferiors.skylake;
+
     # x86_64 AMD
+    # TODO: fill this (need testing)
     btver1         = [ ];
-    btver2         = [ ]; # TODO: fill this (need testing)
-    bdver1         = [ ]; # TODO: fill this (need testing)
-    bdver2         = [ ]; # TODO: fill this (need testing)
-    bdver3         = [ ]; # TODO: fill this (need testing)
-    bdver4         = [ ]; # TODO: fill this (need testing)
-    znver1         = [ ]; # TODO: fill this (need testing)
-    znver2         = [ ]; # TODO: fill this (need testing)
+    btver2         = [ ];
+    bdver1         = [ ];
+    bdver2         = [ ];
+    bdver3         = [ ];
+    bdver4         = [ ];
+    # Regarding `skylake` as inferior of `znver1`, there are reports of
+    # successful usage by Gentoo users and Phoronix benchmarking of different
+    # `-march` targets.
+    #
+    # The GCC documentation on extensions used and wikichip documentation
+    # regarding supperted extensions on znver1 and skylake was used to create
+    # this partial order.
+    #
+    # Note:
+    #
+    # - The succesors of `skylake` (`cannonlake`, `icelake`, etc) use `avx512`
+    #   which no current AMD Zen michroarch support.
+    # - `znver1` uses `ABM`, `CLZERO`, `CX16`, `MWAITX`, and `SSE4A` which no
+    #   current Intel microarch support.
+    #
+    # https://www.phoronix.com/scan.php?page=article&item=amd-znver3-gcc11&num=1
+    # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
+    # https://en.wikichip.org/wiki/amd/microarchitectures/zen
+    # https://en.wikichip.org/wiki/intel/microarchitectures/skylake
+    znver1         = [ "skylake" ] ++ inferiors.skylake;
+    znver2         = [ "znver1"  ] ++ inferiors.znver1;
+    znver3         = [ "znver2"  ] ++ inferiors.znver2;
+
     # other
     armv5te        = [ ];
     armv6          = [ ];
diff --git a/lib/systems/default.nix b/lib/systems/default.nix
index 9939743157e..70ec98b03c1 100644
--- a/lib/systems/default.nix
+++ b/lib/systems/default.nix
@@ -24,8 +24,6 @@ rec {
       # Either of these can be losslessly-extracted from `parsed` iff parsing succeeds.
       system = parse.doubleFromSystem final.parsed;
       config = parse.tripleFromSystem final.parsed;
-      # Just a guess, based on `system`
-      platform = platforms.selectBySystem final.system;
       # Determine whether we are compatible with the provided CPU
       isCompatible = platform: parse.isCompatible final.parsed.cpu platform.parsed.cpu;
       # Derived meta-data
@@ -43,6 +41,19 @@ rec {
         else if final.isNetBSD              then "nblibc"
         # TODO(@Ericson2314) think more about other operating systems
         else                                     "native/impure";
+      # Choose what linker we wish to use by default. Someday we might also
+      # choose the C compiler, runtime library, C++ standard library, etc. in
+      # this way, nice and orthogonally, and deprecate `useLLVM`. But due to
+      # the monolithic GCC build we cannot actually make those choices
+      # independently, so we are just doing `linker` and keeping `useLLVM` for
+      # now.
+      linker =
+        /**/ if final.useLLVM or false      then "lld"
+        else if final.isDarwin              then "cctools"
+        # "bfd" and "gold" both come from GNU binutils. The existance of Gold
+        # is why we use the more obscure "bfd" and not "binutils" for this
+        # choice.
+        else                                     "bfd";
       extensions = {
         sharedLibrary =
           /**/ if final.isDarwin  then ".dylib"
@@ -79,12 +90,23 @@ rec {
       };
       isStatic = final.isWasm || final.isRedox;
 
-      kernelArch =
+      # Just a guess, based on `system`
+      inherit
+        ({
+          linux-kernel = args.linux-kernel or {};
+          gcc = args.gcc or {};
+          rustc = args.rust or {};
+        } // platforms.select final)
+        linux-kernel gcc rustc;
+
+      linuxArch =
         if final.isAarch32 then "arm"
         else if final.isAarch64 then "arm64"
-        else if final.isx86_32 then "x86"
-        else if final.isx86_64 then "ia64"
+        else if final.isx86_32 then "i386"
+        else if final.isx86_64 then "x86_64"
         else if final.isMips then "mips"
+        else if final.isPower then "powerpc"
+        else if final.isRiscV then "riscv"
         else final.parsed.cpu.name;
 
       qemuArch =
@@ -98,6 +120,24 @@ rec {
           powerpc64le = "ppc64le";
         }.${final.parsed.cpu.name} or final.parsed.cpu.name;
 
+      darwinArch = {
+        armv7a  = "armv7";
+        aarch64 = "arm64";
+      }.${final.parsed.cpu.name} or final.parsed.cpu.name;
+
+      darwinPlatform =
+        if final.isMacOS then "macos"
+        else if final.isiOS then "ios"
+        else null;
+      # The canonical name for this attribute is darwinSdkVersion, but some
+      # platforms define the old name "sdkVer".
+      darwinSdkVersion = final.sdkVer or (if final.isAarch64 then "11.0" else "10.12");
+      darwinMinVersion = final.darwinSdkVersion;
+      darwinMinVersionVariable =
+        if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET"
+        else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET"
+        else null;
+
       emulator = pkgs: let
         qemu-user = pkgs.qemu.override {
           smartcardSupport = false;
@@ -124,10 +164,12 @@ rec {
         then "${qemu-user}/bin/qemu-${final.qemuArch}"
         else if final.isWasi
         then "${pkgs.wasmtime}/bin/wasmtime"
+        else if final.isMmix
+        then "${pkgs.mmixware}/bin/mmix"
         else throw "Don't know how to run ${final.config} executables.";
 
     } // mapAttrs (n: v: v final.parsed) inspect.predicates
-      // mapAttrs (n: v: v final.platform.gcc.arch or "default") architectures.predicates
+      // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
       // args;
   in assert final.useAndroidPrebuilt -> final.isAndroid;
      assert lib.foldl
diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix
index b0bc7dd1188..c6d90ba9850 100644
--- a/lib/systems/doubles.nix
+++ b/lib/systems/doubles.nix
@@ -6,42 +6,53 @@ let
   inherit (lib.attrsets) matchAttrs;
 
   all = [
-    "aarch64-linux"
-    "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux"
+    # Cygwin
+    "i686-cygwin" "x86_64-cygwin"
 
-    "mipsel-linux"
+    # Darwin
+    "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin"
 
-    "i686-cygwin" "i686-freebsd" "i686-linux" "i686-netbsd" "i686-openbsd"
+    # FreeBSD
+    "i686-freebsd" "x86_64-freebsd"
 
-    "x86_64-cygwin" "x86_64-freebsd" "x86_64-linux"
-    "x86_64-netbsd" "x86_64-openbsd" "x86_64-solaris"
+    # Genode
+    "aarch64-genode" "i686-genode" "x86_64-genode"
 
-    "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin"
+    # illumos
+    "x86_64-solaris"
 
-    "x86_64-windows" "i686-windows"
+    # JS
+    "js-ghcjs"
 
-    "wasm64-wasi" "wasm32-wasi"
+    # Linux
+    "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux"
+    "armv7l-linux" "i686-linux" "mipsel-linux" "powerpc64-linux"
+    "powerpc64le-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux"
 
-    "x86_64-redox"
+    # MMIXware
+    "mmix-mmixware"
 
-    "powerpc64le-linux"
+    # NetBSD
+    "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd"
+    "i686-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd"
+    "riscv64-netbsd" "x86_64-netbsd"
 
-    "riscv32-linux" "riscv64-linux"
+    # none
+    "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-none" "msp430-none"
+    "or1k-none" "powerpc-none" "riscv32-none" "riscv64-none" "vc4-none"
+    "x86_64-none"
 
-    "arm-none" "armv6l-none" "aarch64-none"
-    "avr-none"
-    "i686-none" "x86_64-none"
-    "powerpc-none"
-    "msp430-none"
-    "riscv64-none" "riscv32-none"
-    "vc4-none"
-    "or1k-none"
+    # OpenBSD
+    "i686-openbsd" "x86_64-openbsd"
 
-    "mmix-mmixware"
+    # Redox
+    "x86_64-redox"
 
-    "js-ghcjs"
+    # WASI
+    "wasm64-wasi" "wasm32-wasi"
 
-    "aarch64-genode" "i686-genode" "x86_64-genode"
+    # Windows
+    "x86_64-windows" "i686-windows"
   ];
 
   allParsed = map parse.mkSystemFromString all;
@@ -85,5 +96,5 @@ in {
 
   embedded      = filterDoubles predicates.isNone;
 
-  mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "armv7a-linux" "aarch64-linux" "powerpc64le-linux"];
+  mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "armv7a-linux" "aarch64-linux" "powerpc64-linux" "powerpc64le-linux" "aarch64-darwin"];
 }
diff --git a/lib/systems/examples.nix b/lib/systems/examples.nix
index 2476c3541b1..6a8f4e091aa 100644
--- a/lib/systems/examples.nix
+++ b/lib/systems/examples.nix
@@ -7,7 +7,6 @@ let
 
   riscv = bits: {
     config = "riscv${bits}-unknown-linux-gnu";
-    platform = platforms.riscv-multiplatform bits;
   };
 in
 
@@ -17,84 +16,88 @@ rec {
   #
   powernv = {
     config = "powerpc64le-unknown-linux-gnu";
-    platform = platforms.powernv;
   };
   musl-power = {
     config = "powerpc64le-unknown-linux-musl";
-    platform = platforms.powernv;
+  };
+
+  ppc64 = {
+    config = "powerpc64-unknown-linux-gnu";
+    gcc = { abi = "elfv2"; }; # for gcc configuration
+  };
+  ppc64-musl = {
+    config = "powerpc64-unknown-linux-musl";
+    gcc = { abi = "elfv2"; }; # for gcc configuration
   };
 
   sheevaplug = {
     config = "armv5tel-unknown-linux-gnueabi";
-    platform = platforms.sheevaplug;
-  };
+  } // platforms.sheevaplug;
 
   raspberryPi = {
     config = "armv6l-unknown-linux-gnueabihf";
-    platform = platforms.raspberrypi;
-  };
+  } // platforms.raspberrypi;
 
   remarkable1 = {
     config = "armv7l-unknown-linux-gnueabihf";
-    platform = platforms.zero-gravitas;
-  };
+  } // platforms.zero-gravitas;
 
   remarkable2 = {
     config = "armv7l-unknown-linux-gnueabihf";
-    platform = platforms.zero-sugar;
-  };
+  } // platforms.zero-sugar;
 
   armv7l-hf-multiplatform = {
     config = "armv7l-unknown-linux-gnueabihf";
-    platform = platforms.armv7l-hf-multiplatform;
   };
 
   aarch64-multiplatform = {
     config = "aarch64-unknown-linux-gnu";
-    platform = platforms.aarch64-multiplatform;
   };
 
   armv7a-android-prebuilt = {
     config = "armv7a-unknown-linux-androideabi";
+    rustc.config = "armv7-linux-androideabi";
     sdkVer = "29";
     ndkVer = "21";
-    platform = platforms.armv7a-android;
     useAndroidPrebuilt = true;
-  };
+  } // platforms.armv7a-android;
 
   aarch64-android-prebuilt = {
     config = "aarch64-unknown-linux-android";
+    rustc.config = "aarch64-linux-android";
     sdkVer = "29";
     ndkVer = "21";
-    platform = platforms.aarch64-multiplatform;
     useAndroidPrebuilt = true;
   };
 
-  scaleway-c1 = armv7l-hf-multiplatform // rec {
-    platform = platforms.scaleway-c1;
-    inherit (platform.gcc) fpu;
+  aarch64-android = {
+    config = "aarch64-unknown-linux-android";
+    sdkVer = "30";
+    ndkVer = "21";
+    libc = "bionic";
+    useAndroidPrebuilt = false;
+    useLLVM = true;
   };
 
+  scaleway-c1 = armv7l-hf-multiplatform // platforms.scaleway-c1;
+
   pogoplug4 = {
     config = "armv5tel-unknown-linux-gnueabi";
-    platform = platforms.pogoplug4;
-  };
+  } // platforms.pogoplug4;
 
   ben-nanonote = {
     config = "mipsel-unknown-linux-uclibc";
-    platform = platforms.ben_nanonote;
-  };
+  } // platforms.ben_nanonote;
 
   fuloongminipc = {
     config = "mipsel-unknown-linux-gnu";
-    platform = platforms.fuloong2f_n32;
-  };
+  } // platforms.fuloong2f_n32;
 
   muslpi = raspberryPi // {
     config = "armv6l-unknown-linux-musleabihf";
   };
 
-  aarch64-multiplatform-musl = aarch64-multiplatform // {
+  aarch64-multiplatform-musl = {
     config = "aarch64-unknown-linux-musl";
   };
 
@@ -110,13 +113,11 @@ rec {
   riscv64-embedded = {
     config = "riscv64-none-elf";
     libc = "newlib";
-    platform = platforms.riscv-multiplatform "64";
   };
 
   riscv32-embedded = {
     config = "riscv32-none-elf";
     libc = "newlib";
-    platform = platforms.riscv-multiplatform "32";
   };
 
   mmix = {
@@ -136,13 +137,11 @@ rec {
   vc4 = {
     config = "vc4-elf";
     libc = "newlib";
-    platform = {};
   };
 
   or1k = {
     config = "or1k-elf";
     libc = "newlib";
-    platform = {};
   };
 
   arm-embedded = {
@@ -152,6 +151,12 @@ rec {
   armhf-embedded = {
     config = "arm-none-eabihf";
     libc = "newlib";
+    # GCC8+ does not build without this
+    # (https://www.mail-archive.com/gcc-bugs@gcc.gnu.org/msg552339.html):
+    gcc = {
+      arch = "armv5t";
+      fpu = "vfp";
+    };
   };
 
   aarch64-embedded = {
@@ -200,40 +205,44 @@ rec {
   iphone64 = {
     config = "aarch64-apple-ios";
     # config = "aarch64-apple-darwin14";
-    sdkVer = "13.2";
-    xcodeVer = "11.3.1";
+    sdkVer = "14.3";
+    xcodeVer = "12.3";
     xcodePlatform = "iPhoneOS";
     useiOSPrebuilt = true;
-    platform = {};
   };
 
   iphone32 = {
     config = "armv7a-apple-ios";
     # config = "arm-apple-darwin10";
-    sdkVer = "13.2";
-    xcodeVer = "11.3.1";
+    sdkVer = "14.3";
+    xcodeVer = "12.3";
     xcodePlatform = "iPhoneOS";
     useiOSPrebuilt = true;
-    platform = {};
   };
 
   iphone64-simulator = {
     config = "x86_64-apple-ios";
     # config = "x86_64-apple-darwin14";
-    sdkVer = "13.2";
-    xcodeVer = "11.3.1";
+    sdkVer = "14.3";
+    xcodeVer = "12.3";
     xcodePlatform = "iPhoneSimulator";
+    darwinPlatform = "ios-simulator";
     useiOSPrebuilt = true;
-    platform = {};
   };
 
   iphone32-simulator = {
     config = "i686-apple-ios";
     # config = "i386-apple-darwin11";
-    sdkVer = "13.2";
-    xcodeVer = "11.3.1";
+    sdkVer = "14.3";
+    xcodeVer = "12.3";
     xcodePlatform = "iPhoneSimulator";
+    darwinPlatform = "ios-simulator";
     useiOSPrebuilt = true;
+  };
+
+  aarch64-darwin = {
+    config = "aarch64-apple-darwin";
+    xcodePlatform = "MacOSX";
     platform = {};
   };
 
@@ -245,7 +254,6 @@ rec {
   mingw32 = {
     config = "i686-w64-mingw32";
     libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain
-    platform = {};
   };
 
   # 64 bit mingw-w64
@@ -253,16 +261,23 @@ rec {
     # That's the triplet they use in the mingw-w64 docs.
     config = "x86_64-w64-mingw32";
     libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain
-    platform = {};
   };
 
   # BSDs
 
-  amd64-netbsd = {
+  amd64-netbsd = lib.warn "The amd64-netbsd system example is deprecated. Use x86_64-netbsd instead." x86_64-netbsd;
+
+  x86_64-netbsd = {
     config = "x86_64-unknown-netbsd";
     libc = "nblibc";
   };
 
+  x86_64-netbsd-llvm = {
+    config = "x86_64-unknown-netbsd";
+    libc = "nblibc";
+    useLLVM = true;
+  };
+
   #
   # WASM
   #
@@ -275,6 +290,5 @@ rec {
   # Ghcjs
   ghcjs = {
     config = "js-unknown-ghcjs";
-    platform = {};
   };
 }
diff --git a/lib/systems/parse.nix b/lib/systems/parse.nix
index a06ac0d11f7..accaeb652d0 100644
--- a/lib/systems/parse.nix
+++ b/lib/systems/parse.nix
@@ -121,15 +121,20 @@ rec {
     js       = { bits = 32; significantByte = littleEndian; family = "js"; };
   };
 
-  # Determine where two CPUs are compatible with each other. That is,
-  # can we run code built for system b on system a? For that to
-  # happen, then the set of all possible possible programs that system
-  # b accepts must be a subset of the set of all programs that system
-  # a accepts. This compatibility relation forms a category where each
-  # CPU is an object and each arrow from a to b represents
-  # compatibility. CPUs with multiple modes of Endianness are
-  # isomorphic while all CPUs are endomorphic because any program
-  # built for a CPU can run on that CPU.
+  # Determine when two CPUs are compatible with each other. That is,
+  # can code built for system B run on system A? For that to happen,
+  # the programs that system B accepts must be a subset of the
+  # programs that system A accepts.
+  #
+  # We have the following properties of the compatibility relation,
+  # which must be preserved when adding compatibility information for
+  # additional CPUs.
+  # - (reflexivity)
+  #   Every CPU is compatible with itself.
+  # - (transitivity)
+  #   If A is compatible with B and B is compatible with C then A is compatible with C.
+  # - (compatible under multiple endianness)
+  #   CPUs with multiple modes of endianness are pairwise compatible.
   isCompatible = a: b: with cpuTypes; lib.any lib.id [
     # x86
     (b == i386 && isCompatible a i486)
diff --git a/lib/systems/platforms.nix b/lib/systems/platforms.nix
index 7097e147966..92285346f75 100644
--- a/lib/systems/platforms.nix
+++ b/lib/systems/platforms.nix
@@ -1,39 +1,36 @@
 { lib }:
 rec {
-  pcBase = {
-    name = "pc";
-    kernelBaseConfig = "defconfig";
-    # Build whatever possible as a module, if not stated in the extra config.
-    kernelAutoModules = true;
-    kernelTarget = "bzImage";
-  };
-
-  pc64 = pcBase // { kernelArch = "x86_64"; };
-
-  pc32 = pcBase // { kernelArch = "i386"; };
-
-  pc32_simplekernel = pc32 // {
-    kernelAutoModules = false;
+  pc = {
+    linux-kernel = {
+      name = "pc";
+
+      baseConfig = "defconfig";
+      # Build whatever possible as a module, if not stated in the extra config.
+      autoModules = true;
+      target = "bzImage";
+    };
   };
 
-  pc64_simplekernel = pc64 // {
-    kernelAutoModules = false;
+  pc_simplekernel = lib.recursiveUpdate pc {
+    linux-kernel.autoModules = false;
   };
 
   powernv = {
-    name = "PowerNV";
-    kernelArch = "powerpc";
-    kernelBaseConfig = "powernv_defconfig";
-    kernelTarget = "zImage";
-    kernelInstallTarget = "install";
-    kernelFile = "vmlinux";
-    kernelAutoModules = true;
-    # avoid driver/FS trouble arising from unusual page size
-    kernelExtraConfig = ''
-      PPC_64K_PAGES n
-      PPC_4K_PAGES y
-      IPV6 y
-    '';
+    linux-kernel = {
+      name = "PowerNV";
+
+      baseConfig = "powernv_defconfig";
+      target = "zImage";
+      installTarget = "install";
+      file = "vmlinux";
+      autoModules = true;
+      # avoid driver/FS trouble arising from unusual page size
+      extraConfig = ''
+        PPC_64K_PAGES n
+        PPC_4K_PAGES y
+        IPV6 y
+      '';
+    };
   };
 
   ##
@@ -41,18 +38,121 @@ rec {
   ##
 
   pogoplug4 = {
-    name = "pogoplug4";
+    linux-kernel = {
+      name = "pogoplug4";
 
+      baseConfig = "multi_v5_defconfig";
+      autoModules = false;
+      extraConfig = ''
+        # Ubi for the mtd
+        MTD_UBI y
+        UBIFS_FS y
+        UBIFS_FS_XATTR y
+        UBIFS_FS_ADVANCED_COMPR y
+        UBIFS_FS_LZO y
+        UBIFS_FS_ZLIB y
+        UBIFS_FS_DEBUG n
+      '';
+      makeFlags = [ "LOADADDR=0x8000" ];
+      target = "uImage";
+      # TODO reenable once manual-config's config actually builds a .dtb and this is checked to be working
+      #DTB = true;
+    };
     gcc = {
       arch = "armv5te";
     };
+  };
+
+  sheevaplug = {
+    linux-kernel = {
+      name = "sheevaplug";
+
+      baseConfig = "multi_v5_defconfig";
+      autoModules = false;
+      extraConfig = ''
+        BLK_DEV_RAM y
+        BLK_DEV_INITRD y
+        BLK_DEV_CRYPTOLOOP m
+        BLK_DEV_DM m
+        DM_CRYPT m
+        MD y
+        REISERFS_FS m
+        BTRFS_FS m
+        XFS_FS m
+        JFS_FS m
+        EXT4_FS m
+        USB_STORAGE_CYPRESS_ATACB m
+
+        # mv cesa requires this sw fallback, for mv-sha1
+        CRYPTO_SHA1 y
+        # Fast crypto
+        CRYPTO_TWOFISH y
+        CRYPTO_TWOFISH_COMMON y
+        CRYPTO_BLOWFISH y
+        CRYPTO_BLOWFISH_COMMON y
+
+        IP_PNP y
+        IP_PNP_DHCP y
+        NFS_FS y
+        ROOT_NFS y
+        TUN m
+        NFS_V4 y
+        NFS_V4_1 y
+        NFS_FSCACHE y
+        NFSD m
+        NFSD_V2_ACL y
+        NFSD_V3 y
+        NFSD_V3_ACL y
+        NFSD_V4 y
+        NETFILTER y
+        IP_NF_IPTABLES y
+        IP_NF_FILTER y
+        IP_NF_MATCH_ADDRTYPE y
+        IP_NF_TARGET_LOG y
+        IP_NF_MANGLE y
+        IPV6 m
+        VLAN_8021Q m
+
+        CIFS y
+        CIFS_XATTR y
+        CIFS_POSIX y
+        CIFS_FSCACHE y
+        CIFS_ACL y
+
+        WATCHDOG y
+        WATCHDOG_CORE y
+        ORION_WATCHDOG m
+
+        ZRAM m
+        NETCONSOLE m
+
+        # Disable OABI to have seccomp_filter (required for systemd)
+        # https://github.com/raspberrypi/firmware/issues/651
+        OABI_COMPAT n
+
+        # Fail to build
+        DRM n
+        SCSI_ADVANSYS n
+        USB_ISP1362_HCD n
+        SND_SOC n
+        SND_ALI5451 n
+        FB_SAVAGE n
+        SCSI_NSP32 n
+        ATA_SFF n
+        SUNGEM n
+        IRDA n
+        ATM_HE n
+        SCSI_ACARD n
+        BLK_DEV_CMD640_ENHANCED n
+
+        FUSE_FS m
+
+        # systemd uses cgroups
+        CGROUPS y
+
+        # Latencytop
+        LATENCYTOP y
 
-    kernelMajor = "2.6";
-    kernelBaseConfig = "multi_v5_defconfig";
-    kernelArch = "arm";
-    kernelAutoModules = false;
-    kernelExtraConfig =
-      ''
         # Ubi for the mtd
         MTD_UBI y
         UBIFS_FS y
@@ -61,139 +161,36 @@ rec {
         UBIFS_FS_LZO y
         UBIFS_FS_ZLIB y
         UBIFS_FS_DEBUG n
-      '';
-    kernelMakeFlags = [ "LOADADDR=0x8000" ];
-    kernelTarget = "uImage";
-    # TODO reenable once manual-config's config actually builds a .dtb and this is checked to be working
-    #kernelDTB = true;
-  };
 
-  sheevaplug = {
-    name = "sheevaplug";
-    kernelMajor = "2.6";
-    kernelBaseConfig = "multi_v5_defconfig";
-    kernelArch = "arm";
-    kernelAutoModules = false;
-    kernelExtraConfig = ''
-      BLK_DEV_RAM y
-      BLK_DEV_INITRD y
-      BLK_DEV_CRYPTOLOOP m
-      BLK_DEV_DM m
-      DM_CRYPT m
-      MD y
-      REISERFS_FS m
-      BTRFS_FS m
-      XFS_FS m
-      JFS_FS m
-      EXT4_FS m
-      USB_STORAGE_CYPRESS_ATACB m
-
-      # mv cesa requires this sw fallback, for mv-sha1
-      CRYPTO_SHA1 y
-      # Fast crypto
-      CRYPTO_TWOFISH y
-      CRYPTO_TWOFISH_COMMON y
-      CRYPTO_BLOWFISH y
-      CRYPTO_BLOWFISH_COMMON y
-
-      IP_PNP y
-      IP_PNP_DHCP y
-      NFS_FS y
-      ROOT_NFS y
-      TUN m
-      NFS_V4 y
-      NFS_V4_1 y
-      NFS_FSCACHE y
-      NFSD m
-      NFSD_V2_ACL y
-      NFSD_V3 y
-      NFSD_V3_ACL y
-      NFSD_V4 y
-      NETFILTER y
-      IP_NF_IPTABLES y
-      IP_NF_FILTER y
-      IP_NF_MATCH_ADDRTYPE y
-      IP_NF_TARGET_LOG y
-      IP_NF_MANGLE y
-      IPV6 m
-      VLAN_8021Q m
-
-      CIFS y
-      CIFS_XATTR y
-      CIFS_POSIX y
-      CIFS_FSCACHE y
-      CIFS_ACL y
-
-      WATCHDOG y
-      WATCHDOG_CORE y
-      ORION_WATCHDOG m
-
-      ZRAM m
-      NETCONSOLE m
-
-      # Disable OABI to have seccomp_filter (required for systemd)
-      # https://github.com/raspberrypi/firmware/issues/651
-      OABI_COMPAT n
-
-      # Fail to build
-      DRM n
-      SCSI_ADVANSYS n
-      USB_ISP1362_HCD n
-      SND_SOC n
-      SND_ALI5451 n
-      FB_SAVAGE n
-      SCSI_NSP32 n
-      ATA_SFF n
-      SUNGEM n
-      IRDA n
-      ATM_HE n
-      SCSI_ACARD n
-      BLK_DEV_CMD640_ENHANCED n
-
-      FUSE_FS m
-
-      # systemd uses cgroups
-      CGROUPS y
-
-      # Latencytop
-      LATENCYTOP y
-
-      # Ubi for the mtd
-      MTD_UBI y
-      UBIFS_FS y
-      UBIFS_FS_XATTR y
-      UBIFS_FS_ADVANCED_COMPR y
-      UBIFS_FS_LZO y
-      UBIFS_FS_ZLIB y
-      UBIFS_FS_DEBUG n
-
-      # Kdb, for kernel troubles
-      KGDB y
-      KGDB_SERIAL_CONSOLE y
-      KGDB_KDB y
-    '';
-    kernelMakeFlags = [ "LOADADDR=0x0200000" ];
-    kernelTarget = "uImage";
-    kernelDTB = true; # Beyond 3.10
+        # Kdb, for kernel troubles
+        KGDB y
+        KGDB_SERIAL_CONSOLE y
+        KGDB_KDB y
+      '';
+      makeFlags = [ "LOADADDR=0x0200000" ];
+      target = "uImage";
+      DTB = true; # Beyond 3.10
+    };
     gcc = {
       arch = "armv5te";
     };
   };
 
   raspberrypi = {
-    name = "raspberrypi";
-    kernelMajor = "2.6";
-    kernelBaseConfig = "bcm2835_defconfig";
-    kernelDTB = true;
-    kernelArch = "arm";
-    kernelAutoModules = true;
-    kernelPreferBuiltin = true;
-    kernelExtraConfig = ''
-      # Disable OABI to have seccomp_filter (required for systemd)
-      # https://github.com/raspberrypi/firmware/issues/651
-      OABI_COMPAT n
-    '';
-    kernelTarget = "zImage";
+    linux-kernel = {
+      name = "raspberrypi";
+
+      baseConfig = "bcm2835_defconfig";
+      DTB = true;
+      autoModules = true;
+      preferBuiltin = true;
+      extraConfig = ''
+        # Disable OABI to have seccomp_filter (required for systemd)
+        # https://github.com/raspberrypi/firmware/issues/651
+        OABI_COMPAT n
+      '';
+      target = "zImage";
+    };
     gcc = {
       arch = "armv6";
       fpu = "vfp";
@@ -204,13 +201,15 @@ rec {
   raspberrypi2 = armv7l-hf-multiplatform;
 
   zero-gravitas = {
-    name = "zero-gravitas";
-    kernelBaseConfig = "zero-gravitas_defconfig";
-    kernelArch = "arm";
-    # kernelTarget verified by checking /boot on reMarkable 1 device
-    kernelTarget = "zImage";
-    kernelAutoModules = false;
-    kernelDTB = true;
+    linux-kernel = {
+      name = "zero-gravitas";
+
+      baseConfig = "zero-gravitas_defconfig";
+      # Target verified by checking /boot on reMarkable 1 device
+      target = "zImage";
+      autoModules = false;
+      DTB = true;
+    };
     gcc = {
       fpu = "neon";
       cpu = "cortex-a9";
@@ -218,13 +217,15 @@ rec {
   };
 
   zero-sugar = {
-    name = "zero-sugar";
-    kernelBaseConfig = "zero-sugar_defconfig";
-    kernelArch = "arm";
-    kernelDTB = true;
-    kernelAutoModules = false;
-    kernelPreferBuiltin = true;
-    kernelTarget = "zImage";
+    linux-kernel = {
+      name = "zero-sugar";
+
+      baseConfig = "zero-sugar_defconfig";
+      DTB = true;
+      autoModules = false;
+      preferBuiltin = true;
+      target = "zImage";
+    };
     gcc = {
       cpu = "cortex-a7";
       fpu = "neon-vfpv4";
@@ -232,7 +233,7 @@ rec {
     };
   };
 
-  scaleway-c1 = armv7l-hf-multiplatform // {
+  scaleway-c1 = lib.recursiveUpdate armv7l-hf-multiplatform {
     gcc = {
       cpu = "cortex-a9";
       fpu = "vfpv3";
@@ -240,13 +241,11 @@ rec {
   };
 
   utilite = {
-    name = "utilite";
-    kernelMajor = "2.6";
-    kernelBaseConfig = "multi_v7_defconfig";
-    kernelArch = "arm";
-    kernelAutoModules = false;
-    kernelExtraConfig =
-      ''
+    linux-kernel = {
+      name = "utilite";
+      maseConfig = "multi_v7_defconfig";
+      autoModules = false;
+      extraConfig = ''
         # Ubi for the mtd
         MTD_UBI y
         UBIFS_FS y
@@ -256,35 +255,37 @@ rec {
         UBIFS_FS_ZLIB y
         UBIFS_FS_DEBUG n
       '';
-    kernelMakeFlags = [ "LOADADDR=0x10800000" ];
-    kernelTarget = "uImage";
-    kernelDTB = true;
+      makeFlags = [ "LOADADDR=0x10800000" ];
+      target = "uImage";
+      DTB = true;
+    };
     gcc = {
       cpu = "cortex-a9";
       fpu = "neon";
     };
   };
 
-  guruplug = sheevaplug // {
+  guruplug = lib.recursiveUpdate sheevaplug {
     # Define `CONFIG_MACH_GURUPLUG' (see
     # <http://kerneltrap.org/mailarchive/git-commits-head/2010/5/19/33618>)
     # and other GuruPlug-specific things.  Requires the `guruplug-defconfig'
     # patch.
-
-    kernelBaseConfig = "guruplug_defconfig";
+    linux-kernel.baseConfig = "guruplug_defconfig";
   };
 
-  beaglebone = armv7l-hf-multiplatform // {
-    name = "beaglebone";
-    kernelBaseConfig = "bb.org_defconfig";
-    kernelAutoModules = false;
-    kernelExtraConfig = ""; # TBD kernel config
-    kernelTarget = "zImage";
+  beaglebone = lib.recursiveUpdate armv7l-hf-multiplatform {
+    linux-kernel = {
+      name = "beaglebone";
+      baseConfig = "bb.org_defconfig";
+      autoModules = false;
+      extraConfig = ""; # TBD kernel config
+      target = "zImage";
+    };
   };
 
   # https://developer.android.com/ndk/guides/abis#v7a
-  armv7a-android =  {
-    name = "armeabi-v7a";
+  armv7a-android = {
+    linux-kernel.name = "armeabi-v7a";
     gcc = {
       arch = "armv7-a";
       float-abi = "softfp";
@@ -293,30 +294,29 @@ rec {
   };
 
   armv7l-hf-multiplatform = {
-    name = "armv7l-hf-multiplatform";
-    kernelMajor = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc.
-    kernelBaseConfig = "multi_v7_defconfig";
-    kernelArch = "arm";
-    kernelDTB = true;
-    kernelAutoModules = true;
-    kernelPreferBuiltin = true;
-    kernelTarget = "zImage";
-    kernelExtraConfig = ''
-      # Serial port for Raspberry Pi 3. Upstream forgot to add it to the ARMv7 defconfig.
-      SERIAL_8250_BCM2835AUX y
-      SERIAL_8250_EXTENDED y
-      SERIAL_8250_SHARE_IRQ y
-
-      # Fix broken sunxi-sid nvmem driver.
-      TI_CPTS y
-
-      # Hangs ODROID-XU4
-      ARM_BIG_LITTLE_CPUIDLE n
-
-      # Disable OABI to have seccomp_filter (required for systemd)
-      # https://github.com/raspberrypi/firmware/issues/651
-      OABI_COMPAT n
-    '';
+    linux-kernel = {
+      name = "armv7l-hf-multiplatform";
+      Major = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc.
+      baseConfig = "multi_v7_defconfig";
+      DTB = true;
+      autoModules = true;
+      preferBuiltin = true;
+      target = "zImage";
+      extraConfig = ''
+        # Serial port for Raspberry Pi 3. Wasn't included in ARMv7 defconfig
+        # until 4.17.
+        SERIAL_8250_BCM2835AUX y
+        SERIAL_8250_EXTENDED y
+        SERIAL_8250_SHARE_IRQ y
+
+        # Hangs ODROID-XU4
+        ARM_BIG_LITTLE_CPUIDLE n
+
+        # Disable OABI to have seccomp_filter (required for systemd)
+        # https://github.com/raspberrypi/firmware/issues/651
+        OABI_COMPAT n
+      '';
+    };
     gcc = {
       # Some table about fpu flags:
       # http://community.arm.com/servlet/JiveServlet/showImage/38-1981-3827/blogentry-103749-004812900+1365712953_thumb.png
@@ -341,48 +341,55 @@ rec {
   };
 
   aarch64-multiplatform = {
-    name = "aarch64-multiplatform";
-    kernelMajor = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc.
-    kernelBaseConfig = "defconfig";
-    kernelArch = "arm64";
-    kernelDTB = true;
-    kernelAutoModules = true;
-    kernelPreferBuiltin = true;
-    kernelExtraConfig = ''
-      # Raspberry Pi 3 stuff. Not needed for kernels >= 4.10.
-      ARCH_BCM2835 y
-      BCM2835_MBOX y
-      BCM2835_WDT y
-      RASPBERRYPI_FIRMWARE y
-      RASPBERRYPI_POWER y
-      SERIAL_8250_BCM2835AUX y
-      SERIAL_8250_EXTENDED y
-      SERIAL_8250_SHARE_IRQ y
-
-      # Cavium ThunderX stuff.
-      PCI_HOST_THUNDER_ECAM y
-
-      # Nvidia Tegra stuff.
-      PCI_TEGRA y
-
-      # The default (=y) forces us to have the XHCI firmware available in initrd,
-      # which our initrd builder can't currently do easily.
-      USB_XHCI_TEGRA m
-    '';
-    kernelTarget = "Image";
+    linux-kernel = {
+      name = "aarch64-multiplatform";
+      baseConfig = "defconfig";
+      DTB = true;
+      autoModules = true;
+      preferBuiltin = true;
+      extraConfig = ''
+        # Raspberry Pi 3 stuff. Not needed for   s >= 4.10.
+        ARCH_BCM2835 y
+        BCM2835_MBOX y
+        BCM2835_WDT y
+        RASPBERRYPI_FIRMWARE y
+        RASPBERRYPI_POWER y
+        SERIAL_8250_BCM2835AUX y
+        SERIAL_8250_EXTENDED y
+        SERIAL_8250_SHARE_IRQ y
+
+        # Cavium ThunderX stuff.
+        PCI_HOST_THUNDER_ECAM y
+
+        # Nvidia Tegra stuff.
+        PCI_TEGRA y
+
+        # The default (=y) forces us to have the XHCI firmware available in initrd,
+        # which our initrd builder can't currently do easily.
+        USB_XHCI_TEGRA m
+      '';
+      target = "Image";
+    };
     gcc = {
       arch = "armv8-a";
     };
   };
 
+  apple-m1 = {
+    gcc = {
+      arch = "armv8.3-a+crypto+sha2+aes+crc+fp16+lse+simd+ras+rdm+rcpc";
+      cpu = "apple-a13";
+    };
+  };
+
   ##
   ## MIPS
   ##
 
   ben_nanonote = {
-    name = "ben_nanonote";
-    kernelMajor = "2.6";
-    kernelArch = "mips";
+    linux-kernel = {
+      name = "ben_nanonote";
+    };
     gcc = {
       arch = "mips32";
       float = "soft";
@@ -390,76 +397,76 @@ rec {
   };
 
   fuloong2f_n32 = {
-    name = "fuloong2f_n32";
-    kernelMajor = "2.6";
-    kernelBaseConfig = "lemote2f_defconfig";
-    kernelArch = "mips";
-    kernelAutoModules = false;
-    kernelExtraConfig = ''
-      MIGRATION n
-      COMPACTION n
-
-      # nixos mounts some cgroup
-      CGROUPS y
-
-      BLK_DEV_RAM y
-      BLK_DEV_INITRD y
-      BLK_DEV_CRYPTOLOOP m
-      BLK_DEV_DM m
-      DM_CRYPT m
-      MD y
-      REISERFS_FS m
-      EXT4_FS m
-      USB_STORAGE_CYPRESS_ATACB m
-
-      IP_PNP y
-      IP_PNP_DHCP y
-      IP_PNP_BOOTP y
-      NFS_FS y
-      ROOT_NFS y
-      TUN m
-      NFS_V4 y
-      NFS_V4_1 y
-      NFS_FSCACHE y
-      NFSD m
-      NFSD_V2_ACL y
-      NFSD_V3 y
-      NFSD_V3_ACL y
-      NFSD_V4 y
-
-      # Fail to build
-      DRM n
-      SCSI_ADVANSYS n
-      USB_ISP1362_HCD n
-      SND_SOC n
-      SND_ALI5451 n
-      FB_SAVAGE n
-      SCSI_NSP32 n
-      ATA_SFF n
-      SUNGEM n
-      IRDA n
-      ATM_HE n
-      SCSI_ACARD n
-      BLK_DEV_CMD640_ENHANCED n
-
-      FUSE_FS m
-
-      # Needed for udev >= 150
-      SYSFS_DEPRECATED_V2 n
-
-      VGA_CONSOLE n
-      VT_HW_CONSOLE_BINDING y
-      SERIAL_8250_CONSOLE y
-      FRAMEBUFFER_CONSOLE y
-      EXT2_FS y
-      EXT3_FS y
-      REISERFS_FS y
-      MAGIC_SYSRQ y
-
-      # The kernel doesn't boot at all, with FTRACE
-      FTRACE n
-    '';
-    kernelTarget = "vmlinux";
+    linux-kernel = {
+      name = "fuloong2f_n32";
+      baseConfig = "lemote2f_defconfig";
+      autoModules = false;
+      extraConfig = ''
+        MIGRATION n
+        COMPACTION n
+
+        # nixos mounts some cgroup
+        CGROUPS y
+
+        BLK_DEV_RAM y
+        BLK_DEV_INITRD y
+        BLK_DEV_CRYPTOLOOP m
+        BLK_DEV_DM m
+        DM_CRYPT m
+        MD y
+        REISERFS_FS m
+        EXT4_FS m
+        USB_STORAGE_CYPRESS_ATACB m
+
+        IP_PNP y
+        IP_PNP_DHCP y
+        IP_PNP_BOOTP y
+        NFS_FS y
+        ROOT_NFS y
+        TUN m
+        NFS_V4 y
+        NFS_V4_1 y
+        NFS_FSCACHE y
+        NFSD m
+        NFSD_V2_ACL y
+        NFSD_V3 y
+        NFSD_V3_ACL y
+        NFSD_V4 y
+
+        # Fail to build
+        DRM n
+        SCSI_ADVANSYS n
+        USB_ISP1362_HCD n
+        SND_SOC n
+        SND_ALI5451 n
+        FB_SAVAGE n
+        SCSI_NSP32 n
+        ATA_SFF n
+        SUNGEM n
+        IRDA n
+        ATM_HE n
+        SCSI_ACARD n
+        BLK_DEV_CMD640_ENHANCED n
+
+        FUSE_FS m
+
+        # Needed for udev >= 150
+        SYSFS_DEPRECATED_V2 n
+
+        VGA_CONSOLE n
+        VT_HW_CONSOLE_BINDING y
+        SERIAL_8250_CONSOLE y
+        FRAMEBUFFER_CONSOLE y
+        EXT2_FS y
+        EXT3_FS y
+        REISERFS_FS y
+        MAGIC_SYSRQ y
+
+        # The kernel doesn't boot at all, with FTRACE
+        FTRACE n
+      '';
+      target = "vmlinux";
+    };
     gcc = {
       arch = "loongson2f";
       float = "hard";
@@ -471,30 +478,40 @@ rec {
   ## Other
   ##
 
-  riscv-multiplatform = bits: {
-    name = "riscv-multiplatform";
-    kernelArch = "riscv";
-    bfdEmulation = "elf${bits}lriscv";
-    kernelTarget = "vmlinux";
-    kernelAutoModules = true;
-    kernelBaseConfig = "defconfig";
-    kernelExtraConfig = ''
-      FTRACE n
-      SERIAL_OF_PLATFORM y
-    '';
+  riscv-multiplatform = {
+    linux-kernel = {
+      name = "riscv-multiplatform";
+      target = "Image";
+      autoModules = true;
+      baseConfig = "defconfig";
+      DTB = true;
+      extraConfig = ''
+        SERIAL_OF_PLATFORM y
+      '';
+    };
   };
 
-  selectBySystem = system: {
-      i486-linux = pc32;
-      i586-linux = pc32;
-      i686-linux = pc32;
-      x86_64-linux = pc64;
-      armv5tel-linux = sheevaplug;
-      armv6l-linux = raspberrypi;
-      armv7a-linux = armv7l-hf-multiplatform;
-      armv7l-linux = armv7l-hf-multiplatform;
-      aarch64-linux = aarch64-multiplatform;
-      mipsel-linux = fuloong2f_n32;
-      powerpc64le-linux = powernv;
-    }.${system} or pcBase;
+  select = platform:
+    # x86
+    /**/ if platform.isx86 then pc
+
+    # ARM
+    else if platform.isAarch32 then let
+      version = platform.parsed.cpu.version or null;
+      in     if version == null then pc
+        else if lib.versionOlder version "6" then sheevaplug
+        else if lib.versionOlder version "7" then raspberrypi
+        else armv7l-hf-multiplatform
+
+    else if platform.isAarch64 then
+      if platform.isDarwin then apple-m1
+      else aarch64-multiplatform
+
+    else if platform.isRiscV then riscv-multiplatform
+
+    else if platform.parsed.cpu == lib.systems.parse.cpuTypes.mipsel then fuloong2f_n32
+
+    else if platform.parsed.cpu == lib.systems.parse.cpuTypes.powerpc64le then powernv
+
+    else pc;
 }
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 35a5801c724..0d249968402 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -660,4 +660,71 @@ runTests {
     expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
   };
 
+  testCartesianProductOfEmptySet = {
+    expr = cartesianProductOfSets {};
+    expected = [ {} ];
+  };
+
+  testCartesianProductOfOneSet = {
+    expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
+    expected = [ { a = 1; } { a = 2; } { a = 3; } ];
+  };
+
+  testCartesianProductOfTwoSets = {
+    expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
+    expected = [
+      { a = 1; b = 10; }
+      { a = 1; b = 20; }
+    ];
+  };
+
+  testCartesianProductOfTwoSetsWithOneEmpty = {
+    expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
+    expected = [ ];
+  };
+
+  testCartesianProductOfThreeSets = {
+    expr = cartesianProductOfSets {
+      a = [   1   2   3 ];
+      b = [  10  20  30 ];
+      c = [ 100 200 300 ];
+    };
+    expected = [
+      { a = 1; b = 10; c = 100; }
+      { a = 1; b = 10; c = 200; }
+      { a = 1; b = 10; c = 300; }
+
+      { a = 1; b = 20; c = 100; }
+      { a = 1; b = 20; c = 200; }
+      { a = 1; b = 20; c = 300; }
+
+      { a = 1; b = 30; c = 100; }
+      { a = 1; b = 30; c = 200; }
+      { a = 1; b = 30; c = 300; }
+
+      { a = 2; b = 10; c = 100; }
+      { a = 2; b = 10; c = 200; }
+      { a = 2; b = 10; c = 300; }
+
+      { a = 2; b = 20; c = 100; }
+      { a = 2; b = 20; c = 200; }
+      { a = 2; b = 20; c = 300; }
+
+      { a = 2; b = 30; c = 100; }
+      { a = 2; b = 30; c = 200; }
+      { a = 2; b = 30; c = 300; }
+
+      { a = 3; b = 10; c = 100; }
+      { a = 3; b = 10; c = 200; }
+      { a = 3; b = 10; c = 300; }
+
+      { a = 3; b = 20; c = 100; }
+      { a = 3; b = 20; c = 200; }
+      { a = 3; b = 20; c = 300; }
+
+      { a = 3; b = 30; c = 100; }
+      { a = 3; b = 30; c = 200; }
+      { a = 3; b = 30; c = 300; }
+    ];
+  };
 }
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index 309c5311361..2e57c2f8e2a 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -169,12 +169,15 @@ checkConfigOutput "foo" config.submodule.foo ./declare-submoduleWith-special.nix
 ## shorthandOnlyDefines config behaves as expected
 checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
 checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
-checkConfigError 'value is a boolean while a set was expected' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigError "You're trying to declare a value of type \`bool'\nrather than an attribute-set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
 checkConfigOutput "true" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
 
 ## submoduleWith should merge all modules in one swoop
 checkConfigOutput "true" config.submodule.inner ./declare-submoduleWith-modules.nix
 checkConfigOutput "true" config.submodule.outer ./declare-submoduleWith-modules.nix
+# Should also be able to evaluate the type name (which evaluates freeformType,
+# which evaluates all the modules defined by the type)
+checkConfigOutput "submodule" options.submodule.type.description ./declare-submoduleWith-modules.nix
 
 ## Paths should be allowed as values and work as expected
 checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.nix
@@ -262,6 +265,13 @@ checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix
 checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix
 checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix
 
+## types.functionTo
+checkConfigOutput "input is input" config.result ./functionTo/trivial.nix
+checkConfigOutput "a b" config.result ./functionTo/merging-list.nix
+checkConfigError 'A definition for option .fun.\[function body\]. is not of type .string.. Definition values:\n- In .*wrong-type.nix' config.result ./functionTo/wrong-type.nix
+checkConfigOutput "b a" config.result ./functionTo/list-order.nix
+checkConfigOutput "a c" config.result ./functionTo/merging-attrs.nix
+
 cat <<EOF
 ====== module tests ======
 $pass Pass
diff --git a/lib/tests/modules/declare-submoduleWith-modules.nix b/lib/tests/modules/declare-submoduleWith-modules.nix
index 4736ab41751..a8b82d17688 100644
--- a/lib/tests/modules/declare-submoduleWith-modules.nix
+++ b/lib/tests/modules/declare-submoduleWith-modules.nix
@@ -8,9 +8,6 @@
             default = false;
           };
         }
-        {
-          outer = true;
-        }
       ];
     };
     default = {};
@@ -25,6 +22,7 @@
     })
     {
       inner = true;
+      outer = true;
     }
   ];
 }
diff --git a/lib/tests/modules/functionTo/list-order.nix b/lib/tests/modules/functionTo/list-order.nix
new file mode 100644
index 00000000000..77a1a43a84f
--- /dev/null
+++ b/lib/tests/modules/functionTo/list-order.nix
@@ -0,0 +1,25 @@
+
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+in {
+  options = {
+    fun = lib.mkOption {
+      type = types.functionTo (types.listOf types.str);
+    };
+
+    result = lib.mkOption {
+      type = types.str;
+      default = toString (config.fun {
+        a = "a";
+        b = "b";
+        c = "c";
+      });
+    };
+  };
+
+  config.fun = lib.mkMerge [
+    (input: lib.mkAfter [ input.a ])
+    (input: [ input.b ])
+  ];
+}
diff --git a/lib/tests/modules/functionTo/merging-attrs.nix b/lib/tests/modules/functionTo/merging-attrs.nix
new file mode 100644
index 00000000000..97c015f928a
--- /dev/null
+++ b/lib/tests/modules/functionTo/merging-attrs.nix
@@ -0,0 +1,27 @@
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+in {
+  options = {
+    fun = lib.mkOption {
+      type = types.functionTo (types.attrsOf types.str);
+    };
+
+    result = lib.mkOption {
+      type = types.str;
+      default = toString (lib.attrValues (config.fun {
+        a = "a";
+        b = "b";
+        c = "c";
+      }));
+    };
+  };
+
+  config.fun = lib.mkMerge [
+    (input: { inherit (input) a; })
+    (input: { inherit (input) b; })
+    (input: {
+      b = lib.mkForce input.c;
+    })
+  ];
+}
diff --git a/lib/tests/modules/functionTo/merging-list.nix b/lib/tests/modules/functionTo/merging-list.nix
new file mode 100644
index 00000000000..15fcd2bdcc4
--- /dev/null
+++ b/lib/tests/modules/functionTo/merging-list.nix
@@ -0,0 +1,24 @@
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+in {
+  options = {
+    fun = lib.mkOption {
+      type = types.functionTo (types.listOf types.str);
+    };
+
+    result = lib.mkOption {
+      type = types.str;
+      default = toString (config.fun {
+        a = "a";
+        b = "b";
+        c = "c";
+      });
+    };
+  };
+
+  config.fun = lib.mkMerge [
+    (input: [ input.a ])
+    (input: [ input.b ])
+  ];
+}
diff --git a/lib/tests/modules/functionTo/trivial.nix b/lib/tests/modules/functionTo/trivial.nix
new file mode 100644
index 00000000000..0962a0cf893
--- /dev/null
+++ b/lib/tests/modules/functionTo/trivial.nix
@@ -0,0 +1,17 @@
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+in {
+  options = {
+    fun = lib.mkOption {
+      type = types.functionTo types.str;
+    };
+
+    result = lib.mkOption {
+      type = types.str;
+      default = config.fun "input";
+    };
+  };
+
+  config.fun = input: "input is ${input}";
+}
diff --git a/lib/tests/modules/functionTo/wrong-type.nix b/lib/tests/modules/functionTo/wrong-type.nix
new file mode 100644
index 00000000000..fd65b75088d
--- /dev/null
+++ b/lib/tests/modules/functionTo/wrong-type.nix
@@ -0,0 +1,18 @@
+
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+in {
+  options = {
+    fun = lib.mkOption {
+      type = types.functionTo types.str;
+    };
+
+    result = lib.mkOption {
+      type = types.str;
+      default = config.fun 0;
+    };
+  };
+
+  config.fun = input: input + 1;
+}
diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix
index eed7ee725bc..36f82b783b4 100644
--- a/lib/tests/systems.nix
+++ b/lib/tests/systems.nix
@@ -15,9 +15,9 @@ in
 with lib.systems.doubles; lib.runTests {
   testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox);
 
-  testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-none" "armv7a-linux" "armv7l-linux" "arm-none" "armv7a-darwin" ];
+  testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ];
   testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
-  testmips = mseteq mips [ "mipsel-linux" ];
+  testmips = mseteq mips [ "mipsel-linux" "mipsel-netbsd" ];
   testmmix = mseteq mmix [ "mmix-mmixware" ];
   testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ];
 
@@ -28,8 +28,8 @@ with lib.systems.doubles; lib.runTests {
   testredox = mseteq redox [ "x86_64-redox" ];
   testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */);
   testillumos = mseteq illumos [ "x86_64-solaris" ];
-  testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "mipsel-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux" "powerpc64le-linux" ];
-  testnetbsd = mseteq netbsd [ "i686-netbsd" "x86_64-netbsd" ];
+  testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "mipsel-linux" "riscv32-linux" "riscv64-linux" "x86_64-linux" "powerpc64-linux" "powerpc64le-linux" ];
+  testnetbsd = mseteq netbsd [ "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "i686-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" ];
   testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ];
   testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "i686-windows" "x86_64-windows" ];
   testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox);
diff --git a/lib/trivial.nix b/lib/trivial.nix
index 268f39d3210..e1581f1e91d 100644
--- a/lib/trivial.nix
+++ b/lib/trivial.nix
@@ -158,7 +158,7 @@ rec {
     seq deepSeq genericClosure;
 
 
-  ## nixpks version strings
+  ## nixpkgs version strings
 
   /* Returns the current full nixpkgs version number. */
   version = release + versionSuffix;
@@ -171,7 +171,7 @@ rec {
      On each release the first letter is bumped and a new animal is chosen
      starting with that new letter.
   */
-  codeName = "Okapi";
+  codeName = "Porcupine";
 
   /* Returns the current nixpkgs version suffix as string. */
   versionSuffix =
@@ -297,12 +297,15 @@ rec {
   # Usage:
   # {
   #   foo = lib.warn "foo is deprecated" oldFoo;
+  #   bar = lib.warnIf (bar == "") "Empty bar is deprecated" bar;
   # }
   #
   # TODO: figure out a clever way to integrate location information from
   # something like __unsafeGetAttrPos.
 
   warn = msg: builtins.trace "warning: ${msg}";
+  warnIf = cond: msg: if cond then warn msg else id;
+
   info = msg: builtins.trace "INFO: ${msg}";
 
   showWarnings = warnings: res: lib.fold (w: x: warn w x) res warnings;
diff --git a/lib/types.nix b/lib/types.nix
index ee891f8231b..f47a1f92de7 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -147,9 +147,13 @@ rec {
     , # The deprecation message to display when this type is used by an option
       # If null, the type isn't deprecated
       deprecationMessage ? null
+    , # The types that occur in the definition of this type. This is used to
+      # issue deprecation warnings recursively. Can also be used to reuse
+      # nested types
+      nestedTypes ? {}
     }:
     { _type = "option-type";
-      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage;
+      inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage nestedTypes;
       description = if description == null then name else description;
     };
 
@@ -256,14 +260,14 @@ rec {
         };
         u8 = unsign 8 256;
         u16 = unsign 16 65536;
-        # the biggest int a 64-bit Nix accepts is 2^63 - 1 (9223372036854775808), for a 32-bit Nix it is 2^31 - 1 (2147483647)
-        # the smallest int a 64-bit Nix accepts is -2^63 (-9223372036854775807), for a 32-bit Nix it is -2^31 (-2147483648)
-        # u32 = unsign 32 4294967296;
+        # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
+        # the smallest int Nix accepts is -2^63 (-9223372036854775807)
+        u32 = unsign 32 4294967296;
         # u64 = unsign 64 18446744073709551616;
 
         s8 = sign 8 256;
         s16 = sign 16 65536;
-        # s32 = sign 32 4294967296;
+        s32 = sign 32 4294967296;
       };
 
     # Alias of u16 for a port number
@@ -337,7 +341,7 @@ rec {
     };
 
     shellPackage = package // {
-      check = x: (package.check x) && (hasAttr "shellPath" x);
+      check = x: isDerivation x && hasAttr "shellPath" x;
     };
 
     path = mkOptionType {
@@ -365,6 +369,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: listOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     nonEmptyListOf = elemType:
@@ -389,6 +394,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: attrsOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # A version of attrsOf that's lazy in its values at the expense of
@@ -413,6 +419,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # TODO: drop this in the future:
@@ -421,6 +428,7 @@ rec {
       deprecationMessage = "Mixing lists with attribute values is no longer"
         + " possible; please use `types.attrsOf` instead. See"
         + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
+      nestedTypes.elemType = elemType;
     };
 
     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
@@ -433,6 +441,7 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: uniq (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
     };
 
     # Null or value of ...
@@ -451,6 +460,18 @@ rec {
       getSubModules = elemType.getSubModules;
       substSubModules = m: nullOr (elemType.substSubModules m);
       functor = (defaultFunctor name) // { wrapped = elemType; };
+      nestedTypes.elemType = elemType;
+    };
+
+    functionTo = elemType: mkOptionType {
+      name = "functionTo";
+      description = "function that evaluates to a(n) ${elemType.name}";
+      check = isFunction;
+      merge = loc: defs:
+        fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue;
+      getSubOptions = elemType.getSubOptions;
+      getSubModules = elemType.getSubModules;
+      substSubModules = m: functionTo (elemType.substSubModules m);
     };
 
     # A submodule (like typed attribute set). See NixOS manual.
@@ -524,6 +545,9 @@ rec {
         substSubModules = m: submoduleWith (attrs // {
           modules = m;
         });
+        nestedTypes = lib.optionalAttrs (freeformType != null) {
+          freeformType = freeformType;
+        };
         functor = defaultFunctor name // {
           type = types.submoduleWith;
           payload = {
@@ -585,6 +609,8 @@ rec {
            then functor.type mt1 mt2
            else null;
       functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
+      nestedTypes.left = t1;
+      nestedTypes.right = t2;
     };
 
     # Any of the types in the given list
@@ -616,6 +642,8 @@ rec {
         substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
         typeMerge = t1: t2: null;
         functor = (defaultFunctor name) // { wrapped = finalType; };
+        nestedTypes.coercedType = coercedType;
+        nestedTypes.finalType = finalType;
       };
 
     # Obsolete alternative to configOf.  It takes its option