about summary refs log tree commit diff
path: root/nixpkgs/lib
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2023-12-01 19:00:09 +0100
committerAlyssa Ross <hi@alyssa.is>2023-12-01 19:00:09 +0100
commit9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d (patch)
tree4368f9e4cb2d5b93a956c085337e45cb70f1e331 /nixpkgs/lib
parenta9cbfb6941b47d6f50129e6e36927882392daed7 (diff)
parent2344fe1da14cb08b0c18743b207995f9b8597915 (diff)
downloadnixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar.gz
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar.bz2
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar.lz
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar.xz
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.tar.zst
nixlib-9e9b07490d5bab5d115c66b80bdb10ff0c11ed8d.zip
Merge https://github.com/NixOS/nixpkgs
Diffstat (limited to 'nixpkgs/lib')
-rw-r--r--nixpkgs/lib/attrsets.nix67
-rw-r--r--nixpkgs/lib/customisation.nix93
-rw-r--r--nixpkgs/lib/fileset/README.md16
-rw-r--r--nixpkgs/lib/fileset/default.nix484
-rw-r--r--nixpkgs/lib/fileset/internal.nix8
-rwxr-xr-xnixpkgs/lib/fileset/tests.sh72
-rw-r--r--nixpkgs/lib/filesystem.nix6
-rw-r--r--nixpkgs/lib/gvariant.nix19
-rw-r--r--nixpkgs/lib/licenses.nix7
-rw-r--r--nixpkgs/lib/lists.nix3
-rw-r--r--nixpkgs/lib/meta.nix32
-rw-r--r--nixpkgs/lib/options.nix2
-rw-r--r--nixpkgs/lib/path/default.nix3
-rw-r--r--nixpkgs/lib/sources.nix2
-rw-r--r--nixpkgs/lib/systems/default.nix184
-rw-r--r--nixpkgs/lib/tests/misc.nix40
-rw-r--r--nixpkgs/lib/trivial.nix2
17 files changed, 650 insertions, 390 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix
index b0460ab139e8..3d4366ce1814 100644
--- a/nixpkgs/lib/attrsets.nix
+++ b/nixpkgs/lib/attrsets.nix
@@ -1,5 +1,5 @@
+/* Operations on attribute sets. */
 { lib }:
-# Operations on attribute sets.
 
 let
   inherit (builtins) head tail length;
@@ -34,12 +34,20 @@ rec {
     default:
     # The nested attribute set to select values from
     set:
-    let attr = head attrPath;
+    let
+      lenAttrPath = length attrPath;
+      attrByPath' = n: s: (
+        if n == lenAttrPath then s
+        else (
+          let
+            attr = elemAt attrPath n;
+          in
+          if s ? ${attr} then attrByPath' (n + 1) s.${attr}
+          else default
+        )
+      );
     in
-      if attrPath == [] then set
-      else if set ? ${attr}
-      then attrByPath (tail attrPath) default set.${attr}
-      else default;
+      attrByPath' 0 set;
 
   /* Return if an attribute from nested attribute set exists.
 
@@ -58,13 +66,19 @@ rec {
     attrPath:
     # The nested attribute set to check
     e:
-    let attr = head attrPath;
+    let
+      lenAttrPath = length attrPath;
+      hasAttrByPath' = n: s: (
+        n == lenAttrPath || (
+          let
+            attr = elemAt attrPath n;
+          in
+          if s ? ${attr} then hasAttrByPath' (n + 1) s.${attr}
+          else false
+        )
+      );
     in
-      if attrPath == [] then true
-      else if e ? ${attr}
-      then hasAttrByPath (tail attrPath) e.${attr}
-      else false;
-
+      hasAttrByPath' 0 e;
 
   /* Create a new attribute set with `value` set at the nested attribute location specified in `attrPath`.
 
@@ -883,7 +897,10 @@ rec {
     recursiveUpdateUntil (path: lhs: rhs: !(isAttrs lhs && isAttrs rhs)) lhs rhs;
 
 
-  /* Returns true if the pattern is contained in the set. False otherwise.
+  /*
+    Recurse into every attribute set of the first argument and check that:
+    - Each attribute path also exists in the second argument.
+    - If the attribute's value is not a nested attribute set, it must have the same value in the right argument.
 
      Example:
        matchAttrs { cpu = {}; } { cpu = { bits = 64; }; }
@@ -895,16 +912,24 @@ rec {
   matchAttrs =
     # Attribute set structure to match
     pattern:
-    # Attribute set to find patterns in
+    # Attribute set to check
     attrs:
     assert isAttrs pattern;
-    all id (attrValues (zipAttrsWithNames (attrNames pattern) (n: values:
-      let pat = head values; val = elemAt values 1; in
-      if length values == 1 then false
-      else if isAttrs pat then isAttrs val && matchAttrs pat val
-      else pat == val
-    ) [pattern attrs]));
-
+    all
+    ( # Compare equality between `pattern` & `attrs`.
+      attr:
+      # Missing attr, not equal.
+      attrs ? ${attr} && (
+        let
+          lhs = pattern.${attr};
+          rhs = attrs.${attr};
+        in
+        # If attrset check recursively
+        if isAttrs lhs then isAttrs rhs && matchAttrs lhs rhs
+        else lhs == rhs
+      )
+    )
+    (attrNames pattern);
 
   /* Override only the attributes that are already present in the old set
     useful for deep-overriding.
diff --git a/nixpkgs/lib/customisation.nix b/nixpkgs/lib/customisation.nix
index 08fc5db0614d..5e290230ca4e 100644
--- a/nixpkgs/lib/customisation.nix
+++ b/nixpkgs/lib/customisation.nix
@@ -1,5 +1,16 @@
 { lib }:
 
+let
+  inherit (builtins)
+    intersectAttrs;
+  inherit (lib)
+    functionArgs isFunction mirrorFunctionArgs isAttrs setFunctionArgs levenshteinAtMost
+    optionalAttrs attrNames levenshtein filter elemAt concatStringsSep sort take length
+    filterAttrs optionalString flip pathIsDirectory head pipe isDerivation listToAttrs
+    mapAttrs seq flatten deepSeq warnIf isInOldestRelease extends
+    ;
+
+in
 rec {
 
 
@@ -43,15 +54,15 @@ rec {
   overrideDerivation = drv: f:
     let
       newDrv = derivation (drv.drvAttrs // (f drv));
-    in lib.flip (extendDerivation (builtins.seq drv.drvPath true)) newDrv (
+    in flip (extendDerivation (seq drv.drvPath true)) newDrv (
       { meta = drv.meta or {};
         passthru = if drv ? passthru then drv.passthru else {};
       }
       //
       (drv.passthru or {})
       //
-      lib.optionalAttrs (drv ? __spliced) {
-        __spliced = {} // (lib.mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
+      optionalAttrs (drv ? __spliced) {
+        __spliced = {} // (mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
       });
 
 
@@ -79,30 +90,30 @@ rec {
   makeOverridable = f:
     let
       # Creates a functor with the same arguments as f
-      mirrorArgs = lib.mirrorFunctionArgs f;
+      mirrorArgs = mirrorFunctionArgs f;
     in
     mirrorArgs (origArgs:
     let
       result = f origArgs;
 
       # Changes the original arguments with (potentially a function that returns) a set of new attributes
-      overrideWith = newArgs: origArgs // (if lib.isFunction newArgs then newArgs origArgs else newArgs);
+      overrideWith = newArgs: origArgs // (if isFunction newArgs then newArgs origArgs else newArgs);
 
       # Re-call the function but with different arguments
       overrideArgs = mirrorArgs (newArgs: makeOverridable f (overrideWith newArgs));
       # Change the result of the function call by applying g to it
       overrideResult = g: makeOverridable (mirrorArgs (args: g (f args))) origArgs;
     in
-      if builtins.isAttrs result then
+      if isAttrs result then
         result // {
           override = overrideArgs;
           overrideDerivation = fdrv: overrideResult (x: overrideDerivation x fdrv);
           ${if result ? overrideAttrs then "overrideAttrs" else null} = fdrv:
             overrideResult (x: x.overrideAttrs fdrv);
         }
-      else if lib.isFunction result then
+      else if isFunction result then
         # Transform the result into a functor while propagating its arguments
-        lib.setFunctionArgs result (lib.functionArgs result) // {
+        setFunctionArgs result (functionArgs result) // {
           override = overrideArgs;
         }
       else result);
@@ -140,39 +151,39 @@ rec {
   */
   callPackageWith = autoArgs: fn: args:
     let
