about summary refs log tree commit diff
path: root/nixpkgs/lib
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2023-08-08 16:04:42 +0000
committerAlyssa Ross <hi@alyssa.is>2023-08-13 06:35:37 +0000
commit12aaa58dac35800b5b7d77f81cf2a87c21ee55da (patch)
treebe0add9e5c22a85d20b5d78206aa74f956eb2a1b /nixpkgs/lib
parent45892a5591202f75a1c2f1ca7c62a92c7566e3c5 (diff)
parent5a8e9243812ba528000995b294292d3b5e120947 (diff)
downloadnixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar.gz
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar.bz2
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar.lz
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar.xz
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.tar.zst
nixlib-12aaa58dac35800b5b7d77f81cf2a87c21ee55da.zip
Merge branch 'nixos-unstable' of https://github.com/NixOS/nixpkgs
Conflicts:
	nixpkgs/pkgs/applications/window-managers/sway/default.nix
	nixpkgs/pkgs/build-support/go/module.nix
	nixpkgs/pkgs/build-support/rust/build-rust-package/default.nix
	nixpkgs/pkgs/development/libraries/mesa/default.nix
	nixpkgs/pkgs/servers/dict/dictd-db.nix

Link: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/issues/391
Diffstat (limited to 'nixpkgs/lib')
-rw-r--r--nixpkgs/lib/README.md73
-rw-r--r--nixpkgs/lib/attrsets.nix38
-rw-r--r--nixpkgs/lib/customisation.nix6
-rw-r--r--nixpkgs/lib/debug.nix7
-rw-r--r--nixpkgs/lib/default.nix2
-rw-r--r--nixpkgs/lib/filesystem.nix4
-rw-r--r--nixpkgs/lib/fixed-points.nix196
-rw-r--r--nixpkgs/lib/generators.nix7
-rw-r--r--nixpkgs/lib/licenses.nix85
-rw-r--r--nixpkgs/lib/lists.nix70
-rw-r--r--nixpkgs/lib/meta.nix11
-rw-r--r--nixpkgs/lib/modules.nix175
-rw-r--r--nixpkgs/lib/options.nix33
-rw-r--r--nixpkgs/lib/path/README.md21
-rw-r--r--nixpkgs/lib/path/default.nix202
-rw-r--r--nixpkgs/lib/path/tests/default.nix5
-rwxr-xr-xnixpkgs/lib/path/tests/prop.sh9
-rw-r--r--nixpkgs/lib/path/tests/unit.nix66
-rw-r--r--nixpkgs/lib/sources.nix6
-rw-r--r--nixpkgs/lib/strings.nix6
-rw-r--r--nixpkgs/lib/systems/architectures.nix42
-rw-r--r--nixpkgs/lib/systems/default.nix49
-rw-r--r--nixpkgs/lib/systems/doubles.nix2
-rw-r--r--nixpkgs/lib/systems/examples.nix14
-rw-r--r--nixpkgs/lib/systems/inspect.nix2
-rw-r--r--nixpkgs/lib/systems/platforms.nix7
-rw-r--r--nixpkgs/lib/tests/misc.nix126
-rwxr-xr-xnixpkgs/lib/tests/modules.sh47
-rw-r--r--nixpkgs/lib/tests/modules/merge-typeless-option.nix25
-rw-r--r--nixpkgs/lib/tests/modules/module-argument-default.nix9
-rw-r--r--nixpkgs/lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix21
-rw-r--r--nixpkgs/lib/tests/modules/types.nix24
-rw-r--r--nixpkgs/lib/tests/release.nix6
-rwxr-xr-xnixpkgs/lib/tests/sources.sh7
-rw-r--r--nixpkgs/lib/tests/systems.nix79
-rw-r--r--nixpkgs/lib/trivial.nix2
-rw-r--r--nixpkgs/lib/types.nix11
37 files changed, 1215 insertions, 280 deletions
diff --git a/nixpkgs/lib/README.md b/nixpkgs/lib/README.md
new file mode 100644
index 000000000000..ac7cbd4330ad
--- /dev/null
+++ b/nixpkgs/lib/README.md
@@ -0,0 +1,73 @@
+# Nixpkgs lib
+
+This directory contains the implementation, documentation and tests for the Nixpkgs `lib` library.
+
+## Overview
+
+The evaluation entry point for `lib` is [`default.nix`](default.nix).
+This file evaluates to an attribute set containing two separate kinds of attributes:
+- Sub-libraries:
+  Attribute sets grouping together similar functionality.
+  Each sub-library is defined in a separate file usually matching its attribute name.
+
+  Example: `lib.lists` is a sub-library containing list-related functionality such as `lib.lists.take` and `lib.lists.imap0`.
+  These are defined in the file [`lists.nix`](lists.nix).
+
+- Aliases:
+  Attributes that point to an attribute of the same name in some sub-library.
+
+  Example: `lib.take` is an alias for `lib.lists.take`.
+
+Most files in this directory are definitions of sub-libraries, but there are a few others:
+- [`minver.nix`](minver.nix): A string of the minimum version of Nix that is required to evaluate Nixpkgs.
+- [`tests`](tests): Tests, see [Running tests](#running-tests)
+  - [`release.nix`](tests/release.nix): A derivation aggregating all tests
+  - [`misc.nix`](tests/misc.nix): Evaluation unit tests for most sub-libraries
+  - `*.sh`: Bash scripts that run tests for specific sub-libraries
+  - All other files in this directory exist to support the tests
+- [`systems`](systems): The `lib.systems` sub-library, structured into a directory instead of a file due to its complexity
+- [`path`](path): The `lib.path` sub-library, which includes tests as well as a document describing the design goals of `lib.path`
+- All other files in this directory are sub-libraries
+
+### Module system
+
+The [module system](https://nixos.org/manual/nixpkgs/#module-system) spans multiple sub-libraries:
+- [`modules.nix`](modules.nix): `lib.modules` for the core functions and anything not relating to option definitions
+- [`options.nix`](options.nix): `lib.options` for anything relating to option definitions
+- [`types.nix`](types.nix): `lib.types` for module system types
+
+## Reference documentation
+
+Reference documentation for library functions is written above each function as a multi-line comment.
+These comments are processed using [nixdoc](https://github.com/nix-community/nixdoc) and [rendered in the Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#chap-functions).
+The nixdoc README describes the [comment format](https://github.com/nix-community/nixdoc#comment-format).
+
+See the [chapter on contributing to the Nixpkgs manual](https://nixos.org/manual/nixpkgs/#chap-contributing) for how to build the manual.
+
+## Running tests
+
+All library tests can be run by building the derivation in [`tests/release.nix`](tests/release.nix):
+
+```bash
+nix-build tests/release.nix
+```
+
+Some commands for quicker iteration over parts of the test suite are also available:
+
+```bash
+# Run all evaluation unit tests in tests/misc.nix
+# if the resulting list is empty, all tests passed
+nix-instantiate --eval --strict tests/misc.nix
+
+# Run the module system tests
+tests/modules.sh
+
+# Run the lib.sources tests
+tests/sources.sh
+
+# Run the lib.filesystem tests
+tests/filesystem.sh
+
+# Run the lib.path property tests
+path/tests/prop.sh
+```
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix
index 73b71f68db79..77e36d3271f7 100644
--- a/nixpkgs/lib/attrsets.nix
+++ b/nixpkgs/lib/attrsets.nix
@@ -3,7 +3,7 @@
 
 let
   inherit (builtins) head tail length;
-  inherit (lib.trivial) flip id mergeAttrs pipe;
+  inherit (lib.trivial) id mergeAttrs;
   inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName;
   inherit (lib.lists) foldr foldl' concatMap concatLists elemAt all partition groupBy take foldl;
 in
@@ -738,6 +738,42 @@ rec {
     sets:
     zipAttrsWith (name: values: values) sets;
 
+  /*
+    Merge a list of attribute sets together using the `//` operator.
+    In case of duplicate attributes, values from later list elements take precedence over earlier ones.
+    The result is the same as `foldl mergeAttrs { }`, but the performance is better for large inputs.
+    For n list elements, each with an attribute set containing m unique attributes, the complexity of this operation is O(nm log n).
+
+    Type:
+      mergeAttrsList :: [ Attrs ] -> Attrs
+
+    Example:
+      mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ]
+      => { a = 0; b = 1; c = 2; d = 3; }
+      mergeAttrsList [ { a = 0; } { a = 1; } ]
+      => { a = 1; }
+  */
+  mergeAttrsList = list:
+    let
+      # `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end`
+      # Type: Int -> Int -> Attrs
+      binaryMerge = start: end:
+        # assert start < end; # Invariant
+        if end - start >= 2 then
+          # If there's at least 2 elements, split the range in two, recurse on each part and merge the result
+          # The invariant is satisfied because each half will have at least 1 element
+          binaryMerge start (start + (end - start) / 2)
+          // binaryMerge (start + (end - start) / 2) end
+        else
+          # Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly
+          elemAt list start;
+    in
+    if list == [ ] then
+      # Calling binaryMerge as below would not satisfy its invariant
+      { }
+    else
+      binaryMerge 0 (length list);
+
 
   /* Does the same as the update operator '//' except that attributes are
      merged until the given predicate is verified.  The predicate should
diff --git a/nixpkgs/lib/customisation.nix b/nixpkgs/lib/customisation.nix
index fe32e890f357..a9281b1ab698 100644
--- a/nixpkgs/lib/customisation.nix
+++ b/nixpkgs/lib/customisation.nix
@@ -46,12 +46,6 @@ rec {
       //
       (drv.passthru or {})
       //
-      # TODO(@Artturin): remove before release 23.05 and only have __spliced.
-      (lib.optionalAttrs (drv ? crossDrv && drv ? nativeDrv) {
-        crossDrv = overrideDerivation drv.crossDrv f;
-        nativeDrv = overrideDerivation drv.nativeDrv f;
-      })
-      //
       lib.optionalAttrs (drv ? __spliced) {
         __spliced = {} // (lib.mapAttrs (_: sDrv: overrideDerivation sDrv f) drv.__spliced);
       });
diff --git a/nixpkgs/lib/debug.nix b/nixpkgs/lib/debug.nix
index a851cd74778c..97e87acccf0e 100644
--- a/nixpkgs/lib/debug.nix
+++ b/nixpkgs/lib/debug.nix
@@ -15,22 +15,15 @@
 { lib }:
 let
   inherit (lib)
-    isInt
-    attrNames
     isList
     isAttrs
     substring
-    addErrorContext
     attrValues
     concatLists
-    concatStringsSep
     const
     elem
     generators
-    head
     id
-    isDerivation
-    isFunction
     mapAttrs
     trace;
 in
diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix
index 8fea4b8ad637..73b8ad871544 100644
--- a/nixpkgs/lib/default.nix
+++ b/nixpkgs/lib/default.nix
@@ -138,7 +138,7 @@ let
       mergeDefaultOption mergeOneOption mergeEqualOption mergeUniqueOption
       getValues getFiles
       optionAttrSetToDocList optionAttrSetToDocList'
-      scrubOptionValue literalExpression literalExample literalDocBook
+      scrubOptionValue literalExpression literalExample
       showOption showOptionWithDefLocs showFiles
       unknownModule mkOption mkPackageOption mkPackageOptionMD
       mdDoc literalMD;
diff --git a/nixpkgs/lib/filesystem.nix b/nixpkgs/lib/filesystem.nix
index 4860d4d02a77..f5bb8e9b5950 100644
--- a/nixpkgs/lib/filesystem.nix
+++ b/nixpkgs/lib/filesystem.nix
@@ -9,10 +9,6 @@ let
     pathExists
     ;
 
-  inherit (lib.strings)
-    hasPrefix
-    ;
-
   inherit (lib.filesystem)
     pathType
     ;
diff --git a/nixpkgs/lib/fixed-points.nix b/nixpkgs/lib/fixed-points.nix
index 926428293c1c..a63f349b713d 100644
--- a/nixpkgs/lib/fixed-points.nix
+++ b/nixpkgs/lib/fixed-points.nix
@@ -1,34 +1,49 @@
 { lib, ... }:
 rec {
-  # Compute the fixed point of the given function `f`, which is usually an
-  # attribute set that expects its final, non-recursive representation as an
-  # argument:
-  #
-  #     f = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
-  #
-  # Nix evaluates this recursion until all references to `self` have been
-  # resolved. At that point, the final result is returned and `f x = x` holds:
-  #
-  #     nix-repl> fix f
-  #     { bar = "bar"; foo = "foo"; foobar = "foobar"; }
-  #
-  #  Type: fix :: (a -> a) -> a
-  #
-  # See https://en.wikipedia.org/wiki/Fixed-point_combinator for further
-  # details.
+  /*
+    Compute the fixed point of the given function `f`, which is usually an
+    attribute set that expects its final, non-recursive representation as an
+    argument:
+
+    ```
+    f = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
+    ```
+
+    Nix evaluates this recursion until all references to `self` have been
+    resolved. At that point, the final result is returned and `f x = x` holds:
+
+    ```
+    nix-repl> fix f
+    { bar = "bar"; foo = "foo"; foobar = "foobar"; }
+    ```
+
+    Type: fix :: (a -> a) -> a
+
+    See https://en.wikipedia.org/wiki/Fixed-point_combinator for further
+    details.
+  */
   fix = f: let x = f x; in x;
 
-  # A variant of `fix` that records the original recursive attribute set in the
-  # result. This is useful in combination with the `extends` function to
-  # implement deep overriding. See pkgs/development/haskell-modules/default.nix
-  # for a concrete example.
+  /*
+    A variant of `fix` that records the original recursive attribute set in the
+    result, in an attribute named `__unfix__`.
+
+    This is useful in combination with the `extends` function to
+    implement deep overriding.
+  */
   fix' = f: let x = f x // { __unfix__ = f; }; in x;
 
-  # Return the fixpoint that `f` converges to when called recursively, starting
-  # with the input `x`.
-  #
-  #     nix-repl> converge (x: x / 2) 16
-  #     0
+  /*
+    Return the fixpoint that `f` converges to when called iteratively, starting
+    with the input `x`.
+
+    ```
+    nix-repl> converge (x: x / 2) 16
+    0
+    ```
+
+    Type: (a -> a) -> a -> a
+  */
   converge = f: x:
     let
       x' = f x;
@@ -37,75 +52,94 @@ rec {
       then x
       else converge f x';
 
-  # Modify the contents of an explicitly recursive attribute set in a way that
-  # honors `self`-references. This is accomplished with a function
-  #
-  #     g = self: super: { foo = super.foo + " + "; }
-  #
-  # that has access to the unmodified input (`super`) as well as the final
-  # non-recursive representation of the attribute set (`self`). `extends`
-  # differs from the native `//` operator insofar as that it's applied *before*
-  # references to `self` are resolved:
-  #
-  #     nix-repl> fix (extends g f)
-  #     { bar = "bar"; foo = "foo + "; foobar = "foo + bar"; }
-  #
-  # The name of the function is inspired by object-oriented inheritance, i.e.
-  # think of it as an infix operator `g extends f` that mimics the syntax from
-  # Java. It may seem counter-intuitive to have the "base class" as the second
-  # argument, but it's nice this way if several uses of `extends` are cascaded.
-  #
-  # To get a better understanding how `extends` turns a function with a fix
-  # point (the package set we start with) into a new function with a different fix
-  # point (the desired packages set) lets just see, how `extends g f`
-  # unfolds with `g` and `f` defined above:
-  #
-  # extends g f = self: let super = f self; in super // g self super;
-  #             = self: let super = { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }; in super // g self super
-  #             = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // g self { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
-  #             = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // { foo = "foo" + " + "; }
-  #             = self: { foo = "foo + "; bar = "bar"; foobar = self.foo + self.bar; }
-  #
+  /*
+    Modify the contents of an explicitly recursive attribute set in a way that
+    honors `self`-references. This is accomplished with a function
+
+    ```nix
+    g = self: super: { foo = super.foo + " + "; }
+    ```
+
+    that has access to the unmodified input (`super`) as well as the final
+    non-recursive representation of the attribute set (`self`). `extends`
+    differs from the native `//` operator insofar as that it's applied *before*
+    references to `self` are resolved:
+
+    ```
+    nix-repl> fix (extends g f)
+    { bar = "bar"; foo = "foo + "; foobar = "foo + bar"; }
+    ```
+
+    The name of the function is inspired by object-oriented inheritance, i.e.
+    think of it as an infix operator `g extends f` that mimics the syntax from
+    Java. It may seem counter-intuitive to have the "base class" as the second
+    argument, but it's nice this way if several uses of `extends` are cascaded.
+
+    To get a better understanding how `extends` turns a function with a fix
+    point (the package set we start with) into a new function with a different fix
+    point (the desired packages set) lets just see, how `extends g f`
+    unfolds with `g` and `f` defined above:
+
+    ```
+    extends g f = self: let super = f self; in super // g self super;
+                = self: let super = { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }; in super // g self super
+                = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // g self { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
+                = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // { foo = "foo" + " + "; }
+                = self: { foo = "foo + "; bar = "bar"; foobar = self.foo + self.bar; }
+    ```
+  */
   extends = f: rattrs: self: let super = rattrs self; in super // f self super;
 
-  # Compose two extending functions of the type expected by 'extends'
-  # into one where changes made in the first are available in the
-  # 'super' of the second
+  /*
+    Compose two extending functions of the type expected by 'extends'
+    into one where changes made in the first are available in the
+    'super' of the second
+  */
   composeExtensions =
     f: g: final: prev:
       let fApplied = f final prev;
           prev' = prev // fApplied;
       in fApplied // g final prev';
 
-  # Compose several extending functions of the type expected by 'extends' into
-  # one where changes made in preceding functions are made available to
-  # subsequent ones.
-  #
-  # composeManyExtensions : [packageSet -> packageSet -> packageSet] -> packageSet -> packageSet -> packageSet
-  #                          ^final        ^prev         ^overrides     ^final        ^prev         ^overrides
+  /*
+    Compose several extending functions of the type expected by 'extends' into
+    one where changes made in preceding functions are made available to
+    subsequent ones.
+
+    ```
+    composeManyExtensions : [packageSet -> packageSet -> packageSet] -> packageSet -> packageSet -> packageSet
+                              ^final        ^prev         ^overrides     ^final        ^prev         ^overrides
+    ```
+  */
   composeManyExtensions =
     lib.foldr (x: y: composeExtensions x y) (final: prev: {});
 
-  # Create an overridable, recursive attribute set. For example:
-  #
-  #     nix-repl> obj = makeExtensible (self: { })
-  #
-  #     nix-repl> obj
-  #     { __unfix__ = «lambda»; extend = «lambda»; }
-  #
-  #     nix-repl> obj = obj.extend (self: super: { foo = "foo"; })
-  #
-  #     nix-repl> obj
-  #     { __unfix__ = «lambda»; extend = «lambda»; foo = "foo"; }
-  #
-  #     nix-repl> obj = obj.extend (self: super: { foo = super.foo + " + "; bar = "bar"; foobar = self.foo + self.bar; })
-  #
-  #     nix-repl> obj
-  #     { __unfix__ = «lambda»; bar = "bar"; extend = «lambda»; foo = "foo + "; foobar = "foo + bar"; }
+  /*
+    Create an overridable, recursive attribute set. For example:
+
+    ```
+    nix-repl> obj = makeExtensible (self: { })
+
+    nix-repl> obj
+    { __unfix__ = «lambda»; extend = «lambda»; }
+
+    nix-repl> obj = obj.extend (self: super: { foo = "foo"; })
+
+    nix-repl> obj
+    { __unfix__ = «lambda»; extend = «lambda»; foo = "foo"; }
+
+    nix-repl> obj = obj.extend (self: super: { foo = super.foo + " + "; bar = "bar"; foobar = self.foo + self.bar; })
+
+    nix-repl> obj
+    { __unfix__ = «lambda»; bar = "bar"; extend = «lambda»; foo = "foo + "; foobar = "foo + bar"; }
+    ```
+  */
   makeExtensible = makeExtensibleWithCustomName "extend";
 
-  # Same as `makeExtensible` but the name of the extending attribute is
-  # customized.
+  /*
+    Same as `makeExtensible` but the name of the extending attribute is
+    customized.
+  */
   makeExtensibleWithCustomName = extenderName: rattrs:
     fix' (self: (rattrs self) // {
       ${extenderName} = f: makeExtensibleWithCustomName extenderName (extends f rattrs);
diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix
index 496845fc9ae4..c37be1942d82 100644
--- a/nixpkgs/lib/generators.nix
+++ b/nixpkgs/lib/generators.nix
@@ -81,9 +81,10 @@ rec {
    */
   toKeyValue = {
     mkKeyValue ? mkKeyValueDefault {} "=",
-    listsAsDuplicateKeys ? false
+    listsAsDuplicateKeys ? false,
+    indent ? ""
   }:
-  let mkLine = k: v: mkKeyValue k v + "\n";
+  let mkLine = k: v: indent + mkKeyValue k v + "\n";
       mkLines = if listsAsDuplicateKeys
         then k: v: map (mkLine k) (if lib.isList v then v else [v])
         else k: v: [ (mkLine k v) ];
@@ -168,7 +169,7 @@ rec {
     mkKeyValue    ? mkKeyValueDefault {} "=",
     # allow lists as values for duplicate keys
     listsAsDuplicateKeys ? false
-  }: { globalSection, sections }:
+  }: { globalSection, sections ? {} }:
     ( if globalSection == {}
       then ""
       else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection)
diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix
index 1d2565fba6c0..599e8ee53c93 100644
--- a/nixpkgs/lib/licenses.nix
+++ b/nixpkgs/lib/licenses.nix
@@ -1,7 +1,7 @@
 { lib }:
 
 lib.mapAttrs (lname: lset: let
-  defaultLicense = rec {
+  defaultLicense = {
     shortName = lname;
     free = true; # Most of our licenses are Free, explicitly declare unfree additions as such!
     deprecated = false;
@@ -98,6 +98,11 @@ in mkLicense lset) ({
     fullName = "Artistic License 1.0";
   };
 
+  artistic1-cl8 = {
+    spdxId = "Artistic-1.0-cl8";
+    fullName = "Artistic License 1.0 w/clause 8";
+  };
+
   artistic2 = {
     spdxId = "Artistic-2.0";
     fullName = "Artistic License 2.0";
@@ -178,6 +183,11 @@ in mkLicense lset) ({
     fullName = ''BSD 3-clause "New" or "Revised" License'';
   };
 
+  bsd3Clear = {
+    spdxId = "BSD-3-Clause-Clear";
+    fullName = "BSD 3-Clause Clear License";
+  };
+
   bsdOriginal = {
     spdxId = "BSD-4-Clause";
     fullName = ''BSD 4-clause "Original" or "Old" License'';
@@ -290,11 +300,26 @@ in mkLicense lset) ({
     free = false;
   };
 
+  cc-by-sa-10 = {
+    spdxId = "CC-BY-SA-1.0";
+    fullName = "Creative Commons Attribution Share Alike 1.0";
+  };
+
+  cc-by-sa-20 = {
+    spdxId = "CC-BY-SA-2.0";
+    fullName = "Creative Commons Attribution Share Alike 2.0";
+  };
+
   cc-by-sa-25 = {
     spdxId = "CC-BY-SA-2.5";
     fullName = "Creative Commons Attribution Share Alike 2.5";
   };
 
+  cc-by-10 = {
+    spdxId = "CC-BY-1.0";
+    fullName = "Creative Commons Attribution 1.0";
+  };
+
   cc-by-30 = {
     spdxId = "CC-BY-3.0";
     fullName = "Creative Commons Attribution 3.0";
@@ -481,6 +506,16 @@ in mkLicense lset) ({
     url = "http://www.schristiancollins.com/generaluser.php"; # license included in sources
   };
 
+  gfl = {
+    fullName = "GUST Font License";
+    url = "http://www.gust.org.pl/fonts/licenses/GUST-FONT-LICENSE.txt";
+  };
+
+  gfsl = {
+    fullName = "GUST Font Source License";
+    url = "http://www.gust.org.pl/fonts/licenses/GUST-FONT-SOURCE-LICENSE.txt";
+  };
+
   gpl1Only = {
     spdxId = "GPL-1.0-only";
     fullName = "GNU General Public License v1.0 only";
@@ -615,12 +650,31 @@ in mkLicense lset) ({
     free = true;
   };
 
+  fairsource09 = {
+    fullName = "Fair Source License, version 0.9";
+    url = "https://fair.io/v0.9.txt";
+    free = false;
+    redistributable = true;
+  };
+
+  hl3 = {
+    fullName = "Hippocratic License v3.0";
+    url = "https://firstdonoharm.dev/version/3/0/core.txt";
+    free = false;
+    redistributable = true;
+  };
+
   issl = {
     fullName = "Intel Simplified Software License";
     url = "https://software.intel.com/en-us/license/intel-simplified-software-license";
     free = false;
   };
 
+  knuth = {
+    fullName = "Knuth CTAN License";
+    spdxId = "Knuth-CTAN";
+  };
+
   lal12 = {
     spdxId = "LAL-1.2";
     fullName = "Licence Art Libre 1.2";
@@ -697,11 +751,21 @@ in mkLicense lset) ({
     url = "https://opensource.franz.com/preamble.html";
   };
 
+  lppl1 = {
+    spdxId = "LPPL-1.0";
+    fullName = "LaTeX Project Public License v1.0";
+  };
+
   lppl12 = {
     spdxId = "LPPL-1.2";
     fullName = "LaTeX Project Public License v1.2";
   };
 
+  lppl13a = {
+    spdxId = "LPPL-1.3a";
+    fullName = "LaTeX Project Public License v1.3a";
+  };
+
   lppl13c = {
     spdxId = "LPPL-1.3c";
     fullName = "LaTeX Project Public License v1.3c";
@@ -759,6 +823,12 @@ in mkLicense lset) ({
     fullName = "Microsoft Public License";
   };
 
+  mulan-psl2 = {
+    spdxId = "MulanPSL-2.0";
+    fullName = "Mulan Permissive Software License, Version 2";
+    url = "https://license.coscl.org.cn/MulanPSL2";
+  };
+
   nasa13 = {
     spdxId = "NASA-1.3";
     fullName = "NASA Open Source Agreement 1.3";
@@ -817,6 +887,11 @@ in mkLicense lset) ({
     fullName = "OpenSSL License";
   };
 
+  opubl = {
+    spdxId = "OPUBL-1.0";
+    fullName = "Open Publication License v1.0";
+  };
+
   osl2 = {
     spdxId = "OSL-2.0";
     fullName = "Open Software License 2.0";
@@ -939,6 +1014,14 @@ in mkLicense lset) ({
     url = "https://github.com/thestk/stk/blob/master/LICENSE";
   };
 
+  sustainableUse = {
+    shortName = "sustainable";
+    fullName = "Sustainable Use License";
+    url = "https://github.com/n8n-io/n8n/blob/master/LICENSE.md";
+    free = false;
+    redistributable = false; # only free to redistribute "for non-commercial purposes"
+  };
+
   tsl = {
     shortName = "TSL";
     fullName = "Timescale License Agreegment";
diff --git a/nixpkgs/lib/lists.nix b/nixpkgs/lib/lists.nix
index 5d9af0cf7114..3e058b3f1aef 100644
--- a/nixpkgs/lib/lists.nix
+++ b/nixpkgs/lib/lists.nix
@@ -3,7 +3,7 @@
 { lib }:
 let
   inherit (lib.strings) toInt;
-  inherit (lib.trivial) compare min;
+  inherit (lib.trivial) compare min id;
   inherit (lib.attrsets) mapAttrs;
 in
 rec {
@@ -180,18 +180,18 @@ rec {
       else if len != 1 then multiple
       else head found;
 
-  /* Find the first element in the list matching the specified
+  /* Find the first index in the list matching the specified
      predicate or return `default` if no such element exists.
 
-     Type: findFirst :: (a -> bool) -> a -> [a] -> a
+     Type: findFirstIndex :: (a -> Bool) -> b -> [a] -> (Int | b)
 
      Example:
-       findFirst (x: x > 3) 7 [ 1 6 4 ]
-       => 6
-       findFirst (x: x > 9) 7 [ 1 6 4 ]
-       => 7
+       findFirstIndex (x: x > 3) null [ 0 6 4 ]
+       => 1
+       findFirstIndex (x: x > 9) null [ 0 6 4 ]
+       => null
   */
-  findFirst =
+  findFirstIndex =
     # Predicate
     pred:
     # Default value to return
@@ -229,7 +229,33 @@ rec {
     if resultIndex < 0 then
       default
     else
-      elemAt list resultIndex;
+      resultIndex;
+
+  /* Find the first element in the list matching the specified
+     predicate or return `default` if no such element exists.
+
+     Type: findFirst :: (a -> bool) -> a -> [a] -> a
+
+     Example:
+       findFirst (x: x > 3) 7 [ 1 6 4 ]
+       => 6
+       findFirst (x: x > 9) 7 [ 1 6 4 ]
+       => 7
+  */
+  findFirst =
+    # Predicate
+    pred:
+    # Default value to return
+    default:
+    # Input list
+    list:
+    let
+      index = findFirstIndex pred null list;
+    in
+    if index == null then
+      default
+    else
+      elemAt list index;
 
   /* Return true if function `pred` returns true for at least one
      element of `list`.
@@ -637,6 +663,32 @@ rec {
        else if start + count > len then len - start
        else count);
 
+  /* The common prefix of two lists.
+
+  Type: commonPrefix :: [a] -> [a] -> [a]
+
+  Example:
+    commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ]
+    => [ 1 2 ]
+    commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ]
+    => [ 1 2 3 ]
+    commonPrefix [ 1 2 3 ] [ 4 5 6 ]
+    => [ ]
+  */
+  commonPrefix =
+    list1:
+    list2:
+    let
+      # Zip the lists together into a list of booleans whether each element matches
+      matchings = zipListsWith (fst: snd: fst != snd) list1 list2;
+      # Find the first index where the elements don't match,
+      # which will then also be the length of the common prefix.
+      # If all elements match, we fall back to the length of the zipped list,
+      # which is the same as the length of the smaller list.
+      commonPrefixLength = findFirstIndex id (length matchings) matchings;
+    in
+    take commonPrefixLength list1;
+
   /* Return the last element of a list.
 
      This function throws an error if the list is empty.
diff --git a/nixpkgs/lib/meta.nix b/nixpkgs/lib/meta.nix
index 5fd55c4e90d6..21404b3a2bfa 100644
--- a/nixpkgs/lib/meta.nix
+++ b/nixpkgs/lib/meta.nix
@@ -132,10 +132,9 @@ rec {
         { shortName = licstr; }
       );
 
-  /* Get the path to the main program of a derivation with either
-     meta.mainProgram or pname or name
+  /* Get the path to the main program of a package based on meta.mainProgram
 
-     Type: getExe :: derivation -> string
+     Type: getExe :: package -> string
 
      Example:
        getExe pkgs.hello
@@ -144,5 +143,9 @@ rec {
        => "/nix/store/am9ml4f4ywvivxnkiaqwr0hyxka1xjsf-mustache-go-1.3.0/bin/mustache"
   */
   getExe = x:
-    "${lib.getBin x}/bin/${x.meta.mainProgram or (lib.getName x)}";
+    "${lib.getBin x}/bin/${x.meta.mainProgram or (
+      # This could be turned into an error when 23.05 is at end of life
+      lib.warn "getExe: Package ${lib.strings.escapeNixIdentifier x.meta.name or x.pname or x.name} does not have the meta.mainProgram attribute. We'll assume that the main program has the same name for now, but this behavior is deprecated, because it leads to surprising errors when the assumption does not hold. If the package has a main program, please set `meta.mainProgram` in its definition to make this warning go away. Otherwise, if the package does not have a main program, or if you don't control its definition, specify the full path to the program, such as \"\${lib.getBin foo}/bin/bar\"."
+      lib.getName x
+    )}";
 }
diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix
index 4dc8c663b2fe..4966619f6630 100644
--- a/nixpkgs/lib/modules.nix
+++ b/nixpkgs/lib/modules.nix
@@ -21,7 +21,6 @@ let
     isBool
     isFunction
     isList
-    isPath
     isString
     length
     mapAttrs
@@ -134,11 +133,6 @@ let
             ${if prefix == []
               then null  # unset => visible
               else "internal"} = true;
-            # TODO: hidden during the markdown transition to not expose downstream
-            # users of the docs infra to markdown if they're not ready for it.
-            # we don't make this visible conditionally because it can impact
-            # performance (https://github.com/NixOS/nixpkgs/pull/208407#issuecomment-1368246192)
-            visible = false;
             # TODO: Change the type of this option to a submodule with a
             # freeformType, so that individual arguments can be documented
             # separately
@@ -545,59 +539,74 @@ let
 
   mergeModules' = prefix: options: configs:
     let
-     /* byName is like foldAttrs, but will look for attributes to merge in the
-        specified attribute name.
-
-        byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
-        [
-          {
-            hidden="baz";
-            foo={qux="bar"; gla="flop";};
-          }
-          {
-            hidden="fli";
-            foo={qux="gne"; gli="flip";};
-          }
-        ]
-        ===>
-        {
-          gla = [ "module.hidden=baz,value=flop" ];
-          gli = [ "module.hidden=fli,value=flip" ];
-          qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
-        }
-      */
-      byName = attr: f: modules:
-        zipAttrsWith (n: concatLists)
-          (map (module: let subtree = module.${attr}; in
+      # an attrset 'name' => list of submodules that declare ‘name’.
+      declsByName =
+        zipAttrsWith
+          (n: concatLists)
+          (map
+            (module: let subtree = module.options; in
               if !(builtins.isAttrs subtree) then
-                throw (if attr == "config" then ''
-                  You're trying to define a value of type `${builtins.typeOf subtree}'
-                  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 ''
+                throw ''
                   An option declaration for `${builtins.concatStringsSep "." prefix}' has type
                   `${builtins.typeOf subtree}' rather than an attribute set.
                   Did you mean to define this outside of `options'?
-                '')
+                ''
               else
-                mapAttrs (n: f module) subtree
-              ) modules);
-      # an attrset 'name' => list of submodules that declare ‘name’.
-      declsByName = byName "options" (module: option:
-          [{ inherit (module) _file; options = option; }]
-        ) options;
+                mapAttrs
+                  (n: option:
+                    [{ inherit (module) _file; options = option; }]
+                  )
+                  subtree
+              )
+            options);
+
+      # The root of any module definition must be an attrset.
+      checkedConfigs =
+        assert
+          lib.all
+            (c:
+              # TODO: I have my doubts that this error would occur when option definitions are not matched.
+              #       The implementation of this check used to be tied to a superficially similar check for
+              #       options, so maybe that's why this is here.
+              isAttrs c.config || throw ''
+                In module `${c.file}', you're trying to define a value of type `${builtins.typeOf c.config}'
+                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'!
+              ''
+            )
+            configs;
+        configs;
+
       # an attrset 'name' => list of submodules that define ‘name’.
-      defnsByName = byName "config" (module: value:
-          map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
-        ) configs;
+      pushedDownDefinitionsByName =
+        zipAttrsWith
+          (n: concatLists)
+          (map
+            (module:
+              mapAttrs
+                (n: value:
+                  map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
+                )
+              module.config
+            )
+            checkedConfigs);
       # extract the definitions for each loc
-      defnsByName' = byName "config" (module: value:
-          [{ inherit (module) file; inherit value; }]
-        ) configs;
+      rawDefinitionsByName =
+        zipAttrsWith
+          (n: concatLists)
+          (map
+            (module:
+              mapAttrs
+                (n: value:
+                  [{ inherit (module) file; inherit value; }]
+                )
+                module.config
+            )
+            checkedConfigs);
 
       # Convert an option tree decl to a submodule option decl
       optionTreeToOption = decl:
@@ -619,8 +628,8 @@ let
         # We're descending into attribute ‘name’.
         let
           loc = prefix ++ [name];
-          defns = defnsByName.${name} or [];
-          defns' = defnsByName'.${name} or [];
+          defns = pushedDownDefinitionsByName.${name} or [];
+          defns' = rawDefinitionsByName.${name} or [];
           optionDecls = filter (m: isOption m.options) decls;
         in
           if length optionDecls == length decls then
@@ -630,7 +639,7 @@ let
               unmatchedDefns = [];
             }
           else if optionDecls != [] then
-              if all (x: x.options.type.name == "submodule") optionDecls
+              if all (x: x.options.type.name or null == "submodule") optionDecls
               # Raw options can only be merged into submodules. Merging into
               # attrsets might be nice, but ambiguous. Suppose we have
               # attrset as a `attrsOf submodule`. User declares option
@@ -663,7 +672,7 @@ let
         # Propagate all unmatched definitions from nested option sets
         mapAttrs (n: v: v.unmatchedDefns) resultsByName
         # Plus the definitions for the current prefix that don't have a matching option
-        // removeAttrs defnsByName' (attrNames matchedOptions);
+        // removeAttrs rawDefinitionsByName (attrNames matchedOptions);
     in {
       inherit matchedOptions;
 
@@ -910,6 +919,40 @@ let
     else opt // { type = opt.type.substSubModules opt.options; options = []; };
 
 
+  /*
+    Merge an option's definitions in a way that preserves the priority of the
+    individual attributes in the option value.
+
+    This does not account for all option semantics, such as readOnly.
+
+    Type:
+      option -> attrsOf { highestPrio, value }
+  */
+  mergeAttrDefinitionsWithPrio = opt:
+        let
+            defsByAttr =
+              lib.zipAttrs (
+                lib.concatLists (
+                  lib.concatMap
+                    ({ value, ... }@def:
+                      map
+                        (lib.mapAttrsToList (k: value: { ${k} = def // { inherit value; }; }))
+                        (pushDownProperties value)
+                    )
+                    opt.definitionsWithLocations
+                )
+              );
+        in
+          assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf";
+          lib.mapAttrs
+                (k: v:
+                  let merging = lib.mergeDefinitions (opt.loc ++ [k]) opt.type.nestedTypes.elemType v;
+                  in {
+                    value = merging.mergedValue;
+                    inherit (merging.defsFinal') highestPrio;
+                  })
+                defsByAttr;
+
   /* Properties. */
 
   mkIf = condition: content:
@@ -1146,14 +1189,11 @@ let
     use = id;
   };
 
-  /* Transitional version of mkAliasOptionModule that uses MD docs. */
-  mkAliasOptionModuleMD = from: to: doRename {
-    inherit from to;
-    visible = true;
-    warn = false;
-    use = id;
-    markdown = true;
-  };
+  /* Transitional version of mkAliasOptionModule that uses MD docs.
+
+     This function is no longer necessary and merely an alias of `mkAliasOptionModule`.
+  */
+  mkAliasOptionModuleMD = mkAliasOptionModule;
 
   /* mkDerivedConfig : Option a -> (a -> Definition b) -> Definition b
 
@@ -1175,7 +1215,7 @@ let
       (opt.highestPrio or defaultOverridePriority)
       (f opt.value);
 
-  doRename = { from, to, visible, warn, use, withPriority ? true, markdown ? false }:
+  doRename = { from, to, visible, warn, use, withPriority ? true }:
     { config, options, ... }:
     let
       fromOpt = getAttrFromPath from options;
@@ -1186,9 +1226,7 @@ let
     {
       options = setAttrByPath from (mkOption {
         inherit visible;
-        description = if markdown
-          then lib.mdDoc "Alias of {option}`${showOption to}`."
-          else "Alias of <option>${showOption to}</option>.";
+        description = "Alias of {option}`${showOption to}`.";
         apply = x: use (toOf config);
       } // optionalAttrs (toType != null) {
         type = toType;
@@ -1256,6 +1294,7 @@ private //
     importJSON
     importTOML
     mergeDefinitions
+    mergeAttrDefinitionsWithPrio
     mergeOptionDecls  # should be private?
     mkAfter
     mkAliasAndWrapDefinitions
diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix
index af7914bb5137..c42bc1e6c67e 100644
--- a/nixpkgs/lib/options.nix
+++ b/nixpkgs/lib/options.nix
@@ -100,10 +100,7 @@ rec {
     name: mkOption {
     default = false;
     example = true;
-    description =
-      if name ? _type && name._type == "mdDoc"
-      then lib.mdDoc "Whether to enable ${name.text}."
-      else "Whether to enable ${name}.";
+    description = "Whether to enable ${name}.";
     type = lib.types.bool;
   };
 
@@ -185,10 +182,10 @@ rec {
           (if isList example then "pkgs." + concatStringsSep "." example else example);
       });
 
-  /* Like mkPackageOption, but emit an mdDoc description instead of DocBook. */
-  mkPackageOptionMD = pkgs: name: extra:
-    let option = mkPackageOption pkgs name extra;
-    in option // { description = lib.mdDoc option.description; };
+  /* Alias of mkPackageOption. Previously used to create options with markdown
+     documentation, which is no longer required.
+  */
+  mkPackageOptionMD = mkPackageOption;
 
   /* This option accepts anything, but it does not produce any result.
 
@@ -344,26 +341,12 @@ rec {
     if ! isString text then throw "literalExpression expects a string."
     else { _type = "literalExpression"; inherit text; };
 
-  literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalDocBook for a non-Nix description." literalExpression;
-
-
-  /* For use in the `defaultText` and `example` option attributes. Causes the
-     given DocBook text to be inserted verbatim in the documentation, for when
-     a `literalExpression` would be too hard to read.
-  */
-  literalDocBook = text:
-    if ! isString text then throw "literalDocBook expects a string."
-    else
-      lib.warnIf (lib.isInOldestRelease 2211)
-        "literalDocBook is deprecated, use literalMD instead"
-        { _type = "literalDocBook"; inherit text; };
+  literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalMD for a non-Nix description." literalExpression;
 
   /* Transition marker for documentation that's already migrated to markdown
-     syntax.
+     syntax. This is a no-op and no longer needed.
   */
-  mdDoc = text:
-    if ! isString text then throw "mdDoc expects a string."
-    else { _type = "mdDoc"; inherit text; };
+  mdDoc = lib.id;
 
   /* For use in the `defaultText` and `example` option attributes. Causes the
      given MD text to be inserted verbatim in the documentation, for when
diff --git a/nixpkgs/lib/path/README.md b/nixpkgs/lib/path/README.md
index 87e552d120d7..89eec18b1130 100644
--- a/nixpkgs/lib/path/README.md
+++ b/nixpkgs/lib/path/README.md
@@ -187,6 +187,27 @@ Decision: All functions remove trailing slashes in their results.
 
 </details>
 
+### Prefer returning subpaths over components
+[subpath-preference]: #prefer-returning-subpaths-over-components
+
+Observing: Functions could return subpaths or lists of path component strings.
+
+Considering: Subpaths are used as inputs for some functions. Using them for outputs, too, makes the library more consistent and composable.
+
+Decision: Subpaths should be preferred over list of path component strings.
+
+<details>
+<summary>Arguments</summary>
+
+- (+) It is consistent with functions accepting subpaths, making the library more composable
+- (-) It is less efficient when the components are needed, because after creating the normalised subpath string, it will have to be parsed into components again
+  - (+) If necessary, we can still make it faster by adding builtins to Nix
+  - (+) Alternatively if necessary, versions of these functions that return components could later still be introduced.
+- (+) It makes the path library simpler because there's only two types (paths and subpaths). Only `lib.path.subpath.components` can be used to get a list of components.
+  And once we have a list of component strings, `lib.lists` and `lib.strings` can be used to operate on them.
+  For completeness, `lib.path.subpath.join` allows converting the list of components back to a subpath.
+</details>
+
 ## Other implementations and references
 
 - [Rust](https://doc.rust-lang.org/std/path/struct.Path.html)
diff --git a/nixpkgs/lib/path/default.nix b/nixpkgs/lib/path/default.nix
index a4a08668ae62..5c6c5f608954 100644
--- a/nixpkgs/lib/path/default.nix
+++ b/nixpkgs/lib/path/default.nix
@@ -7,6 +7,7 @@ let
     isPath
     split
     match
+    typeOf
     ;
 
   inherit (lib.lists)
@@ -18,6 +19,8 @@ let
     all
     concatMap
     foldl'
+    take
+    drop
     ;
 
   inherit (lib.strings)
@@ -100,6 +103,22 @@ let
     # An empty string is not a valid relative path, so we need to return a `.` when we have no components
     (if components == [] then "." else concatStringsSep "/" components);
 
+  # Type: Path -> { root :: Path, components :: [ String ] }
+  #
+  # Deconstruct a path value type into:
+  # - root: The filesystem root of the path, generally `/`
+  # - components: All the path's components
+  #
+  # This is similar to `splitString "/" (toString path)` but safer
+  # because it can distinguish different filesystem roots
+  deconstructPath =
+    let
+      recurse = components: base:
+        # If the parent of a path is the path itself, then it's a filesystem root
+        if base == dirOf base then { root = base; inherit components; }
+        else recurse ([ (baseNameOf base) ] ++ components) (dirOf base);
+    in recurse [];
+
 in /* No rec! Add dependencies on this file at the top. */ {
 
   /* Append a subpath string to a path.
@@ -108,6 +127,12 @@ in /* No rec! Add dependencies on this file at the top. */ {
     More specifically, it checks that the first argument is a [path value type](https://nixos.org/manual/nix/stable/language/values.html#type-path"),
     and that the second argument is a valid subpath string (see `lib.path.subpath.isValid`).
 
+    Laws:
+
+    - Not influenced by subpath normalisation
+
+        append p s == append p (subpath.normalise s)
+
     Type:
       append :: Path -> String -> Path
 
@@ -149,8 +174,154 @@ in /* No rec! Add dependencies on this file at the top. */ {
           ${subpathInvalidReason subpath}'';
     path + ("/" + subpath);
 
+  /*
+  Whether the first path is a component-wise prefix of the second path.
+
+  Laws:
+
+  - `hasPrefix p q` is only true if `q == append p s` for some subpath `s`.
+
+  - `hasPrefix` is a [non-strict partial order](https://en.wikipedia.org/wiki/Partially_ordered_set#Non-strict_partial_order) over the set of all path values
+
+  Type:
+    hasPrefix :: Path -> Path -> Bool
+
+  Example:
+    hasPrefix /foo /foo/bar
+    => true
+    hasPrefix /foo /foo
+    => true
+    hasPrefix /foo/bar /foo
+    => false
+    hasPrefix /. /foo
+    => true
+  */
+  hasPrefix =
+    path1:
+    assert assertMsg
+      (isPath path1)
+      "lib.path.hasPrefix: First argument is of type ${typeOf path1}, but a path was expected";
+    let
+      path1Deconstructed = deconstructPath path1;
+    in
+      path2:
+      assert assertMsg
+        (isPath path2)
+        "lib.path.hasPrefix: Second argument is of type ${typeOf path2}, but a path was expected";
+      let
+        path2Deconstructed = deconstructPath path2;
+      in
+        assert assertMsg
+        (path1Deconstructed.root == path2Deconstructed.root) ''
+          lib.path.hasPrefix: Filesystem roots must be the same for both paths, but paths with different roots were given:
+              first argument: "${toString path1}" with root "${toString path1Deconstructed.root}"
+              second argument: "${toString path2}" with root "${toString path2Deconstructed.root}"'';
+        take (length path1Deconstructed.components) path2Deconstructed.components == path1Deconstructed.components;
+
+  /*
+  Remove the first path as a component-wise prefix from the second path.
+  The result is a normalised subpath string, see `lib.path.subpath.normalise`.
+
+  Laws:
+
+  - Inverts `append` for normalised subpaths:
+
+        removePrefix p (append p s) == subpath.normalise s
+
+  Type:
+    removePrefix :: Path -> Path -> String
+
+  Example:
+    removePrefix /foo /foo/bar/baz
+    => "./bar/baz"
+    removePrefix /foo /foo
+    => "./."
+    removePrefix /foo/bar /foo
+    => <error>
+    removePrefix /. /foo
+    => "./foo"
+  */
+  removePrefix =
+    path1:
+    assert assertMsg
+      (isPath path1)
+      "lib.path.removePrefix: First argument is of type ${typeOf path1}, but a path was expected.";
+    let
+      path1Deconstructed = deconstructPath path1;
+      path1Length = length path1Deconstructed.components;
+    in
+      path2:
+      assert assertMsg
+        (isPath path2)
+        "lib.path.removePrefix: Second argument is of type ${typeOf path2}, but a path was expected.";
+      let
+        path2Deconstructed = deconstructPath path2;
+        success = take path1Length path2Deconstructed.components == path1Deconstructed.components;
+        components =
+          if success then
+            drop path1Length path2Deconstructed.components
+          else
+            throw ''
+              lib.path.removePrefix: The first path argument "${toString path1}" is not a component-wise prefix of the second path argument "${toString path2}".'';
+      in
+        assert assertMsg
+        (path1Deconstructed.root == path2Deconstructed.root) ''
+          lib.path.removePrefix: Filesystem roots must be the same for both paths, but paths with different roots were given:
+              first argument: "${toString path1}" with root "${toString path1Deconstructed.root}"
+              second argument: "${toString path2}" with root "${toString path2Deconstructed.root}"'';
+        joinRelPath components;
+
+  /*
+  Split the filesystem root from a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path).
+  The result is an attribute set with these attributes:
+  - `root`: The filesystem root of the path, meaning that this directory has no parent directory.
+  - `subpath`: The [normalised subpath string](#function-library-lib.path.subpath.normalise) that when [appended](#function-library-lib.path.append) to `root` returns the original path.
+
+  Laws:
+  - [Appending](#function-library-lib.path.append) the `root` and `subpath` gives the original path:
+
+        p ==
+          append
+            (splitRoot p).root
+            (splitRoot p).subpath
+
+  - Trying to get the parent directory of `root` using [`readDir`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readDir) returns `root` itself:
+
+        dirOf (splitRoot p).root == (splitRoot p).root
+
+  Type:
+    splitRoot :: Path -> { root :: Path, subpath :: String }
+
+  Example:
+    splitRoot /foo/bar
+    => { root = /.; subpath = "./foo/bar"; }
+
+    splitRoot /.
+    => { root = /.; subpath = "./."; }
+
+    # Nix neutralises `..` path components for all path values automatically
+    splitRoot /foo/../bar
+    => { root = /.; subpath = "./bar"; }
+
+    splitRoot "/foo/bar"
+    => <error>
+  */
+  splitRoot = path:
+    assert assertMsg
+      (isPath path)
+      "lib.path.splitRoot: Argument is of type ${typeOf path}, but a path was expected";
+    let
+      deconstructed = deconstructPath path;
+    in {
+      root = deconstructed.root;
+      subpath = joinRelPath deconstructed.components;
+    };
+
   /* Whether a value is a valid subpath string.
 
+  A subpath string points to a specific file or directory within an absolute base directory.
+  It is a stricter form of a relative path that excludes `..` components, since those could escape the base directory.
+
   - The value is a string
 
   - The string is not empty
@@ -267,6 +438,37 @@ in /* No rec! Add dependencies on this file at the top. */ {
               ${subpathInvalidReason path}''
       ) 0 subpaths;
 
+  /*
+  Split [a subpath](#function-library-lib.path.subpath.isValid) into its path component strings.
+  Throw an error if the subpath isn't valid.
+  Note that the returned path components are also valid subpath strings, though they are intentionally not [normalised](#function-library-lib.path.subpath.normalise).
+
+  Laws:
+
+  - Splitting a subpath into components and [joining](#function-library-lib.path.subpath.join) the components gives the same subpath but [normalised](#function-library-lib.path.subpath.normalise):
+
+        subpath.join (subpath.components s) == subpath.normalise s
+
+  Type:
+    subpath.components :: String -> [ String ]
+
+  Example:
+    subpath.components "."
+    => [ ]
+
+    subpath.components "./foo//bar/./baz/"
+    => [ "foo" "bar" "baz" ]
+
+    subpath.components "/foo"
+    => <error>
+  */
+  subpath.components =
+    subpath:
+    assert assertMsg (isValid subpath) ''
+      lib.path.subpath.components: Argument is not a valid subpath string:
+          ${subpathInvalidReason subpath}'';
+    splitRelPath subpath;
+
   /* Normalise a subpath. Throw an error if the subpath isn't valid, see
   `lib.path.subpath.isValid`
 
diff --git a/nixpkgs/lib/path/tests/default.nix b/nixpkgs/lib/path/tests/default.nix
index 9a31e42828f4..6b8e515f4330 100644
--- a/nixpkgs/lib/path/tests/default.nix
+++ b/nixpkgs/lib/path/tests/default.nix
@@ -24,8 +24,9 @@ pkgs.runCommand "lib-path-tests" {
   export TEST_LIB=$PWD/lib
 
   echo "Running unit tests lib/path/tests/unit.nix"
-  nix-instantiate --eval lib/path/tests/unit.nix \
-    --argstr libpath "$TEST_LIB"
+  nix-instantiate --eval --show-trace \
+    --argstr libpath "$TEST_LIB" \
+    lib/path/tests/unit.nix
 
   echo "Running property tests lib/path/tests/prop.sh"
   bash lib/path/tests/prop.sh ${toString seed}
diff --git a/nixpkgs/lib/path/tests/prop.sh b/nixpkgs/lib/path/tests/prop.sh
index e48c6667fa08..f321fdf1cf45 100755
--- a/nixpkgs/lib/path/tests/prop.sh
+++ b/nixpkgs/lib/path/tests/prop.sh
@@ -1,9 +1,12 @@
 #!/usr/bin/env bash
 
-# Property tests for the `lib.path` library
-#
+# Property tests for lib/path/default.nix
 # It generates random path-like strings and runs the functions on
 # them, checking that the expected laws of the functions hold
+# Run:
+# [nixpkgs]$ lib/path/tests/prop.sh
+# or:
+# [nixpkgs]$ nix-build lib/tests/release.nix
 
 set -euo pipefail
 shopt -s inherit_errexit
@@ -71,7 +74,7 @@ fi
 
 # Precalculate all normalisations with a single Nix call. Calling Nix for each
 # string individually would take way too long
-nix-instantiate --eval --strict --json \
+nix-instantiate --eval --strict --json --show-trace \
     --argstr libpath "$TEST_LIB" \
     --argstr dir "$tmp/strings" \
     "$SCRIPT_DIR"/prop.nix \
diff --git a/nixpkgs/lib/path/tests/unit.nix b/nixpkgs/lib/path/tests/unit.nix
index 61c4ab4d6f2e..bad6560f13a9 100644
--- a/nixpkgs/lib/path/tests/unit.nix
+++ b/nixpkgs/lib/path/tests/unit.nix
@@ -3,7 +3,7 @@
 { libpath }:
 let
   lib = import libpath;
-  inherit (lib.path) append subpath;
+  inherit (lib.path) hasPrefix removePrefix append splitRoot subpath;
 
   cases = lib.runTests {
     # Test examples from the lib.path.append documentation
@@ -40,6 +40,57 @@ let
       expected = false;
     };
 
+    testHasPrefixExample1 = {
+      expr = hasPrefix /foo /foo/bar;
+      expected = true;
+    };
+    testHasPrefixExample2 = {
+      expr = hasPrefix /foo /foo;
+      expected = true;
+    };
+    testHasPrefixExample3 = {
+      expr = hasPrefix /foo/bar /foo;
+      expected = false;
+    };
+    testHasPrefixExample4 = {
+      expr = hasPrefix /. /foo;
+      expected = true;
+    };
+
+    testRemovePrefixExample1 = {
+      expr = removePrefix /foo /foo/bar/baz;
+      expected = "./bar/baz";
+    };
+    testRemovePrefixExample2 = {
+      expr = removePrefix /foo /foo;
+      expected = "./.";
+    };
+    testRemovePrefixExample3 = {
+      expr = (builtins.tryEval (removePrefix /foo/bar /foo)).success;
+      expected = false;
+    };
+    testRemovePrefixExample4 = {
+      expr = removePrefix /. /foo;
+      expected = "./foo";
+    };
+
+    testSplitRootExample1 = {
+      expr = splitRoot /foo/bar;
+      expected = { root = /.; subpath = "./foo/bar"; };
+    };
+    testSplitRootExample2 = {
+      expr = splitRoot /.;
+      expected = { root = /.; subpath = "./."; };
+    };
+    testSplitRootExample3 = {
+      expr = splitRoot /foo/../bar;
+      expected = { root = /.; subpath = "./bar"; };
+    };
+    testSplitRootExample4 = {
+      expr = (builtins.tryEval (splitRoot "/foo/bar")).success;
+      expected = false;
+    };
+
     # Test examples from the lib.path.subpath.isValid documentation
     testSubpathIsValidExample1 = {
       expr = subpath.isValid null;
@@ -187,6 +238,19 @@ let
       expr = (builtins.tryEval (subpath.normalise "..")).success;
       expected = false;
     };
+
+    testSubpathComponentsExample1 = {
+      expr = subpath.components ".";
+      expected = [ ];
+    };
+    testSubpathComponentsExample2 = {
+      expr = subpath.components "./foo//bar/./baz/";
+      expected = [ "foo" "bar" "baz" ];
+    };
+    testSubpathComponentsExample3 = {
+      expr = (builtins.tryEval (subpath.components "/foo")).success;
+      expected = false;
+    };
   };
 in
   if cases == [] then "Unit tests successful"
diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix
index d990777c6fcc..8b7cd5c84f62 100644
--- a/nixpkgs/lib/sources.nix
+++ b/nixpkgs/lib/sources.nix
@@ -5,22 +5,16 @@
 let
   inherit (builtins)
     match
-    readDir
     split
     storeDir
-    tryEval
     ;
   inherit (lib)
     boolToString
     filter
-    getAttr
     isString
-    pathExists
     readFile
     ;
   inherit (lib.filesystem)
-    pathType
-    pathIsDirectory
     pathIsRegularFile
     ;
 
diff --git a/nixpkgs/lib/strings.nix b/nixpkgs/lib/strings.nix
index e875520c6858..1eb6cf9c1afb 100644
--- a/nixpkgs/lib/strings.nix
+++ b/nixpkgs/lib/strings.nix
@@ -18,6 +18,7 @@ rec {
     elemAt
     filter
     fromJSON
+    genList
     head
     isInt
     isList
@@ -264,7 +265,8 @@ rec {
         lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
             This function also copies the path to the Nix store, which may not be what you want.
-            This behavior is deprecated and will throw an error in the future.''
+            This behavior is deprecated and will throw an error in the future.
+            You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
       (substring 0 (stringLength pref) str == pref);
 
   /* Determine whether a string has given suffix.
@@ -345,7 +347,7 @@ rec {
        => [ "�" "�" "�" "�" ]
   */
   stringToCharacters = s:
-    map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
+    genList (p: substring p 1 s) (stringLength s);
 
   /* Manipulate a string character by character and replace them by
      strings before concatenating the results.
diff --git a/nixpkgs/lib/systems/architectures.nix b/nixpkgs/lib/systems/architectures.nix
index 57b9184ca60c..9be8c80e3f11 100644
--- a/nixpkgs/lib/systems/architectures.nix
+++ b/nixpkgs/lib/systems/architectures.nix
@@ -3,8 +3,15 @@
 rec {
   # gcc.arch to its features (as in /proc/cpuinfo)
   features = {
+    # x86_64 Generic
+    # Spec: https://gitlab.com/x86-psABIs/x86-64-ABI/
     default        = [ ];
+    x86-64         = [ ];
+    x86-64-v2      = [ "sse3" "ssse3" "sse4_1" "sse4_2"                                                  ];
+    x86-64-v3      = [ "sse3" "ssse3" "sse4_1" "sse4_2"               "avx" "avx2"          "fma"        ];
+    x86-64-v4      = [ "sse3" "ssse3" "sse4_1" "sse4_2"               "avx" "avx2" "avx512" "fma"        ];
     # x86_64 Intel
+    nehalem        = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes"                                    ];
     westmere       = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes"                                    ];
     sandybridge    = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx"                              ];
     ivybridge      = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx"                              ];
@@ -18,6 +25,7 @@ rec {
     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"        ];
+    alderlake      = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx" "avx2"          "fma"        ];
     # x86_64 AMD
     btver1         = [ "sse3" "ssse3" "sse4_1" "sse4_2"                                                  ];
     btver2         = [ "sse3" "ssse3" "sse4_1" "sse4_2"         "aes" "avx"                              ];
@@ -28,6 +36,7 @@ rec {
     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"        ];
+    znver4         = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "avx512" "fma"        ];
     # other
     armv5te        = [ ];
     armv6          = [ ];
@@ -39,23 +48,35 @@ rec {
 
   # a superior CPU has all the features of an inferior and is able to build and test code for it
   inferiors = {
+    # x86_64 Generic
+    default   = [ ];
+    x86-64    = [ ];
+    x86-64-v2 = [ "x86-64"    ];
+    x86-64-v3 = [ "x86-64-v2" ] ++ inferiors.x86-64-v2;
+    x86-64-v4 = [ "x86-64-v3" ] ++ inferiors.x86-64-v3;
+
     # x86_64 Intel
     # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
-    default        = [ ];
-    westmere       = [ ];
-    sandybridge    = [ "westmere"       ] ++ inferiors.westmere;
-    ivybridge      = [ "sandybridge"    ] ++ inferiors.sandybridge;
-    haswell        = [ "ivybridge"      ] ++ inferiors.ivybridge;
-    broadwell      = [ "haswell"        ] ++ inferiors.haswell;
-    skylake        = [ "broadwell"      ] ++ inferiors.broadwell;
-    skylake-avx512 = [ "skylake"        ] ++ inferiors.skylake;
+    nehalem        = [ "x86-64-v2"   ] ++ inferiors.x86-64-v2;
+    westmere       = [ "nehalem"     ] ++ inferiors.nehalem;
+    sandybridge    = [ "westmere"    ] ++ inferiors.westmere;
+    ivybridge      = [ "sandybridge" ] ++ inferiors.sandybridge;
+
+    haswell        = lib.unique ([ "ivybridge" "x86-64-v3" ] ++ inferiors.ivybridge ++ inferiors.x86-64-v3);
+    broadwell      = [ "haswell"   ] ++ inferiors.haswell;
+    skylake        = [ "broadwell" ] ++ inferiors.broadwell;
+
+    skylake-avx512 = lib.unique ([ "skylake" "x86-64-v4" ] ++ inferiors.skylake ++ inferiors.x86-64-v4);
     cannonlake     = [ "skylake-avx512" ] ++ inferiors.skylake-avx512;
     icelake-client = [ "cannonlake"     ] ++ inferiors.cannonlake;
     icelake-server = [ "icelake-client" ] ++ inferiors.icelake-client;
-    cascadelake    = [ "skylake-avx512" ] ++ inferiors.cannonlake;
+    cascadelake    = [ "cannonlake"     ] ++ inferiors.cannonlake;
     cooperlake     = [ "cascadelake"    ] ++ inferiors.cascadelake;
     tigerlake      = [ "icelake-server" ] ++ inferiors.icelake-server;
 
+    # CX16 does not exist on alderlake, while it does on nearly all other intel CPUs
+    alderlake      = [ ];
+
     # x86_64 AMD
     # TODO: fill this (need testing)
     btver1         = [ ];
@@ -83,9 +104,10 @@ rec {
     # 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;
+    znver1         = [ "skylake" ] ++ inferiors.skylake; # Includes haswell and x86-64-v3
     znver2         = [ "znver1"  ] ++ inferiors.znver1;
     znver3         = [ "znver2"  ] ++ inferiors.znver2;
+    znver4         = lib.unique ([ "znver3" "x86-64-v4" ] ++ inferiors.znver3 ++ inferiors.x86-64-v4);
 
     # other
     armv5te        = [ ];
diff --git a/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix
index f4784c61c675..40a2c88f32b8 100644
--- a/nixpkgs/lib/systems/default.nix
+++ b/nixpkgs/lib/systems/default.nix
@@ -9,6 +9,24 @@ rec {
   examples = import ./examples.nix { inherit lib; };
   architectures = import ./architectures.nix { inherit lib; };
 
+  /*
+    Elaborated systems contain functions, which means that they don't satisfy
+    `==` for a lack of reflexivity.
+
+    They might *appear* to satisfy `==` reflexivity when the same exact value is
+    compared to itself, because object identity is used as an "optimization";
+    compare the value with a reconstruction of itself, e.g. with `f == a: f a`,
+    or perhaps calling `elaborate` twice, and one will see reflexivity fail as described.
+
+    Hence a custom equality test.
+
+    Note that this does not canonicalize the systems, so you'll want to make sure
+    both arguments have been `elaborate`-d.
+  */
+  equals =
+    let removeFunctions = a: lib.filterAttrs (_: v: !builtins.isFunction v) a;
+    in a: b: removeFunctions a == removeFunctions b;
+
   /* List of all Nix system doubles the nixpkgs flake will expose the package set
      for. All systems listed here must be supported by nixpkgs as `localSystem`.
 
@@ -67,17 +85,18 @@ rec {
         # is why we use the more obscure "bfd" and not "binutils" for this
         # choice.
         else                                     "bfd";
-      extensions = rec {
+      extensions = lib.optionalAttrs final.hasSharedLibraries {
         sharedLibrary =
-          /**/ if final.isDarwin  then ".dylib"
+          if      final.isDarwin  then ".dylib"
           else if final.isWindows then ".dll"
           else                         ".so";
+      } // {
         staticLibrary =
           /**/ if final.isWindows then ".lib"
           else                         ".a";
         library =
-          /**/ if final.isStatic then staticLibrary
-          else                        sharedLibrary;
+          /**/ if final.isStatic then final.extensions.staticLibrary
+          else                        final.extensions.sharedLibrary;
         executable =
           /**/ if final.isWindows then ".exe"
           else                         "";
@@ -114,6 +133,25 @@ rec {
          # uname -r
          release = null;
       };
+
+      # It is important that hasSharedLibraries==false when the platform has no
+      # dynamic library loader.  Various tools (including the gcc build system)
+      # have knowledge of which platforms are incapable of dynamic linking, and
+      # will still build on/for those platforms with --enable-shared, but simply
+      # omit any `.so` build products such as libgcc_s.so.  When that happens,
+      # it causes hard-to-troubleshoot build failures.
+      hasSharedLibraries = with final;
+        (isAndroid || isGnu || isMusl                                  # Linux (allows multiple libcs)
+         || isDarwin || isSunOS || isOpenBSD || isFreeBSD || isNetBSD  # BSDs
+         || isCygwin || isMinGW                                        # Windows
+        ) && !isStatic;
+
+      # The difference between `isStatic` and `hasSharedLibraries` is mainly the
+      # addition of the `staticMarker` (see make-derivation.nix).  Some
+      # platforms, like embedded machines without a libc (e.g. arm-none-eabi)
+      # don't support dynamic linking, but don't get the `staticMarker`.
+      # `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
+      # has the `staticMarker`.
       isStatic = final.isWasm || final.isRedox;
 
       # Just a guess, based on `system`
@@ -193,8 +231,7 @@ rec {
             };
             wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal;
           in
-          if final.parsed.kernel.name == pkgs.stdenv.hostPlatform.parsed.kernel.name &&
-            pkgs.stdenv.hostPlatform.canExecute final
+          if pkgs.stdenv.hostPlatform.canExecute final
           then "${pkgs.runtimeShell} -c '\"$@\"' --"
           else if final.isWindows
           then "${wine}/bin/wine${lib.optionalString (final.parsed.cpu.bits == 64) "64"}"
diff --git a/nixpkgs/lib/systems/doubles.nix b/nixpkgs/lib/systems/doubles.nix
index 66bf3d872bee..13f029ee1f40 100644
--- a/nixpkgs/lib/systems/doubles.nix
+++ b/nixpkgs/lib/systems/doubles.nix
@@ -41,7 +41,7 @@ let
 
     # none
     "aarch64_be-none" "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-none"
-    "microblaze-none" "microblazeel-none" "msp430-none" "or1k-none" "m68k-none"
+    "microblaze-none" "microblazeel-none" "mips-none" "mips64-none" "msp430-none" "or1k-none" "m68k-none"
     "powerpc-none" "powerpcle-none" "riscv32-none" "riscv64-none" "rx-none"
     "s390-none" "s390x-none" "vc4-none" "x86_64-none"
 
diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix
index 45b95c150fce..8d9c09561ddb 100644
--- a/nixpkgs/lib/systems/examples.nix
+++ b/nixpkgs/lib/systems/examples.nix
@@ -37,6 +37,10 @@ rec {
     config = "armv6l-unknown-linux-gnueabihf";
   } // platforms.raspberrypi;
 
+  bluefield2 = {
+    config = "aarch64-unknown-linux-gnu";
+  } // platforms.bluefield2;
+
   remarkable1 = {
     config = "armv7l-unknown-linux-gnueabihf";
   } // platforms.zero-gravitas;
@@ -129,6 +133,16 @@ rec {
     libc = "newlib";
   };
 
+  mips64-embedded = {
+    config = "mips64-none-elf";
+    libc = "newlib";
+  };
+
+  mips-embedded = {
+    config = "mips-none-elf";
+    libc = "newlib";
+  };
+
   loongarch64-linux = {
     config = "loongarch64-unknown-linux-gnu";
   };
diff --git a/nixpkgs/lib/systems/inspect.nix b/nixpkgs/lib/systems/inspect.nix
index 89e9f4231d97..022e459c3945 100644
--- a/nixpkgs/lib/systems/inspect.nix
+++ b/nixpkgs/lib/systems/inspect.nix
@@ -87,7 +87,7 @@ rec {
     isNone         = { kernel = kernels.none; };
 
     isAndroid      = [ { abi = abis.android; } { abi = abis.androideabi; } ];
-    isGnu          = with abis; map (a: { abi = a; }) [ gnuabi64 gnu gnueabi gnueabihf gnuabielfv1 gnuabielfv2 ];
+    isGnu          = with abis; map (a: { abi = a; }) [ gnuabi64 gnuabin32 gnu gnueabi gnueabihf gnuabielfv1 gnuabielfv2 ];
     isMusl         = with abis; map (a: { abi = a; }) [ musl musleabi musleabihf muslabin32 muslabi64 ];
     isUClibc       = with abis; map (a: { abi = a; }) [ uclibc uclibceabi uclibceabihf ];
 
diff --git a/nixpkgs/lib/systems/platforms.nix b/nixpkgs/lib/systems/platforms.nix
index d574943e47df..d2e8f77bec03 100644
--- a/nixpkgs/lib/systems/platforms.nix
+++ b/nixpkgs/lib/systems/platforms.nix
@@ -209,6 +209,13 @@ rec {
   # Legacy attribute, for compatibility with existing configs only.
   raspberrypi2 = armv7l-hf-multiplatform;
 
+  # Nvidia Bluefield 2 (w. crypto support)
+  bluefield2 = {
+    gcc = {
+      arch = "armv8-a+fp+simd+crc+crypto";
+    };
+  };
+
   zero-gravitas = {
     linux-kernel = {
       name = "zero-gravitas";
diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix
index ce980436c1bc..887ea39a080d 100644
--- a/nixpkgs/lib/tests/misc.nix
+++ b/nixpkgs/lib/tests/misc.nix
@@ -1,6 +1,18 @@
-# to run these tests:
-# nix-instantiate --eval --strict nixpkgs/lib/tests/misc.nix
-# if the resulting list is empty, all tests passed
+/*
+Nix evaluation tests for various lib functions.
+
+Since these tests are implemented with Nix evaluation, error checking is limited to what `builtins.tryEval` can detect, which is `throw`'s and `abort`'s, without error messages.
+If you need to test error messages or more complex evaluations, see ./modules.sh, ./sources.sh or ./filesystem.sh as examples.
+
+To run these tests:
+
+  [nixpkgs]$ nix-instantiate --eval --strict lib/tests/misc.nix
+
+If the resulting list is empty, all tests passed.
+Alternatively, to run all `lib` tests:
+
+  [nixpkgs]$ nix-build lib/tests/release.nix
+*/
 with import ../default.nix;
 
 let
@@ -488,6 +500,39 @@ runTests {
     expected = { a = [ 2 3 ]; b = [7]; c = [8];};
   };
 
+  testListCommonPrefixExample1 = {
+    expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
+    expected = [ 1 2 ];
+  };
+  testListCommonPrefixExample2 = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
+    expected = [ 1 2 3 ];
+  };
+  testListCommonPrefixExample3 = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
+    expected = [ ];
+  };
+  testListCommonPrefixEmpty = {
+    expr = lists.commonPrefix [ ] [ 1 2 3 ];
+    expected = [ ];
+  };
+  testListCommonPrefixSame = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
+    expected = [ 1 2 3 ];
+  };
+  testListCommonPrefixLazy = {
+    expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
+    expected = [ 1 ];
+  };
+  # This would stack overflow if `commonPrefix` were implemented using recursion
+  testListCommonPrefixLong =
+    let
+      longList = genList (n: n) 100000;
+    in {
+      expr = lists.commonPrefix longList longList;
+      expected = longList;
+    };
+
   testSort = {
     expr = sort builtins.lessThan [ 40 2 30 42 ];
     expected = [2 30 40 42];
@@ -518,45 +563,55 @@ runTests {
     expected = false;
   };
 
-  testFindFirstExample1 = {
-    expr = findFirst (x: x > 3) 7 [ 1 6 4 ];
-    expected = 6;
+  testFindFirstIndexExample1 = {
+    expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
+    expected = 1;
   };
 
-  testFindFirstExample2 = {
-    expr = findFirst (x: x > 9) 7 [ 1 6 4 ];
-    expected = 7;
+  testFindFirstIndexExample2 = {
+    expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
+    expected = "a very specific default";
   };
 
-  testFindFirstEmpty = {
-    expr = findFirst (abort "when the list is empty, the predicate is not needed") null [];
+  testFindFirstIndexEmpty = {
+    expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
     expected = null;
   };
 
-  testFindFirstSingleMatch = {
-    expr = findFirst (x: x == 5) null [ 5 ];
-    expected = 5;
+  testFindFirstIndexSingleMatch = {
+    expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
+    expected = 0;
   };
 
-  testFindFirstSingleDefault = {
-    expr = findFirst (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
+  testFindFirstIndexSingleDefault = {
+    expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
     expected = null;
   };
 
-  testFindFirstNone = {
-    expr = builtins.tryEval (findFirst (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
+  testFindFirstIndexNone = {
+    expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
     expected = { success = false; value = false; };
   };
 
   # Makes sure that the implementation doesn't cause a stack overflow
-  testFindFirstBig = {
-    expr = findFirst (x: x == 1000000) null (range 0 1000000);
+  testFindFirstIndexBig = {
+    expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
     expected = 1000000;
   };
 
-  testFindFirstLazy = {
-    expr = findFirst (x: x == 1) 7 [ 1 (abort "list elements after the match must not be evaluated") ];
-    expected = 1;
+  testFindFirstIndexLazy = {
+    expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
+    expected = 0;
+  };
+
+  testFindFirstExample1 = {
+    expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
+    expected = 6;
+  };
+
+  testFindFirstExample2 = {
+    expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
+    expected = 7;
   };
 
 # ATTRSETS
@@ -609,6 +664,31 @@ runTests {
     };
   };
 
+
+  testMergeAttrsListExample1 = {
+    expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
+    expected = { a = 0; b = 1; c = 2; d = 3; };
+  };
+  testMergeAttrsListExample2 = {
+    expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
+    expected = { a = 1; };
+  };
+  testMergeAttrsListExampleMany =
+    let
+      list = genList (n:
+        listToAttrs (genList (m:
+          let
+            # Integer divide n by two to create duplicate attributes
+            str = "halfn${toString (n / 2)}m${toString m}";
+          in
+          nameValuePair str str
+        ) 100)
+      ) 100;
+    in {
+      expr = attrsets.mergeAttrsList list;
+      expected = foldl' mergeAttrs { } list;
+    };
+
   # code from the example
   testRecursiveUpdateUntil = {
     expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {
diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh
index 7aebba6b589e..b933a24a57a1 100755
--- a/nixpkgs/lib/tests/modules.sh
+++ b/nixpkgs/lib/tests/modules.sh
@@ -1,7 +1,13 @@
 #!/usr/bin/env bash
-#
+
 # This script is used to test that the module system is working as expected.
+# Executing it runs tests for `lib.modules`, `lib.options` and `lib.types`.
 # By default it test the version of nixpkgs which is defined in the NIX_PATH.
+#
+# Run:
+# [nixpkgs]$ lib/tests/modules.sh
+# or:
+# [nixpkgs]$ nix-build lib/tests/release.nix
 
 set -o errexit -o noclobber -o nounset -o pipefail
 shopt -s failglob inherit_errexit
@@ -61,6 +67,40 @@ checkConfigError() {
 # Shorthand meta attribute does not duplicate the config
 checkConfigOutput '^"one two"$' config.result ./shorthand-meta.nix
 
+checkConfigOutput '^true$' config.result ./test-mergeAttrDefinitionsWithPrio.nix
+
+# Check that a module argument is passed, also when a default is available
+# (but not needed)
+#
+# When the default is needed, we currently fail to do what the users expect, as
+# we pass our own argument anyway, even if it *turns out* not to exist.
+#
+# The reason for this is that we don't know at invocation time what is in the
+# _module.args option. That value is only available *after* all modules have been
+# invoked.
+#
+# Hypothetically, Nix could help support this by giving access to the default
+# values, through a new built-in function.
+# However the default values are allowed to depend on other arguments, so those
+# would have to be passed in somehow, making this not just a getter but
+# something more complicated.
+#
+# At that point we have to wonder whether the extra complexity is worth the cost.
+# Another - subjective - reason not to support it is that default values
+# contradict the notion that an option has a single value, where _module.args
+# is the option.
+checkConfigOutput '^true$' config.result ./module-argument-default.nix
+
+# types.pathInStore
+checkConfigOutput '".*/store/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv"' config.pathInStore.ok1 ./types.nix
+checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15"' config.pathInStore.ok2 ./types.nix
+checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15/bin/bash"' config.pathInStore.ok3 ./types.nix
+checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ""' config.pathInStore.bad1 ./types.nix
+checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store"' config.pathInStore.bad2 ./types.nix
+checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store/"' config.pathInStore.bad3 ./types.nix
+checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: ".*/store/.links"' config.pathInStore.bad4 ./types.nix
+checkConfigError 'A definition for option .* is not of type .path in the Nix store.. Definition values:\n\s*- In .*: "/foo/bar"' config.pathInStore.bad5 ./types.nix
+
 # Check boolean option.
 checkConfigOutput '^false$' config.enable ./declare-enable.nix
 checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix
@@ -195,7 +235,7 @@ checkConfigOutput '^"foo"$' config.submodule.foo ./declare-submoduleWith-special
 ## 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 "You're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
+checkConfigError "In module ..*define-submoduleWith-shorthand.nix., you're trying to define a value of type \`bool'\n\s*rather 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
@@ -353,6 +393,9 @@ checkConfigError \
   config.set \
   ./declare-set.nix ./declare-enable-nested.nix
 
+# Check that that merging of option collisions doesn't depend on type being set
+checkConfigError 'The option .group..*would be a parent of the following options, but its type .<no description>. does not support nested options.\n\s*- option.s. with prefix .group.enable..*' config.group.enable ./merge-typeless-option.nix
+
 # Test that types.optionType merges types correctly
 checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
 checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix
diff --git a/nixpkgs/lib/tests/modules/merge-typeless-option.nix b/nixpkgs/lib/tests/modules/merge-typeless-option.nix
new file mode 100644
index 000000000000..627d90b15db2
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/merge-typeless-option.nix
@@ -0,0 +1,25 @@
+{ lib, ... }:
+
+let
+  typeless =
+    { lib, ... }:
+
+    {
+      options.group = lib.mkOption { };
+    };
+  childOfTypeless =
+    { lib, ... }:
+
+    {
+      options.group.enable = lib.mkEnableOption "nothing";
+    };
+in
+
+{
+  imports = [
+    typeless
+    childOfTypeless
+  ];
+
+  config.group.enable = false;
+}
diff --git a/nixpkgs/lib/tests/modules/module-argument-default.nix b/nixpkgs/lib/tests/modules/module-argument-default.nix
new file mode 100644
index 000000000000..8dbb783e2df1
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/module-argument-default.nix
@@ -0,0 +1,9 @@
+{ a ? false, lib, ... }: {
+  options = {
+    result = lib.mkOption {};
+  };
+  config = {
+    _module.args.a = true;
+    result = a;
+  };
+}
diff --git a/nixpkgs/lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix b/nixpkgs/lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix
new file mode 100644
index 000000000000..3233f4151368
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/test-mergeAttrDefinitionsWithPrio.nix
@@ -0,0 +1,21 @@
+{ lib, options, ... }:
+
+let
+  defs = lib.modules.mergeAttrDefinitionsWithPrio options._module.args;
+  assertLazy = pos: throw "${pos.file}:${toString pos.line}:${toString pos.column}: The test must not evaluate this the assertLazy thunk, but it did. Unexpected strictness leads to unexpected errors and performance problems.";
+in
+
+{
+  options.result = lib.mkOption { };
+  config._module.args = {
+    default = lib.mkDefault (assertLazy __curPos);
+    regular = null;
+    force = lib.mkForce (assertLazy __curPos);
+    unused = assertLazy __curPos;
+  };
+  config.result =
+    assert defs.default.highestPrio == (lib.mkDefault (assertLazy __curPos)).priority;
+    assert defs.regular.highestPrio == lib.modules.defaultOverridePriority;
+    assert defs.force.highestPrio == (lib.mkForce (assertLazy __curPos)).priority;
+    true;
+}
diff --git a/nixpkgs/lib/tests/modules/types.nix b/nixpkgs/lib/tests/modules/types.nix
new file mode 100644
index 000000000000..7c43a6819e0e
--- /dev/null
+++ b/nixpkgs/lib/tests/modules/types.nix
@@ -0,0 +1,24 @@
+{ lib, ... }:
+let
+  inherit (builtins)
+    storeDir;
+  inherit (lib)
+    types
+    mkOption
+    ;
+in
+{
+  options = {
+    pathInStore = mkOption { type = types.lazyAttrsOf types.pathInStore; };
+  };
+  config = {
+    pathInStore.ok1 = "${storeDir}/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv";
+    pathInStore.ok2 = "${storeDir}/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15";
+    pathInStore.ok3 = "${storeDir}/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15/bin/bash";
+    pathInStore.bad1 = "";
+    pathInStore.bad2 = "${storeDir}";
+    pathInStore.bad3 = "${storeDir}/";
+    pathInStore.bad4 = "${storeDir}/.links"; # technically true, but not reasonable
+    pathInStore.bad5 = "/foo/bar";
+  };
+}
diff --git a/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix
index c3bf58db241f..805f7a7e95d6 100644
--- a/nixpkgs/lib/tests/release.nix
+++ b/nixpkgs/lib/tests/release.nix
@@ -38,9 +38,6 @@ let
       export PAGER=cat
       cacheDir=$TEST_ROOT/binary-cache
 
-      mkdir -p $NIX_CONF_DIR
-      echo "experimental-features = nix-command" >> $NIX_CONF_DIR/nix.conf
-
       nix-store --init
 
       cp -r ${../.} lib
@@ -53,6 +50,9 @@ let
       echo "Running lib/tests/sources.sh"
       TEST_LIB=$PWD/lib bash lib/tests/sources.sh
 
+      echo "Running lib/tests/systems.nix"
+      [[ $(nix-instantiate --eval --strict lib/tests/systems.nix | tee /dev/stderr) == '[ ]' ]];
+
       mkdir $out
       echo success > $out/${nix.version}
     '';
diff --git a/nixpkgs/lib/tests/sources.sh b/nixpkgs/lib/tests/sources.sh
index cda77aa96b28..079c7eea5657 100755
--- a/nixpkgs/lib/tests/sources.sh
+++ b/nixpkgs/lib/tests/sources.sh
@@ -1,4 +1,11 @@
 #!/usr/bin/env bash
+
+# Tests lib/sources.nix
+# Run:
+# [nixpkgs]$ lib/tests/sources.sh
+# or:
+# [nixpkgs]$ nix-build lib/tests/release.nix
+
 set -euo pipefail
 shopt -s inherit_errexit
 
diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix
index 2afe128c4208..e142ff307fbd 100644
--- a/nixpkgs/lib/tests/systems.nix
+++ b/nixpkgs/lib/tests/systems.nix
@@ -1,24 +1,47 @@
-# We assert that the new algorithmic way of generating these lists matches the
-# way they were hard-coded before.
+# Run:
+# [nixpkgs]$ nix-instantiate --eval --strict lib/tests/systems.nix
+# Expected output: [], or the failed cases
 #
-# One might think "if we exhaustively test, what's the point of procedurally
-# calculating the lists anyway?". The answer is one can mindlessly update these
-# tests as new platforms become supported, and then just give the diff a quick
-# sanity check before committing :).
+# OfBorg runs (approximately) nix-build lib/tests/release.nix
 let
   lib = import ../default.nix;
   mseteq = x: y: {
     expr     = lib.sort lib.lessThan x;
     expected = lib.sort lib.lessThan y;
   };
+
+  /*
+    Try to convert an elaborated system back to a simple string. If not possible,
+    return null. So we have the property:
+
+        sys: _valid_ sys ->
+          sys == elaborate (toLosslessStringMaybe sys)
+
+    NOTE: This property is not guaranteed when `sys` was elaborated by a different
+          version of Nixpkgs.
+  */
+  toLosslessStringMaybe = sys:
+    if lib.isString sys then sys
+    else if lib.systems.equals sys (lib.systems.elaborate sys.system) then sys.system
+    else null;
+
 in
-with lib.systems.doubles; lib.runTests {
+lib.runTests (
+# We assert that the new algorithmic way of generating these lists matches the
+# way they were hard-coded before.
+#
+# One might think "if we exhaustively test, what's the point of procedurally
+# calculating the lists anyway?". The answer is one can mindlessly update these
+# tests as new platforms become supported, and then just give the diff a quick
+# sanity check before committing :).
+
+(with lib.systems.doubles; {
   testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox);
 
   testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ];
   testarmv7 = mseteq armv7 [ "armv7a-darwin" "armv7a-linux" "armv7l-linux" "armv7a-netbsd" "armv7l-netbsd" ];
   testi686 = mseteq i686 [ "i686-linux" "i686-freebsd13" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ];
-  testmips = mseteq mips [ "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "mipsel-netbsd" ];
+  testmips = mseteq mips [ "mips-none" "mips64-none" "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "mipsel-netbsd" ];
   testmmix = mseteq mmix [ "mmix-mmixware" ];
   testpower = mseteq power [ "powerpc-netbsd" "powerpc-none" "powerpc64-linux" "powerpc64le-linux" "powerpcle-none" ];
   testriscv = mseteq riscv [ "riscv32-linux" "riscv64-linux" "riscv32-netbsd" "riscv64-netbsd" "riscv32-none" "riscv64-none" ];
@@ -39,4 +62,44 @@ with lib.systems.doubles; lib.runTests {
   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);
+})
+
+// {
+  test_equals_example_x86_64-linux = {
+    expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") (lib.systems.elaborate "x86_64-linux");
+    expected = true;
+  };
+
+  test_toLosslessStringMaybe_example_x86_64-linux = {
+    expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux");
+    expected = "x86_64-linux";
+  };
+  test_toLosslessStringMaybe_fail = {
+    expr = toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux" // { something = "extra"; });
+    expected = null;
+  };
 }
+
+# Generate test cases to assert that a change in any non-function attribute makes a platform unequal
+// lib.concatMapAttrs (platformAttrName: origValue: {
+
+  ${"test_equals_unequal_${platformAttrName}"} =
+    let modified =
+          assert origValue != arbitraryValue;
+          lib.systems.elaborate "x86_64-linux" // { ${platformAttrName} = arbitraryValue; };
+        arbitraryValue = x: "<<modified>>";
+    in {
+      expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") modified;
+      expected = {
+        # Changes in these attrs are not detectable because they're function.
+        # The functions should be derived from the data, so this is not a problem.
+        canExecute = null;
+        emulator = null;
+        emulatorAvailable = null;
+        isCompatible = null;
+      }?${platformAttrName};
+    };
+
+}) (lib.systems.elaborate "x86_64-linux" /* arbitrary choice, just to get all the elaborated attrNames */)
+
+)
diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix
index 26e4b32400f2..34c100959e6f 100644
--- a/nixpkgs/lib/trivial.nix
+++ b/nixpkgs/lib/trivial.nix
@@ -179,7 +179,7 @@ rec {
      they take effect as soon as the oldest release reaches end of life. */
   oldestSupportedRelease =
     # Update on master only. Do not backport.
-    2211;
+    2305;
 
   /* Whether a feature is supported in all supported releases (at the time of
      release branch-off, if applicable). See `oldestSupportedRelease`. */
diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix
index 9360d42f5850..ddd37f260c9a 100644
--- a/nixpkgs/lib/types.nix
+++ b/nixpkgs/lib/types.nix
@@ -211,7 +211,7 @@ rec {
   # nixos/doc/manual/development/option-types.xml!
   types = rec {
 
-    raw = mkOptionType rec {
+    raw = mkOptionType {
       name = "raw";
       description = "raw value";
       descriptionClass = "noun";
@@ -461,6 +461,7 @@ rec {
     # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
     # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
     #   ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
+    # If you don't need a *top-level* store path, consider using pathInStore instead.
     package = mkOptionType {
       name = "package";
       descriptionClass = "noun";
@@ -491,6 +492,14 @@ rec {
       merge = mergeEqualOption;
     };
 
+    pathInStore = mkOptionType {
+      name = "pathInStore";
+      description = "path in the Nix store";
+      descriptionClass = "noun";
+      check = x: isStringLike x && builtins.match "${builtins.storeDir}/[^.].*" (toString x) != null;
+      merge = mergeEqualOption;
+    };
+
     listOf = elemType: mkOptionType rec {
       name = "listOf";
       description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";