-      f = if lib.isFunction fn then fn else import fn;
-      fargs = lib.functionArgs f;
+      f = if isFunction fn then fn else import fn;
+      fargs = functionArgs f;
 
       # All arguments that will be passed to the function
       # This includes automatic ones and ones passed explicitly
-      allArgs = builtins.intersectAttrs fargs autoArgs // args;
+      allArgs = intersectAttrs fargs autoArgs // args;
 
       # a list of argument names that the function requires, but
       # wouldn't be passed to it
-      missingArgs = lib.attrNames
+      missingArgs =
         # Filter out arguments that have a default value
-        (lib.filterAttrs (name: value: ! value)
+        (filterAttrs (name: value: ! value)
         # Filter out arguments that would be passed
-        (removeAttrs fargs (lib.attrNames allArgs)));
+        (removeAttrs fargs (attrNames allArgs)));
 
       # Get a list of suggested argument names for a given missing one
-      getSuggestions = arg: lib.pipe (autoArgs // args) [
-        lib.attrNames
+      getSuggestions = arg: pipe (autoArgs // args) [
+        attrNames
         # Only use ones that are at most 2 edits away. While mork would work,
         # levenshteinAtMost is only fast for 2 or less.
-        (lib.filter (lib.strings.levenshteinAtMost 2 arg))
+        (filter (levenshteinAtMost 2 arg))
         # Put strings with shorter distance first
-        (lib.sort (x: y: lib.strings.levenshtein x arg < lib.strings.levenshtein y arg))
+        (sort (x: y: levenshtein x arg < levenshtein y arg))
         # Only take the first couple results
-        (lib.take 3)
+        (take 3)
         # Quote all entries
         (map (x: "\"" + x + "\""))
       ];
 
       prettySuggestions = suggestions:
         if suggestions == [] then ""
-        else if lib.length suggestions == 1 then ", did you mean ${lib.elemAt suggestions 0}?"
-        else ", did you mean ${lib.concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";
+        else if length suggestions == 1 then ", did you mean ${elemAt suggestions 0}?"
+        else ", did you mean ${concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?";
 
       errorForArg = arg:
         let
@@ -180,16 +191,18 @@ rec {
           # loc' can be removed once lib/minver.nix is >2.3.4, since that includes
           # https://github.com/NixOS/nix/pull/3468 which makes loc be non-null
           loc' = if loc != null then loc.file + ":" + toString loc.line
-            else if ! lib.isFunction fn then
-              toString fn + lib.optionalString (lib.sources.pathIsDirectory fn) "/default.nix"
+            else if ! isFunction fn then
+              toString fn + optionalString (pathIsDirectory fn) "/default.nix"
             else "<unknown location>";
         in "Function called without required argument \"${arg}\" at "
         + "${loc'}${prettySuggestions (getSuggestions arg)}";
 
       # Only show the error for the first missing argument
-      error = errorForArg (lib.head missingArgs);
+      error = errorForArg missingArgs.${head (attrNames missingArgs)};
 
-    in if missingArgs == [] then makeOverridable f allArgs else abort error;
+    in if missingArgs == {}
+       then makeOverridable f allArgs
+       else throw "lib.customisation.callPackageWith: ${error}";
 
 
   /* Like callPackage, but for a function that returns an attribute
@@ -201,17 +214,17 @@ rec {
   */
   callPackagesWith = autoArgs: fn: args:
     let
-      f = if lib.isFunction fn then fn else import fn;
-      auto = builtins.intersectAttrs (lib.functionArgs f) autoArgs;
+      f = if isFunction fn then fn else import fn;
+      auto = intersectAttrs (functionArgs f) autoArgs;
       origArgs = auto // args;
       pkgs = f origArgs;
       mkAttrOverridable = name: _: makeOverridable (newArgs: (f newArgs).${name}) origArgs;
     in
-      if lib.isDerivation pkgs then throw
+      if isDerivation pkgs then throw
         ("function `callPackages` was called on a *single* derivation "
           + ''"${pkgs.name or "<unknown-name>"}";''
           + " did you mean to use `callPackage` instead?")
-      else lib.mapAttrs mkAttrOverridable pkgs;
+      else mapAttrs mkAttrOverridable pkgs;
 
 
   /* Add attributes to each output of a derivation without changing
@@ -224,7 +237,7 @@ rec {
     let
       outputs = drv.outputs or [ "out" ];
 
-      commonAttrs = drv // (builtins.listToAttrs outputsList) //
+      commonAttrs = drv // (listToAttrs outputsList) //
         ({ all = map (x: x.value) outputsList; }) // passthru;
 
       outputToAttrListElement = outputName:
@@ -238,7 +251,7 @@ rec {
             # TODO: give the derivation control over the outputs.
             #       `overrideAttrs` may not be the only attribute that needs
             #       updating when switching outputs.
-            lib.optionalAttrs (passthru?overrideAttrs) {
+            optionalAttrs (passthru?overrideAttrs) {
               # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing.
               overrideAttrs = f: (passthru.overrideAttrs f).${outputName};
             };
@@ -264,11 +277,11 @@ rec {
 
       commonAttrs =
         { inherit (drv) name system meta; inherit outputs; }
-        // lib.optionalAttrs (drv._hydraAggregate or false) {
+        // optionalAttrs (drv._hydraAggregate or false) {
           _hydraAggregate = true;
-          constituents = map hydraJob (lib.flatten drv.constituents);
+          constituents = map hydraJob (flatten drv.constituents);
         }
-        // (lib.listToAttrs outputsList);
+        // (listToAttrs outputsList);
 
       makeOutput = outputName:
         let output = drv.${outputName}; in
@@ -283,9 +296,9 @@ rec {
 
       outputsList = map makeOutput outputs;
 
-      drv' = (lib.head outputsList).value;
+      drv' = (head outputsList).value;
     in if drv == null then null else
-      lib.deepSeq drv' drv';
+      deepSeq drv' drv';
 
   /* Make a set of packages with a common scope. All packages called
      with the provided `callPackage` will be evaluated with the same
@@ -304,11 +317,11 @@ rec {
     let self = f self // {
           newScope = scope: newScope (self // scope);
           callPackage = self.newScope {};
-          overrideScope = g: makeScope newScope (lib.fixedPoints.extends g f);
+          overrideScope = g: makeScope newScope (extends g f);
           # Remove after 24.11 is released.
-          overrideScope' = g: lib.warnIf (lib.isInOldestRelease 2311)
+          overrideScope' = g: warnIf (isInOldestRelease 2311)
             "`overrideScope'` (from `lib.makeScope`) has been renamed to `overrideScope`."
-            (makeScope newScope (lib.fixedPoints.extends g f));
+            (makeScope newScope (extends g f));
           packages = f;
         };
     in self;
@@ -384,7 +397,7 @@ rec {
         overrideScope = g: (makeScopeWithSplicing'
           { inherit splicePackages newScope; }
           { inherit otherSplices keep extra;
-            f = lib.fixedPoints.extends g f;
+            f = extends g f;
           });
         packages = f;
       };
diff --git a/nixpkgs/lib/fileset/README.md b/nixpkgs/lib/fileset/README.md
index 14b6877a9065..93e0199c32a4 100644
--- a/nixpkgs/lib/fileset/README.md
+++ b/nixpkgs/lib/fileset/README.md
@@ -253,7 +253,15 @@ The `fileFilter` function takes a path, and not a file set, as its second argume
       it would change the `subpath`/`components` value depending on which files are included.
 - (+) If necessary, this restriction can be relaxed later, the opposite wouldn't be possible
 
-## To update in the future
-
-Here's a list of places in the library that need to be updated in the future:
-- If/Once a function exists that can optionally include a path depending on whether it exists, the error message for the path not existing in `_coerce` should mention the new function
+### Strict path existence checking
+
+Coercing paths that don't exist to file sets always gives an error.
+
+- (-) Sometimes you want to remove a file that may not always exist using `difference ./. ./does-not-exist`,
+  but this does not work because coercion of `./does-not-exist` fails,
+  even though its existence would have no influence on the result.
+  - (+) This is dangerous, because you wouldn't be protected against typos anymore.
+    E.g. when trying to prevent `./secret` from being imported, a typo like `difference ./. ./sercet` would import it regardless.
+  - (+) `difference ./. (maybeMissing ./does-not-exist)` can be used to do this more explicitly.
+  - (+) `difference ./. (difference ./foo ./foo/bar)` should report an error when `./foo/bar` does not exist ("double negation"). Unfortunately, the current internal representation does not lend itself to a behavior where both `difference x ./does-not-exists` and double negation are handled and checked correctly.
+    This could be fixed, but would require significant changes to the internal representation that are not worth the effort and the risk of introducing implicit behavior.
diff --git a/nixpkgs/lib/fileset/default.nix b/nixpkgs/lib/fileset/default.nix
index ca2ab9a35740..75e609a072e7 100644
--- a/nixpkgs/lib/fileset/default.nix
+++ b/nixpkgs/lib/fileset/default.nix
@@ -3,10 +3,56 @@
   []{#sec-fileset}
 
   The [`lib.fileset`](#sec-functions-library-fileset) library allows you to work with _file sets_.
-  A file set is a mathematical set of local files that can be added to the Nix store for use in Nix derivations.
+  A file set is a (mathematical) set of local files that can be added to the Nix store for use in Nix derivations.
   File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.
 
-  See the [function reference](#sec-functions-library-fileset) for function-specific documentation.
+  ## Overview {#sec-fileset-overview}
+
+  Basics:
+  - [Implicit coercion from paths to file sets](#sec-fileset-path-coercion)
+
+  - [`lib.fileset.maybeMissing`](#function-library-lib.fileset.maybeMissing):
+
+    Create a file set from a path that may be missing.
+
+  - [`lib.fileset.trace`](#function-library-lib.fileset.trace)/[`lib.fileset.traceVal`](#function-library-lib.fileset.trace):
+
+    Pretty-print file sets for debugging.
+
+  - [`lib.fileset.toSource`](#function-library-lib.fileset.toSource):
+
+    Add files in file sets to the store to use as derivation sources.
+
+  Combinators:
+  - [`lib.fileset.union`](#function-library-lib.fileset.union)/[`lib.fileset.unions`](#function-library-lib.fileset.unions):
+
+    Create a larger file set from all the files in multiple file sets.
+
+  - [`lib.fileset.intersection`](#function-library-lib.fileset.intersection):
+
+    Create a smaller file set from only the files in both file sets.
+
+  - [`lib.fileset.difference`](#function-library-lib.fileset.difference):
+
+    Create a smaller file set containing all files that are in one file set, but not another one.
+
+  Filtering:
+  - [`lib.fileset.fileFilter`](#function-library-lib.fileset.fileFilter):
+
+    Create a file set from all files that satisisfy a predicate in a directory.
+
+  Utilities:
+  - [`lib.fileset.fromSource`](#function-library-lib.fileset.fromSource):
+
+    Create a file set from a `lib.sources`-based value.
+
+  - [`lib.fileset.gitTracked`](#function-library-lib.fileset.gitTracked)/[`lib.fileset.gitTrackedWith`](#function-library-lib.fileset.gitTrackedWith):
+
+    Create a file set from all tracked files in a local Git repository.
+
+  If you need more file set functions,
+  see [this issue](https://github.com/NixOS/nixpkgs/issues/266356) to request it.
+
 
   ## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
 
@@ -63,6 +109,7 @@ let
     _difference
     _mirrorStorePath
     _fetchGitSubmodulesMinver
+    _emptyWithoutBase
     ;
 
   inherit (builtins)
@@ -107,6 +154,122 @@ let
 in {
 
   /*
+    Create a file set from a path that may or may not exist:
+    - If the path does exist, the path is [coerced to a file set](#sec-fileset-path-coercion).
+    - If the path does not exist, a file set containing no files is returned.
+
+    Type:
+      maybeMissing :: Path -> FileSet
+
+    Example:
+      # All files in the current directory, but excluding main.o if it exists
+      difference ./. (maybeMissing ./main.o)
+  */
+  maybeMissing =
+    path:
+    if ! isPath path then
+      if isStringLike path then
+        throw ''
+          lib.fileset.maybeMissing: Argument ("${toString path}") is a string-like value, but it should be a path instead.''
+      else
+        throw ''
+          lib.fileset.maybeMissing: Argument is of type ${typeOf path}, but it should be a path instead.''
+    else if ! pathExists path then
+      _emptyWithoutBase
+    else
+      _singleton path;
+
+  /*
+    Incrementally evaluate and trace a file set in a pretty way.
+    This function is only intended for debugging purposes.
+    The exact tracing format is unspecified and may change.
+
+    This function takes a final argument to return.
+    In comparison, [`traceVal`](#function-library-lib.fileset.traceVal) returns
+    the given file set argument.
+
+    This variant is useful for tracing file sets in the Nix repl.
+
+    Type:
+      trace :: FileSet -> Any -> Any
+
+    Example:
+      trace (unions [ ./Makefile ./src ./tests/run.sh ]) null
+      =>
+      trace: /home/user/src/myProject
+      trace: - Makefile (regular)
+      trace: - src (all files in directory)
+      trace: - tests
+      trace:   - run.sh (regular)
+      null
+  */
+  trace =
+    /*
+    The file set to trace.
+
+    This argument can also be a path,
+    which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
+    */
+    fileset:
+    let
+      # "fileset" would be a better name, but that would clash with the argument name,
+      # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
+      actualFileset = _coerce "lib.fileset.trace: Argument" fileset;
+    in
+    seq
+      (_printFileset actualFileset)
+      (x: x);
+
+  /*
+    Incrementally evaluate and trace a file set in a pretty way.
+    This function is only intended for debugging purposes.
+    The exact tracing format is unspecified and may change.
+
+    This function returns the given file set.
+    In comparison, [`trace`](#function-library-lib.fileset.trace) takes another argument to return.
+
+    This variant is useful for tracing file sets passed as arguments to other functions.
+
+    Type:
+      traceVal :: FileSet -> FileSet
+
+    Example:
+      toSource {
+        root = ./.;
+        fileset = traceVal (unions [
+          ./Makefile
+          ./src
+          ./tests/run.sh
+        ]);
+      }
+      =>
+      trace: /home/user/src/myProject
+      trace: - Makefile (regular)
+      trace: - src (all files in directory)
+      trace: - tests
+      trace:   - run.sh (regular)
+      "/nix/store/...-source"
+  */
+  traceVal =
+    /*
+    The file set to trace and return.
+
+    This argument can also be a path,
+    which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
+    */
+    fileset:
+    let
+      # "fileset" would be a better name, but that would clash with the argument name,
+      # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
+      actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset;
+    in
+    seq
+      (_printFileset actualFileset)
+      # We could also return the original fileset argument here,
+      # but that would then duplicate work for consumers of the fileset, because then they have to coerce it again
+      actualFileset;
+
+  /*
     Add the local files contained in `fileset` to the store as a single [store path](https://nixos.org/manual/nix/stable/glossary#gloss-store-path) rooted at `root`.
 
     The result is the store path as a string-like value, making it usable e.g. as the `src` of a derivation, or in string interpolation:
@@ -251,75 +414,6 @@ in {
       };
 
   /*
-  Create a file set with the same files as a `lib.sources`-based value.
-  This does not import any of the files into the store.
-
-  This can be used to gradually migrate from `lib.sources`-based filtering to `lib.fileset`.
-
-  A file set can be turned back into a source using [`toSource`](#function-library-lib.fileset.toSource).
-
-  :::{.note}
-  File sets cannot represent empty directories.
-  Turning the result of this function back into a source using `toSource` will therefore not preserve empty directories.
-  :::
-
-  Type:
-    fromSource :: SourceLike -> FileSet
-
-  Example:
-    # There's no cleanSource-like function for file sets yet,
-    # but we can just convert cleanSource to a file set and use it that way
-    toSource {
-      root = ./.;
-      fileset = fromSource (lib.sources.cleanSource ./.);
-    }
-
-    # Keeping a previous sourceByRegex (which could be migrated to `lib.fileset.unions`),
-    # but removing a subdirectory using file set functions
-    difference
-      (fromSource (lib.sources.sourceByRegex ./. [
-        "^README\.md$"
-        # This regex includes everything in ./doc
-        "^doc(/.*)?$"
-      ])
-      ./doc/generated
-
-    # Use cleanSource, but limit it to only include ./Makefile and files under ./src
-    intersection
-      (fromSource (lib.sources.cleanSource ./.))
-      (unions [
-        ./Makefile
-        ./src
-      ]);
-  */
-  fromSource = source:
-    let
-      # This function uses `._isLibCleanSourceWith`, `.origSrc` and `.filter`,
-      # which are technically internal to lib.sources,
-      # but we'll allow this since both libraries are in the same code base
-      # and this function is a bridge between them.
-      isFiltered = source ? _isLibCleanSourceWith;
-      path = if isFiltered then source.origSrc else source;
-    in
-    # We can only support sources created from paths
-    if ! isPath path then
-      if isStringLike path then
-        throw ''
-          lib.fileset.fromSource: The source origin of the argument is a string-like value ("${toString path}"), but it should be a path instead.
-              Sources created from paths in strings cannot be turned into file sets, use `lib.sources` or derivations instead.''
-      else
-        throw ''
-          lib.fileset.fromSource: The source origin of the argument is of type ${typeOf path}, but it should be a path instead.''
-    else if ! pathExists path then
-      throw ''
-        lib.fileset.fromSource: The source origin (${toString path}) of the argument is a path that does not exist.''
-    else if isFiltered then
-      _fromSourceFilter path source.filter
-    else
-      # If there's no filter, no need to run the expensive conversion, all subpaths will be included
-      _singleton path;
-
-  /*
     The file set containing all files that are in either of two given file sets.
     This is the same as [`unions`](#function-library-lib.fileset.unions),
     but takes just two file sets instead of a list.
@@ -412,66 +506,6 @@ in {
       ];
 
   /*
-    Filter a file set to only contain files matching some predicate.
-
-    Type:
-      fileFilter ::
-        ({
-          name :: String,
-          type :: String,
-          ...
-        } -> Bool)
-        -> Path
-        -> FileSet
-
-    Example:
-      # Include all regular `default.nix` files in the current directory
-      fileFilter (file: file.name == "default.nix") ./.
-
-      # Include all non-Nix files from the current directory
-      fileFilter (file: ! hasSuffix ".nix" file.name) ./.
-
-      # Include all files that start with a "." in the current directory
-      fileFilter (file: hasPrefix "." file.name) ./.
-
-      # Include all regular files (not symlinks or others) in the current directory
-      fileFilter (file: file.type == "regular") ./.
-  */
-  fileFilter =
-    /*
-      The predicate function to call on all files contained in given file set.
-      A file is included in the resulting file set if this function returns true for it.
-
-      This function is called with an attribute set containing these attributes:
-
-      - `name` (String): The name of the file
-
-      - `type` (String, one of `"regular"`, `"symlink"` or `"unknown"`): The type of the file.
-        This matches result of calling [`builtins.readFileType`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readFileType) on the file's path.
-
-      Other attributes may be added in the future.
-    */
-    predicate:
-    # The path whose files to filter
-    path:
-    if ! isFunction predicate then
-      throw ''
-        lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function instead.''
-    else if ! isPath path then
-      if path._type or "" == "fileset" then
-        throw ''
-          lib.fileset.fileFilter: Second argument is a file set, but it should be a path instead.
-              If you need to filter files in a file set, use `intersection fileset (fileFilter pred ./.)` instead.''
-      else
-        throw ''
-          lib.fileset.fileFilter: Second argument is of type ${typeOf path}, but it should be a path instead.''
-    else if ! pathExists path then
-      throw ''
-        lib.fileset.fileFilter: Second argument (${toString path}) is a path that does not exist.''
-    else
-      _fileFilter predicate path;
-
-  /*
     The file set containing all files that are in both of two given file sets.
     See also [Intersection (set theory)](https://en.wikipedia.org/wiki/Intersection_(set_theory)).
 
@@ -563,94 +597,140 @@ in {
       (elemAt filesets 1);
 
   /*
-    Incrementally evaluate and trace a file set in a pretty way.
-    This function is only intended for debugging purposes.
-    The exact tracing format is unspecified and may change.
-
-    This function takes a final argument to return.
-    In comparison, [`traceVal`](#function-library-lib.fileset.traceVal) returns
-    the given file set argument.
-
-    This variant is useful for tracing file sets in the Nix repl.
+    Filter a file set to only contain files matching some predicate.
 
     Type:
-      trace :: FileSet -> Any -> Any
+      fileFilter ::
+        ({
+          name :: String,
+          type :: String,
+          hasExt :: String -> Bool,
+          ...
+        } -> Bool)
+        -> Path
+        -> FileSet
 
     Example:
-      trace (unions [ ./Makefile ./src ./tests/run.sh ]) null
-      =>
-      trace: /home/user/src/myProject
-      trace: - Makefile (regular)
-      trace: - src (all files in directory)
-      trace: - tests
-      trace:   - run.sh (regular)
-      null
+      # Include all regular `default.nix` files in the current directory
+      fileFilter (file: file.name == "default.nix") ./.
+
+      # Include all non-Nix files from the current directory
+      fileFilter (file: ! file.hasExt "nix") ./.
+
+      # Include all files that start with a "." in the current directory
+      fileFilter (file: hasPrefix "." file.name) ./.
+
+      # Include all regular files (not symlinks or others) in the current directory
+      fileFilter (file: file.type == "regular") ./.
   */
-  trace =
+  fileFilter =
     /*
-    The file set to trace.
+      The predicate function to call on all files contained in given file set.
+      A file is included in the resulting file set if this function returns true for it.
 
-    This argument can also be a path,
-    which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
+      This function is called with an attribute set containing these attributes:
+
+      - `name` (String): The name of the file
+
+      - `type` (String, one of `"regular"`, `"symlink"` or `"unknown"`): The type of the file.
+        This matches result of calling [`builtins.readFileType`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readFileType) on the file's path.
+
+      - `hasExt` (String -> Bool): Whether the file has a certain file extension.
+        `hasExt ext` is true only if `hasSuffix ".${ext}" name`.
+
+        This also means that e.g. for a file with name `.gitignore`,
+        `hasExt "gitignore"` is true.
+
+      Other attributes may be added in the future.
     */
-    fileset:
-    let
-      # "fileset" would be a better name, but that would clash with the argument name,
-      # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
-      actualFileset = _coerce "lib.fileset.trace: Argument" fileset;
-    in
-    seq
-      (_printFileset actualFileset)
-      (x: x);
+    predicate:
+    # The path whose files to filter
+    path:
+    if ! isFunction predicate then
+      throw ''
+        lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function instead.''
+    else if ! isPath path then
+      if path._type or "" == "fileset" then
+        throw ''
+          lib.fileset.fileFilter: Second argument is a file set, but it should be a path instead.
+              If you need to filter files in a file set, use `intersection fileset (fileFilter pred ./.)` instead.''
+      else
+        throw ''
+          lib.fileset.fileFilter: Second argument is of type ${typeOf path}, but it should be a path instead.''
+    else if ! pathExists path then
+      throw ''
+        lib.fileset.fileFilter: Second argument (${toString path}) is a path that does not exist.''
+    else
+      _fileFilter predicate path;
 
   /*
-    Incrementally evaluate and trace a file set in a pretty way.
-    This function is only intended for debugging purposes.
-    The exact tracing format is unspecified and may change.
+  Create a file set with the same files as a `lib.sources`-based value.
+  This does not import any of the files into the store.
 
-    This function returns the given file set.
-    In comparison, [`trace`](#function-library-lib.fileset.trace) takes another argument to return.
+  This can be used to gradually migrate from `lib.sources`-based filtering to `lib.fileset`.
 
-    This variant is useful for tracing file sets passed as arguments to other functions.
+  A file set can be turned back into a source using [`toSource`](#function-library-lib.fileset.toSource).
 
-    Type:
-      traceVal :: FileSet -> FileSet
+  :::{.note}
+  File sets cannot represent empty directories.
+  Turning the result of this function back into a source using `toSource` will therefore not preserve empty directories.
+  :::
 
-    Example:
-      toSource {
-        root = ./.;
-        fileset = traceVal (unions [
-          ./Makefile
-          ./src
-          ./tests/run.sh
-        ]);
-      }
-      =>
-      trace: /home/user/src/myProject
-      trace: - Makefile (regular)
-      trace: - src (all files in directory)
-      trace: - tests
-      trace:   - run.sh (regular)
-      "/nix/store/...-source"
-  */
-  traceVal =
-    /*
-    The file set to trace and return.
+  Type:
+    fromSource :: SourceLike -> FileSet
 
-    This argument can also be a path,
-    which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
-    */
-    fileset:
+  Example:
+    # There's no cleanSource-like function for file sets yet,
+    # but we can just convert cleanSource to a file set and use it that way
+    toSource {
+      root = ./.;
+      fileset = fromSource (lib.sources.cleanSource ./.);
+    }
+
+    # Keeping a previous sourceByRegex (which could be migrated to `lib.fileset.unions`),
+    # but removing a subdirectory using file set functions
+    difference
+      (fromSource (lib.sources.sourceByRegex ./. [
+        "^README\.md$"
+        # This regex includes everything in ./doc
+        "^doc(/.*)?$"
+      ])
+      ./doc/generated
+
+    # Use cleanSource, but limit it to only include ./Makefile and files under ./src
+    intersection
+      (fromSource (lib.sources.cleanSource ./.))
+      (unions [
+        ./Makefile
+        ./src
+      ]);
+  */
+  fromSource = source:
     let
-      # "fileset" would be a better name, but that would clash with the argument name,
-      # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
-      actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset;
+      # This function uses `._isLibCleanSourceWith`, `.origSrc` and `.filter`,
+      # which are technically internal to lib.sources,
+      # but we'll allow this since both libraries are in the same code base
+      # and this function is a bridge between them.
+      isFiltered = source ? _isLibCleanSourceWith;
+      path = if isFiltered then source.origSrc else source;
     in
-    seq
-      (_printFileset actualFileset)
-      # We could also return the original fileset argument here,
-      # but that would then duplicate work for consumers of the fileset, because then they have to coerce it again
-      actualFileset;
+    # We can only support sources created from paths
+    if ! isPath path then
+      if isStringLike path then
+        throw ''
+          lib.fileset.fromSource: The source origin of the argument is a string-like value ("${toString path}"), but it should be a path instead.
+              Sources created from paths in strings cannot be turned into file sets, use `lib.sources` or derivations instead.''
+      else
+        throw ''
+          lib.fileset.fromSource: The source origin of the argument is of type ${typeOf path}, but it should be a path instead.''
+    else if ! pathExists path then
+      throw ''
+        lib.fileset.fromSource: The source origin (${toString path}) of the argument is a path that does not exist.''
+    else if isFiltered then
+      _fromSourceFilter path source.filter
+    else
+      # If there's no filter, no need to run the expensive conversion, all subpaths will be included
+      _singleton path;
 
   /*
     Create a file set containing all [Git-tracked files](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) in a repository.
diff --git a/nixpkgs/lib/fileset/internal.nix b/nixpkgs/lib/fileset/internal.nix
index 23d7b847204c..35d556e78391 100644
--- a/nixpkgs/lib/fileset/internal.nix
+++ b/nixpkgs/lib/fileset/internal.nix
@@ -52,6 +52,7 @@ let
     concatStringsSep
     substring
     stringLength
+    hasSuffix
     ;
 
 in
@@ -181,7 +182,8 @@ rec {
           ${context} is of type ${typeOf value}, but it should be a file set or a path instead.''
     else if ! pathExists value then
       throw ''
-        ${context} (${toString value}) is a path that does not exist.''
+        ${context} (${toString value}) is a path that does not exist.
+            To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`.''
     else
       _singleton value;
 
@@ -796,9 +798,11 @@ rec {
         if
           predicate {
             inherit name type;
+            hasExt = ext: hasSuffix ".${ext}" name;
+
             # To ensure forwards compatibility with more arguments being added in the future,
             # adding an attribute which can't be deconstructed :)
-            "lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you're using `{ name, file }:`, use `{ name, file, ... }:` instead." = null;
+            "lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you're using `{ name, file, hasExt }:`, use `{ name, file, hasExt, ... }:` instead." = null;
           }
         then
           type
diff --git a/nixpkgs/lib/fileset/tests.sh b/nixpkgs/lib/fileset/tests.sh
index 06f92f297d88..077aefe371c3 100755
--- a/nixpkgs/lib/fileset/tests.sh
+++ b/nixpkgs/lib/fileset/tests.sh
@@ -413,7 +413,8 @@ expectFailure 'toSource { root = ./.; fileset = cleanSourceWith { src = ./.; };
 \s*Note that this only works for sources created from paths.'
 
 # Path coercion errors for non-existent paths
-expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) is a path that does not exist.'
+expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) is a path that does not exist.
+\s*To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`.'
 
 # File sets cannot be evaluated directly
 expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported.
@@ -846,7 +847,7 @@ checkFileset 'fileFilter (file: abort "this is not needed") ./.'
 
 # The predicate must be able to handle extra attributes
 touch a
-expectFailure 'toSource { root = ./.; fileset = fileFilter ({ name, type }: true) ./.; }' 'called with unexpected argument '\''"lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you'\''re using `\{ name, file \}:`, use `\{ name, file, ... \}:` instead."'\'
+expectFailure 'toSource { root = ./.; fileset = fileFilter ({ name, type, hasExt }: true) ./.; }' 'called with unexpected argument '\''"lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you'\''re using `\{ name, file, hasExt \}:`, use `\{ name, file, hasExt, ... \}:` instead."'\'
 rm -rf -- *
 
 # .name is the name, and it works correctly, even recursively
@@ -894,6 +895,39 @@ expectEqual \
     'toSource { root = ./.; fileset = union ./d/a ./d/b; }'
 rm -rf -- *
 
+# Check that .hasExt checks for the file extension
+# The empty extension is the same as a file ending with a .
+tree=(
+    [a]=0
+    [a.]=1
+    [a.b]=0
+    [a.b.]=1
+    [a.b.c]=0
+)
+checkFileset 'fileFilter (file: file.hasExt "") ./.'
+
+# It can check for the last extension
+tree=(
+    [a]=0
+    [.a]=1
+    [.a.]=0
+    [.b.a]=1
+    [.b.a.]=0
+)
+checkFileset 'fileFilter (file: file.hasExt "a") ./.'
+
+# It can check for any extension
+tree=(
+    [a.b.c.d]=1
+)
+checkFileset 'fileFilter (file:
+  all file.hasExt [
+    "b.c.d"
+    "c.d"
+    "d"
+  ]
+) ./.'
+
 # It's lazy
 tree=(
     [b]=1
@@ -1450,6 +1484,40 @@ checkGitTracked
 
 rm -rf -- *
 
+## lib.fileset.maybeMissing
+
+# Argument must be a path
+expectFailure 'maybeMissing "someString"' 'lib.fileset.maybeMissing: Argument \("someString"\) is a string-like value, but it should be a path instead.'
+expectFailure 'maybeMissing null' 'lib.fileset.maybeMissing: Argument is of type null, but it should be a path instead.'
+
+tree=(
+)
+checkFileset 'maybeMissing ./a'
+checkFileset 'maybeMissing ./b'
+checkFileset 'maybeMissing ./b/c'
+
+# Works on single files
+tree=(
+    [a]=1
+    [b/c]=0
+    [b/d]=0
+)
+checkFileset 'maybeMissing ./a'
+tree=(
+    [a]=0
+    [b/c]=1
+    [b/d]=0
+)
+checkFileset 'maybeMissing ./b/c'
+
+# Works on directories
+tree=(
+    [a]=0
+    [b/c]=1
+    [b/d]=1
+)
+checkFileset 'maybeMissing ./b'
+
 # TODO: Once we have combinators and a property testing library, derive property tests from https://en.wikipedia.org/wiki/Algebra_of_sets
 
 echo >&2 tests ok
diff --git a/nixpkgs/lib/filesystem.nix b/nixpkgs/lib/filesystem.nix
index f5bb8e9b5950..5569b8ac80fd 100644
--- a/nixpkgs/lib/filesystem.nix
+++ b/nixpkgs/lib/filesystem.nix
@@ -1,5 +1,7 @@
-# Functions for querying information about the filesystem
-# without copying any files to the Nix store.
+/*
+  Functions for querying information about the filesystem
+  without copying any files to the Nix store.
+*/
 { lib }:
 
 # Tested in lib/tests/filesystem.sh
diff --git a/nixpkgs/lib/gvariant.nix b/nixpkgs/lib/gvariant.nix
index 3142ffc5f149..708213224d3e 100644
--- a/nixpkgs/lib/gvariant.nix
+++ b/nixpkgs/lib/gvariant.nix
@@ -1,16 +1,17 @@
+/*
+  A partial and basic implementation of GVariant formatted strings.
+  See [GVariant Format Strings](https://docs.gtk.org/glib/gvariant-format-strings.html) for details.
+
+  :::{.warning}
+  This API is not considered fully stable and it might therefore
+  change in backwards incompatible ways without prior notice.
+  :::
+*/
+
 # This file is based on https://github.com/nix-community/home-manager
 # Copyright (c) 2017-2022 Home Manager contributors
-#
-
-
 { lib }:
 
-/* A partial and basic implementation of GVariant formatted strings.
-   See https://docs.gtk.org/glib/gvariant-format-strings.html for detauls.
-
-   Note, this API is not considered fully stable and it might therefore
-   change in backwards incompatible ways without prior notice.
-*/
 let
   inherit (lib)
     concatMapStringsSep concatStrings escape head replaceStrings;
diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix
index ad6922498ab4..3bce847b03c4 100644
--- a/nixpkgs/lib/licenses.nix
+++ b/nixpkgs/lib/licenses.nix
@@ -38,6 +38,13 @@ in mkLicense lset) ({
     redistributable = false;
   };
 
+  activision = {
+    # https://doomwiki.org/wiki/Raven_source_code_licensing
+    fullName = "Activision EULA";
+    url = "https://www.doomworld.com/eternity/activision_eula.txt";
+    free = false;
+  };
+
   afl20 = {
     spdxId = "AFL-2.0";
     fullName = "Academic Free License v2.0";
diff --git a/nixpkgs/lib/lists.nix b/nixpkgs/lib/lists.nix
index 15047f488f4d..a56667ec9c38 100644
--- a/nixpkgs/lib/lists.nix
+++ b/nixpkgs/lib/lists.nix
@@ -1,5 +1,4 @@
-# General list operations.
-
+/* General list operations. */
 { lib }:
 let
   inherit (lib.strings) toInt;
diff --git a/nixpkgs/lib/meta.nix b/nixpkgs/lib/meta.nix
index 2e817c42327d..1a43016d27c4 100644
--- a/nixpkgs/lib/meta.nix
+++ b/nixpkgs/lib/meta.nix
@@ -3,6 +3,11 @@
 
 { lib }:
 
+let
+  inherit (lib) matchAttrs any all;
+  inherit (builtins) isString;
+
+in
 rec {
 
 
@@ -83,14 +88,21 @@ rec {
      We can inject these into a pattern for the whole of a structured platform,
      and then match that.
   */
-  platformMatch = platform: elem: let
-      pattern =
-        if builtins.isString elem
-        then { system = elem; }
-        else if elem?parsed
-        then elem
-        else { parsed = elem; };
-    in lib.matchAttrs pattern platform;
+  platformMatch = platform: elem: (
+    # Check with simple string comparison if elem was a string.
+    #
+    # The majority of comparisons done with this function will be against meta.platforms
+    # which contains a simple platform string.
+    #
+    # Avoiding an attrset allocation results in significant  performance gains (~2-30) across the board in OfBorg
+    # because this is a hot path for nixpkgs.
+    if isString elem then platform ? system && elem == platform.system
+    else matchAttrs (
+      # Normalize platform attrset.
+      if elem ? parsed then elem
+      else { parsed = elem; }
+    ) platform
+  );
 
   /* Check if a package is available on a given platform.
 
@@ -102,8 +114,8 @@ rec {
        2. None of `meta.badPlatforms` pattern matches the given platform.
   */
   availableOn = platform: pkg:
-    ((!pkg?meta.platforms) || lib.any (platformMatch platform) pkg.meta.platforms) &&
-    lib.all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []);
+    ((!pkg?meta.platforms) || any (platformMatch platform) pkg.meta.platforms) &&
+    all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []);
 
   /* Get the corresponding attribute in lib.licenses
      from the SPDX ID.
diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix
index 7821924873dc..9c10dfc8b36a 100644
--- a/nixpkgs/lib/options.nix
+++ b/nixpkgs/lib/options.nix
@@ -1,4 +1,4 @@
-# Nixpkgs/NixOS option handling.
+/* Nixpkgs/NixOS option handling. */
 { lib }:
 
 let
diff --git a/nixpkgs/lib/path/default.nix b/nixpkgs/lib/path/default.nix
index 1a55a2a7be8d..ab8a9e2202e9 100644
--- a/nixpkgs/lib/path/default.nix
+++ b/nixpkgs/lib/path/default.nix
@@ -1,4 +1,5 @@
-# Functions for working with paths, see ./path.md
+/* Functions for working with path values. */
+# See ./README.md for internal docs
 { lib }:
 let
 
diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix
index 8b7cd5c84f62..f61ea306aec5 100644
--- a/nixpkgs/lib/sources.nix
+++ b/nixpkgs/lib/sources.nix
@@ -1,4 +1,4 @@
-# Functions for copying sources to the Nix store.
+/* Functions for copying sources to the Nix store. */
 { lib }:
 
 # Tested in lib/tests/sources.sh
diff --git a/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix
index ada8c66e3618..0d21175914a3 100644
--- a/nixpkgs/lib/systems/default.nix
+++ b/nixpkgs/lib/systems/default.nix
@@ -45,7 +45,7 @@ rec {
            else args';
 
     # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL.
-    rust = assert !(args ? rust && args ? rustc); args.rust or args.rustc or {};
+    rust = args.rust or args.rustc or {};
 
     final = {
       # Prefer to parse `config` as it is strictly more informative.
@@ -169,96 +169,6 @@ rec {
       # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs.
       rustc = args.rustc or {};
 
-      rust = rust // {
-        # Once args.rustc.platform.target-family is deprecated and
-        # removed, there will no longer be any need to modify any
-        # values from args.rust.platform, so we can drop all the
-        # "args ? rust" etc. checks, and merge args.rust.platform in
-        # /after/.
-        platform = rust.platform or {} // {
-          # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
-          arch =
-            /**/ if rust ? platform then rust.platform.arch
-            else if final.isAarch32 then "arm"
-            else if final.isMips64  then "mips64"     # never add "el" suffix
-            else if final.isPower64 then "powerpc64"  # never add "le" suffix
-            else final.parsed.cpu.name;
-
-          # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
-          os =
-            /**/ if rust ? platform then rust.platform.os or "none"
-            else if final.isDarwin then "macos"
-            else final.parsed.kernel.name;
-
-          # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
-          target-family =
-            /**/ if args ? rust.platform.target-family then args.rust.platform.target-family
-            else if args ? rustc.platform.target-family
-            then
-              (
-                # Since https://github.com/rust-lang/rust/pull/84072
-                # `target-family` is a list instead of single value.
-                let
-                  f = args.rustc.platform.target-family;
-                in
-                  if builtins.isList f then f else [ f ]
-              )
-            else lib.optional final.isUnix "unix"
-                 ++ lib.optional final.isWindows "windows";
-
-          # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
-          vendor = let
-            inherit (final.parsed) vendor;
-          in rust.platform.vendor or {
-            "w64" = "pc";
-          }.${vendor.name} or vendor.name;
-        };
-
-        # The name of the rust target, even if it is custom. Adjustments are
-        # because rust has slightly different naming conventions than we do.
-        rustcTarget = let
-          inherit (final.parsed) cpu kernel abi;
-          cpu_ = rust.platform.arch or {
-            "armv7a" = "armv7";
-            "armv7l" = "armv7";
-            "armv6l" = "arm";
-            "armv5tel" = "armv5te";
-            "riscv64" = "riscv64gc";
-          }.${cpu.name} or cpu.name;
-          vendor_ = final.rust.platform.vendor;
-        in rust.config
-          or "${cpu_}-${vendor_}-${kernel.name}${lib.optionalString (abi.name != "unknown") "-${abi.name}"}";
-
-        # The name of the rust target if it is standard, or the json file
-        # containing the custom target spec.
-        rustcTargetSpec =
-          /**/ if rust ? platform
-          then builtins.toFile (final.rust.rustcTarget + ".json") (builtins.toJSON rust.platform)
-          else final.rust.rustcTarget;
-
-        # The name of the rust target if it is standard, or the
-        # basename of the file containing the custom target spec,
-        # without the .json extension.
-        #
-        # This is the name used by Cargo for target subdirectories.
-        cargoShortTarget =
-          lib.removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
-
-        # When used as part of an environment variable name, triples are
-        # uppercased and have all hyphens replaced by underscores:
-        #
-        # https://github.com/rust-lang/cargo/pull/9169
-        # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
-        cargoEnvVarTarget =
-          lib.strings.replaceStrings ["-"] ["_"]
-            (lib.strings.toUpper final.rust.cargoShortTarget);
-
-        # True if the target is no_std
-        # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
-        isNoStdTarget =
-          builtins.any (t: lib.hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"];
-      };
-
       linuxArch =
         if final.isAarch32 then "arm"
         else if final.isAarch64 then "arm64"
@@ -356,7 +266,97 @@ rec {
 
     }) // mapAttrs (n: v: v final.parsed) inspect.predicates
       // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates
-      // args;
+      // args // {
+        rust = rust // {
+          # Once args.rustc.platform.target-family is deprecated and
+          # removed, there will no longer be any need to modify any
+          # values from args.rust.platform, so we can drop all the
+          # "args ? rust" etc. checks, and merge args.rust.platform in
+          # /after/.
+          platform = rust.platform or {} // {
+            # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch
+            arch =
+              /**/ if rust ? platform then rust.platform.arch
+              else if final.isAarch32 then "arm"
+              else if final.isMips64  then "mips64"     # never add "el" suffix
+              else if final.isPower64 then "powerpc64"  # never add "le" suffix
+              else final.parsed.cpu.name;
+
+            # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
+            os =
+              /**/ if rust ? platform then rust.platform.os or "none"
+              else if final.isDarwin then "macos"
+              else final.parsed.kernel.name;
+
+            # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family
+            target-family =
+              /**/ if args ? rust.platform.target-family then args.rust.platform.target-family
+              else if args ? rustc.platform.target-family
+              then
+                (
+                  # Since https://github.com/rust-lang/rust/pull/84072
+                  # `target-family` is a list instead of single value.
+                  let
+                    f = args.rustc.platform.target-family;
+                  in
+                    if builtins.isList f then f else [ f ]
+                )
+              else lib.optional final.isUnix "unix"
+                   ++ lib.optional final.isWindows "windows";
+
+            # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor
+            vendor = let
+              inherit (final.parsed) vendor;
+            in rust.platform.vendor or {
+              "w64" = "pc";
+            }.${vendor.name} or vendor.name;
+          };
+
+          # The name of the rust target, even if it is custom. Adjustments are
+          # because rust has slightly different naming conventions than we do.
+          rustcTarget = let
+            inherit (final.parsed) cpu kernel abi;
+            cpu_ = rust.platform.arch or {
+              "armv7a" = "armv7";
+              "armv7l" = "armv7";
+              "armv6l" = "arm";
+              "armv5tel" = "armv5te";
+              "riscv64" = "riscv64gc";
+            }.${cpu.name} or cpu.name;
+            vendor_ = final.rust.platform.vendor;
+          in rust.config
+            or "${cpu_}-${vendor_}-${kernel.name}${lib.optionalString (abi.name != "unknown") "-${abi.name}"}";
+
+          # The name of the rust target if it is standard, or the json file
+          # containing the custom target spec.
+          rustcTargetSpec = rust.rustcTargetSpec or (
+            /**/ if rust ? platform
+            then builtins.toFile (final.rust.rustcTarget + ".json") (builtins.toJSON rust.platform)
+            else final.rust.rustcTarget);
+
+          # The name of the rust target if it is standard, or the
+          # basename of the file containing the custom target spec,
+          # without the .json extension.
+          #
+          # This is the name used by Cargo for target subdirectories.
+          cargoShortTarget =
+            lib.removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}");
+
+          # When used as part of an environment variable name, triples are
+          # uppercased and have all hyphens replaced by underscores:
+          #
+          # https://github.com/rust-lang/cargo/pull/9169
+          # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431
+          cargoEnvVarTarget =
+            lib.strings.replaceStrings ["-"] ["_"]
+              (lib.strings.toUpper final.rust.cargoShortTarget);
+
+          # True if the target is no_std
+          # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421
+          isNoStdTarget =
+            builtins.any (t: lib.hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"];
+        };
+      };
   in assert final.useAndroidPrebuilt -> final.isAndroid;
      assert lib.foldl
        (pass: { assertion, message }:
diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix
index 06cb5e763e2c..9f1fee2ba234 100644
--- a/nixpkgs/lib/tests/misc.nix
+++ b/nixpkgs/lib/tests/misc.nix
@@ -831,6 +831,26 @@ runTests {
     };
   };
 
+  testMatchAttrsMatchingExact = {
+    expr = matchAttrs { cpu = { bits = 64; }; } { cpu = { bits = 64; }; };
+    expected = true;
+  };
+
+  testMatchAttrsMismatch = {
+    expr = matchAttrs { cpu = { bits = 128; }; } { cpu = { bits = 64; }; };
+    expected = false;
+  };
+
+  testMatchAttrsMatchingImplicit = {
+    expr = matchAttrs { cpu = { }; } { cpu = { bits = 64; }; };
+    expected = true;
+  };
+
+  testMatchAttrsMissingAttrs = {
+    expr = matchAttrs { cpu = {}; } { };
+    expected = false;
+  };
+
   testOverrideExistingEmpty = {
     expr = overrideExisting {} { a = 1; };
     expected = {};
@@ -1948,4 +1968,24 @@ runTests {
   testGetExe'FailureSecondArg = testingThrow (
     getExe' { type = "derivation"; } "dir/executable"
   );
+
+  testPlatformMatch = {
+    expr = meta.platformMatch { system = "x86_64-linux"; } "x86_64-linux";
+    expected = true;
+  };
+
+  testPlatformMatchAttrs = {
+    expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed;
+    expected = true;
+  };
+
+  testPlatformMatchNoMatch = {
+    expr = meta.platformMatch { system = "x86_64-darwin"; } "x86_64-linux";
+    expected = false;
+  };
+
+  testPlatformMatchMissingSystem = {
+    expr = meta.platformMatch { } "x86_64-linux";
+    expected = false;
+  };
 }
diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix
index a89c1aa25b1f..caff77190fde 100644
--- a/nixpkgs/lib/trivial.nix
+++ b/nixpkgs/lib/trivial.nix
@@ -195,7 +195,7 @@ rec {
      On each release the first letter is bumped and a new animal is chosen
      starting with that new letter.
   */
-  codeName = "Tapir";
+  codeName = "Uakari";
 
   /* Returns the current nixpkgs version suffix as string. */
   versionSuffix =