From 374e6bcc403e02a35e07b650463c01a52b13a7c8 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Tue, 1 Apr 2025 20:10:43 +0200 Subject: treewide: Format all Nix files Format all Nix files using the officially approved formatter, making the CI check introduced in the previous commit succeed: nix-build ci -A fmt.check This is the next step of the of the [implementation](https://github.com/NixOS/nixfmt/issues/153) of the accepted [RFC 166](https://github.com/NixOS/rfcs/pull/166). This commit will lead to merge conflicts for a number of PRs, up to an estimated ~1100 (~33%) among the PRs with activity in the past 2 months, but that should be lower than what it would be without the previous [partial treewide format](https://github.com/NixOS/nixpkgs/pull/322537). Merge conflicts caused by this commit can now automatically be resolved while rebasing using the [auto-rebase script](https://github.com/NixOS/nixpkgs/tree/8616af08d915377bd930395f3b700a0e93d08728/maintainers/scripts/auto-rebase). If you run into any problems regarding any of this, please reach out to the [formatting team](https://nixos.org/community/teams/formatting/) by pinging @NixOS/nix-formatting. --- lib/attrsets.nix | 593 ++++----- lib/default.nix | 766 ++++++++--- lib/deprecated/misc.nix | 506 ++++--- lib/filesystem.nix | 188 +-- lib/generators.nix | 622 +++++---- lib/licenses.nix | 2900 +++++++++++++++++++++-------------------- lib/lists.nix | 380 +++--- lib/meta.nix | 138 +- lib/modules.nix | 1633 +++++++++++++---------- lib/options.nix | 415 +++--- lib/strings-with-deps.nix | 64 +- lib/strings.nix | 759 ++++++----- lib/systems/architectures.nix | 496 +++++-- lib/systems/default.nix | 836 ++++++------ lib/systems/doubles.nix | 215 ++- lib/systems/examples.nix | 48 +- lib/systems/inspect.nix | 509 ++++++-- lib/systems/parse.nix | 998 +++++++++----- lib/tests/misc.nix | 2705 ++++++++++++++++++++++++++++---------- lib/tests/modules/types.nix | 3 +- lib/tests/release.nix | 24 +- lib/tests/systems.nix | 342 +++-- lib/trivial.nix | 269 ++-- lib/types.nix | 2353 ++++++++++++++++++--------------- 24 files changed, 10831 insertions(+), 6931 deletions(-) (limited to 'lib') diff --git a/lib/attrsets.nix b/lib/attrsets.nix index 180c585961d7..8482887023f7 100644 --- a/lib/attrsets.nix +++ b/lib/attrsets.nix @@ -5,14 +5,42 @@ let inherit (builtins) head length; - inherit (lib.trivial) oldestSupportedReleaseIsAtLeast mergeAttrs warn warnIf; - inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName; - inherit (lib.lists) filter foldr foldl' concatMap elemAt all partition groupBy take foldl; + inherit (lib.trivial) + oldestSupportedReleaseIsAtLeast + mergeAttrs + warn + warnIf + ; + inherit (lib.strings) + concatStringsSep + concatMapStringsSep + escapeNixIdentifier + sanitizeDerivationName + ; + inherit (lib.lists) + filter + foldr + foldl' + concatMap + elemAt + all + partition + groupBy + take + foldl + ; in rec { - inherit (builtins) attrNames listToAttrs hasAttr isAttrs getAttr removeAttrs intersectAttrs; - + inherit (builtins) + attrNames + listToAttrs + hasAttr + isAttrs + getAttr + removeAttrs + intersectAttrs + ; /** Return an attribute from nested attribute sets. @@ -25,7 +53,6 @@ rec { (x.${f p}."example.com" or 6) == attrByPath [ (f p) "example.com" ] 6 x ``` - # Inputs `attrPath` @@ -63,23 +90,24 @@ rec { ::: */ attrByPath = - attrPath: - default: - set: + attrPath: default: set: let lenAttrPath = length attrPath; - attrByPath' = n: s: ( - if n == lenAttrPath then s - else ( - let - attr = elemAt attrPath n; - in - if s ? ${attr} then attrByPath' (n + 1) s.${attr} - else default - ) - ); + attrByPath' = + n: s: + ( + if n == lenAttrPath then + s + else + ( + let + attr = elemAt attrPath n; + in + if s ? ${attr} then attrByPath' (n + 1) s.${attr} else default + ) + ); in - attrByPath' 0 set; + attrByPath' 0 set; /** Return if an attribute from nested attribute set exists. @@ -97,7 +125,6 @@ rec { hasAttrByPath [] x == true ``` - # Inputs `attrPath` @@ -131,21 +158,22 @@ rec { ::: */ hasAttrByPath = - attrPath: - e: + attrPath: e: let lenAttrPath = length attrPath; - hasAttrByPath' = n: s: ( - n == lenAttrPath || ( - let - attr = elemAt attrPath n; - in - if s ? ${attr} then hasAttrByPath' (n + 1) s.${attr} - else false - ) - ); + hasAttrByPath' = + n: s: + ( + n == lenAttrPath + || ( + let + attr = elemAt attrPath n; + in + if s ? ${attr} then hasAttrByPath' (n + 1) s.${attr} else false + ) + ); in - hasAttrByPath' 0 e; + hasAttrByPath' 0 e; /** Return the longest prefix of an attribute path that refers to an existing attribute in a nesting of attribute sets. @@ -164,7 +192,6 @@ rec { hasAttrByPath (attrsets.longestValidPathPrefix p x) x == true ``` - # Inputs `attrPath` @@ -200,8 +227,7 @@ rec { ::: */ longestValidPathPrefix = - attrPath: - v: + attrPath: v: let lenAttrPath = length attrPath; getPrefixForSetAtIndex = @@ -212,29 +238,27 @@ rec { # the length of the prefix we've already checked. remainingPathIndex: - if remainingPathIndex == lenAttrPath then - # All previously checked attributes exist, and no attr names left, - # so we return the whole path. - attrPath + if remainingPathIndex == lenAttrPath then + # All previously checked attributes exist, and no attr names left, + # so we return the whole path. + attrPath + else + let + attr = elemAt attrPath remainingPathIndex; + in + if remainingSet ? ${attr} then + getPrefixForSetAtIndex remainingSet.${attr} # advance from the set to the attribute value + (remainingPathIndex + 1) # advance the path else - let - attr = elemAt attrPath remainingPathIndex; - in - if remainingSet ? ${attr} then - getPrefixForSetAtIndex - remainingSet.${attr} # advance from the set to the attribute value - (remainingPathIndex + 1) # advance the path - else - # The attribute doesn't exist, so we return the prefix up to the - # previously checked length. - take remainingPathIndex attrPath; + # The attribute doesn't exist, so we return the prefix up to the + # previously checked length. + take remainingPathIndex attrPath; in - getPrefixForSetAtIndex v 0; + getPrefixForSetAtIndex v 0; /** Create a new attribute set with `value` set at the nested attribute location specified in `attrPath`. - # Inputs `attrPath` @@ -263,15 +287,12 @@ rec { ::: */ setAttrByPath = - attrPath: - value: + attrPath: value: let len = length attrPath; - atDepth = n: - if n == len - then value - else { ${elemAt attrPath n} = atDepth (n + 1); }; - in atDepth 0; + atDepth = n: if n == len then value else { ${elemAt attrPath n} = atDepth (n + 1); }; + in + atDepth 0; /** Like `attrByPath`, but without a default value. If it doesn't find the @@ -285,7 +306,6 @@ rec { x.${f p}."example.com" == getAttrByPath [ (f p) "example.com" ] x ``` - # Inputs `attrPath` @@ -317,14 +337,12 @@ rec { ::: */ getAttrFromPath = - attrPath: - set: + attrPath: set: attrByPath attrPath (abort ("cannot find attribute '" + concatStringsSep "." attrPath + "'")) set; /** Map each attribute in the given set and merge them into a new attribute set. - # Inputs `f` @@ -357,12 +375,7 @@ rec { ::: */ - concatMapAttrs = f: v: - foldl' mergeAttrs { } - (attrValues - (mapAttrs f v) - ); - + concatMapAttrs = f: v: foldl' mergeAttrs { } (attrValues (mapAttrs f v)); /** Update or set specific paths of an attribute set. @@ -420,69 +433,78 @@ rec { ::: */ - updateManyAttrsByPath = let - # When recursing into attributes, instead of updating the `path` of each - # update using `tail`, which needs to allocate an entirely new list, - # we just pass a prefix length to use and make sure to only look at the - # path without the prefix length, so that we can reuse the original list - # entries. - go = prefixLength: hasValue: value: updates: - let - # Splits updates into ones on this level (split.right) - # And ones on levels further down (split.wrong) - split = partition (el: length el.path == prefixLength) updates; - - # Groups updates on further down levels into the attributes they modify - nested = groupBy (el: elemAt el.path prefixLength) split.wrong; - - # Applies only nested modification to the input value - withNestedMods = - # Return the value directly if we don't have any nested modifications - if split.wrong == [] then - if hasValue then value + updateManyAttrsByPath = + let + # When recursing into attributes, instead of updating the `path` of each + # update using `tail`, which needs to allocate an entirely new list, + # we just pass a prefix length to use and make sure to only look at the + # path without the prefix length, so that we can reuse the original list + # entries. + go = + prefixLength: hasValue: value: updates: + let + # Splits updates into ones on this level (split.right) + # And ones on levels further down (split.wrong) + split = partition (el: length el.path == prefixLength) updates; + + # Groups updates on further down levels into the attributes they modify + nested = groupBy (el: elemAt el.path prefixLength) split.wrong; + + # Applies only nested modification to the input value + withNestedMods = + # Return the value directly if we don't have any nested modifications + if split.wrong == [ ] then + if hasValue then + value + else + # Throw an error if there is no value. This `head` call here is + # safe, but only in this branch since `go` could only be called + # with `hasValue == false` for nested updates, in which case + # it's also always called with at least one update + let + updatePath = (head split.right).path; + in + throw ( + "updateManyAttrsByPath: Path '${showAttrPath updatePath}' does " + + "not exist in the given value, but the first update to this " + + "path tries to access the existing value." + ) else - # Throw an error if there is no value. This `head` call here is - # safe, but only in this branch since `go` could only be called - # with `hasValue == false` for nested updates, in which case - # it's also always called with at least one update - let updatePath = (head split.right).path; in - throw - ( "updateManyAttrsByPath: Path '${showAttrPath updatePath}' does " - + "not exist in the given value, but the first update to this " - + "path tries to access the existing value.") - else # If there are nested modifications, try to apply them to the value - if ! hasValue then + if !hasValue then # But if we don't have a value, just use an empty attribute set # as the value, but simplify the code a bit mapAttrs (name: go (prefixLength + 1) false null) nested else if isAttrs value then # If we do have a value and it's an attribute set, override it # with the nested modifications - value // - mapAttrs (name: go (prefixLength + 1) (value ? ${name}) value.${name}) nested + value // mapAttrs (name: go (prefixLength + 1) (value ? ${name}) value.${name}) nested else # However if it's not an attribute set, we can't apply the nested # modifications, throw an error - let updatePath = (head split.wrong).path; in - throw - ( "updateManyAttrsByPath: Path '${showAttrPath updatePath}' needs to " - + "be updated, but path '${showAttrPath (take prefixLength updatePath)}' " - + "of the given value is not an attribute set, so we can't " - + "update an attribute inside of it."); - - # We get the final result by applying all the updates on this level - # after having applied all the nested updates - # We use foldl instead of foldl' so that in case of multiple updates, - # intermediate values aren't evaluated if not needed - in foldl (acc: el: el.update acc) withNestedMods split.right; + let + updatePath = (head split.wrong).path; + in + throw ( + "updateManyAttrsByPath: Path '${showAttrPath updatePath}' needs to " + + "be updated, but path '${showAttrPath (take prefixLength updatePath)}' " + + "of the given value is not an attribute set, so we can't " + + "update an attribute inside of it." + ); + + # We get the final result by applying all the updates on this level + # after having applied all the nested updates + # We use foldl instead of foldl' so that in case of multiple updates, + # intermediate values aren't evaluated if not needed + in + foldl (acc: el: el.update acc) withNestedMods split.right; - in updates: value: go 0 true value updates; + in + updates: value: go 0 true value updates; /** Return the specified attributes from a set. - # Inputs `nameList` @@ -510,10 +532,7 @@ rec { ::: */ - attrVals = - nameList: - set: map (x: set.${x}) nameList; - + attrVals = nameList: set: map (x: set.${x}) nameList; /** Return the values of all attributes in the given set, sorted by @@ -538,12 +557,10 @@ rec { */ attrValues = builtins.attrValues; - /** Given a set of attribute names, return the set of the corresponding attributes from the given set. - # Inputs `names` @@ -571,9 +588,7 @@ rec { ::: */ - getAttrs = - names: - attrs: genAttrs names (name: attrs.${name}); + getAttrs = names: attrs: genAttrs names (name: attrs.${name}); /** Collect each attribute named `attr` from a list of attribute @@ -608,12 +623,10 @@ rec { */ catAttrs = builtins.catAttrs; - /** Filter an attribute set by removing all attributes for which the given predicate return false. - # Inputs `pred` @@ -641,16 +654,12 @@ rec { ::: */ - filterAttrs = - pred: - set: - removeAttrs set (filter (name: ! pred name set.${name}) (attrNames set)); + filterAttrs = pred: set: removeAttrs set (filter (name: !pred name set.${name}) (attrNames set)); /** Filter an attribute set recursively by removing all attributes for which the given predicate return false. - # Inputs `pred` @@ -679,21 +688,23 @@ rec { ::: */ filterAttrsRecursive = - pred: - set: + pred: set: listToAttrs ( - concatMap (name: - let v = set.${name}; in - if pred name v then [ - (nameValuePair name ( - if isAttrs v then filterAttrsRecursive pred v - else v - )) - ] else [] + concatMap ( + name: + let + v = set.${name}; + in + if pred name v then + [ + (nameValuePair name (if isAttrs v then filterAttrsRecursive pred v else v)) + ] + else + [ ] ) (attrNames set) ); - /** + /** Like [`lib.lists.foldl'`](#function-library-lib.lists.foldl-prime) but for attribute sets. Iterates over every name-value pair in the given attribute set. The result of the callback function is often called `acc` for accumulator. It is passed between callbacks from left to right and the final `acc` is the return value of `foldlAttrs`. @@ -703,7 +714,6 @@ rec { There is a completely different function `lib.foldAttrs` which has nothing to do with this function, despite the similar name. - # Inputs `f` @@ -772,16 +782,13 @@ rec { ::: */ - foldlAttrs = f: init: set: - foldl' - (acc: name: f acc name set.${name}) - init - (attrNames set); + foldlAttrs = + f: init: set: + foldl' (acc: name: f acc name set.${name}) init (attrNames set); /** Apply fold functions to values grouped by key. - # Inputs `op` @@ -814,22 +821,16 @@ rec { ::: */ foldAttrs = - op: - nul: - list_of_attrs: - foldr (n: a: - foldr (name: o: - o // { ${name} = op n.${name} (a.${name} or nul); } - ) a (attrNames n) - ) {} list_of_attrs; - + op: nul: list_of_attrs: + foldr ( + n: a: foldr (name: o: o // { ${name} = op n.${name} (a.${name} or nul); }) a (attrNames n) + ) { } list_of_attrs; /** Recursively collect sets that verify a given predicate named `pred` from the set `attrs`. The recursion is stopped when the predicate is verified. - # Inputs `pred` @@ -862,19 +863,17 @@ rec { ::: */ collect = - pred: - attrs: + pred: attrs: if pred attrs then [ attrs ] else if isAttrs attrs then concatMap (collect pred) (attrValues attrs) else - []; + [ ]; /** Return the cartesian product of attribute set value combinations. - # Inputs `attrsOfLists` @@ -905,12 +904,12 @@ rec { */ cartesianProduct = attrsOfLists: - foldl' (listOfAttrs: attrName: - concatMap (attrs: - map (listValue: attrs // { ${attrName} = listValue; }) attrsOfLists.${attrName} + foldl' ( + listOfAttrs: attrName: + concatMap ( + attrs: map (listValue: attrs // { ${attrName} = listValue; }) attrsOfLists.${attrName} ) listOfAttrs - ) [{}] (attrNames attrsOfLists); - + ) [ { } ] (attrNames attrsOfLists); /** Return the result of function f applied to the cartesian product of attribute set value combinations. @@ -942,14 +941,12 @@ rec { ``` ::: - */ mapCartesianProduct = f: attrsOfLists: map f (cartesianProduct attrsOfLists); /** Utility function that creates a `{name, value}` pair as expected by `builtins.listToAttrs`. - # Inputs `name` @@ -977,11 +974,7 @@ rec { ::: */ - nameValuePair = - name: - value: - { inherit name value; }; - + nameValuePair = name: value: { inherit name value; }; /** Apply a function to each element in an attribute set, creating a new attribute set. @@ -1016,13 +1009,11 @@ rec { */ mapAttrs = builtins.mapAttrs; - /** Like `mapAttrs`, but allows the name of each attribute to be changed in addition to the value. The applied function should return both the new name and value as a `nameValuePair`. - # Inputs `f` @@ -1051,11 +1042,7 @@ rec { ::: */ - mapAttrs' = - f: - set: - listToAttrs (map (attr: f attr set.${attr}) (attrNames set)); - + mapAttrs' = f: set: listToAttrs (map (attr: f attr set.${attr}) (attrNames set)); /** Call a function for each attribute in the given set and return @@ -1089,10 +1076,7 @@ rec { ::: */ - mapAttrsToList = - f: - attrs: - map (name: f name attrs.${name}) (attrNames attrs); + mapAttrsToList = f: attrs: map (name: f name attrs.${name}) (attrNames attrs); /** Deconstruct an attrset to a list of name-value pairs as expected by [`builtins.listToAttrs`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-listToAttrs). @@ -1139,7 +1123,6 @@ rec { */ attrsToList = mapAttrsToList nameValuePair; - /** Like `mapAttrs`, except that it recursively applies itself to the *leaf* attributes of a potentially-nested attribute set: the second argument of the function will never be an attrset. @@ -1165,11 +1148,7 @@ rec { mapAttrsRecursive :: ([String] -> a -> b) -> AttrSet -> AttrSet ``` */ - mapAttrsRecursive = - f: - set: - mapAttrsRecursiveCond (as: true) f set; - + mapAttrsRecursive = f: set: mapAttrsRecursiveCond (as: true) f set; /** Like `mapAttrsRecursive`, but it takes an additional predicate that tells it whether to recurse into an attribute set. @@ -1195,25 +1174,21 @@ rec { ``` */ mapAttrsRecursiveCond = - cond: - f: - set: + cond: f: set: let - recurse = path: - mapAttrs - (name: value: - if isAttrs value && cond value - then recurse (path ++ [ name ]) value - else f (path ++ [ name ]) value); + recurse = + path: + mapAttrs ( + name: value: + if isAttrs value && cond value then recurse (path ++ [ name ]) value else f (path ++ [ name ]) value + ); in recurse [ ] set; - /** Generate an attribute set by mapping a function over a list of attribute names. - # Inputs `names` @@ -1241,17 +1216,12 @@ rec { ::: */ - genAttrs = - names: - f: - listToAttrs (map (n: nameValuePair n (f n)) names); - + genAttrs = names: f: listToAttrs (map (n: nameValuePair n (f n)) names); /** Check whether the argument is a derivation. Any set with `{ type = "derivation"; }` counts as a derivation. - # Inputs `value` @@ -1278,13 +1248,11 @@ rec { ::: */ - isDerivation = - value: value.type or null == "derivation"; + isDerivation = value: value.type or null == "derivation"; - /** + /** Converts a store path to a fake derivation. - # Inputs `path` @@ -1297,26 +1265,25 @@ rec { toDerivation :: Path -> Derivation ``` */ - toDerivation = - path: - let - path' = builtins.storePath path; - res = - { type = "derivation"; - name = sanitizeDerivationName (builtins.substring 33 (-1) (baseNameOf path')); - outPath = path'; - outputs = [ "out" ]; - out = res; - outputName = "out"; - }; - in res; - + toDerivation = + path: + let + path' = builtins.storePath path; + res = { + type = "derivation"; + name = sanitizeDerivationName (builtins.substring 33 (-1) (baseNameOf path')); + outPath = path'; + outputs = [ "out" ]; + out = res; + outputName = "out"; + }; + in + res; /** If `cond` is true, return the attribute set `as`, otherwise an empty attribute set. - # Inputs `cond` @@ -1346,17 +1313,12 @@ rec { ::: */ - optionalAttrs = - cond: - as: - if cond then as else {}; - + optionalAttrs = cond: as: if cond then as else { }; /** Merge sets of attributes and use the function `f` to merge attributes values. - # Inputs `names` @@ -1389,14 +1351,13 @@ rec { ::: */ zipAttrsWithNames = - names: - f: - sets: - listToAttrs (map (name: { - inherit name; - value = f name (catAttrs name sets); - }) names); - + names: f: sets: + listToAttrs ( + map (name: { + inherit name; + value = f name (catAttrs name sets); + }) names + ); /** Merge sets of attributes and use the function f to merge attribute values. @@ -1427,7 +1388,6 @@ rec { zipAttrsWith = builtins.zipAttrsWith or (f: sets: zipAttrsWithNames (concatMap attrNames sets) f sets); - /** Merge sets of attributes and combine each attribute value in to a list. @@ -1458,7 +1418,6 @@ rec { 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). - # Inputs `list` @@ -1484,17 +1443,18 @@ rec { ::: */ - mergeAttrsList = list: + 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: + 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 + 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; @@ -1505,7 +1465,6 @@ rec { 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 @@ -1514,7 +1473,6 @@ rec { the predicate is satisfied, the value of the first attribute set is replaced by the value of the second attribute set. - # Inputs `pred` @@ -1563,20 +1521,22 @@ rec { ::: */ recursiveUpdateUntil = - pred: - lhs: - rhs: - let f = attrPath: - zipAttrsWith (n: values: - let here = attrPath ++ [n]; in - if length values == 1 - || pred here (elemAt values 1) (head values) then - head values - else - f here values - ); - in f [] [rhs lhs]; - + pred: lhs: rhs: + let + f = + attrPath: + zipAttrsWith ( + n: values: + let + here = attrPath ++ [ n ]; + in + if length values == 1 || pred here (elemAt values 1) (head values) then + head values + else + f here values + ); + in + f [ ] [ rhs lhs ]; /** A recursive variant of the update operator ‘//’. The recursion @@ -1584,7 +1544,6 @@ rec { in which case the right hand side value takes precedence over the left hand side value. - # Inputs `lhs` @@ -1622,17 +1581,17 @@ rec { ::: */ recursiveUpdate = - lhs: - rhs: - recursiveUpdateUntil (path: lhs: rhs: !(isAttrs lhs && isAttrs rhs)) lhs rhs; - + lhs: rhs: + recursiveUpdateUntil ( + path: lhs: rhs: + !(isAttrs lhs && isAttrs rhs) + ) lhs rhs; /** Recurse into every attribute set of the first argument and check that: - Each attribute path also exists in the second argument. - If the attribute's value is not a nested attribute set, it must have the same value in the right argument. - # Inputs `pattern` @@ -1661,30 +1620,27 @@ rec { ::: */ matchAttrs = - pattern: - attrs: + pattern: attrs: assert isAttrs pattern; - all - ( # Compare equality between `pattern` & `attrs`. + all ( + # Compare equality between `pattern` & `attrs`. attr: # Missing attr, not equal. - attrs ? ${attr} && ( + attrs ? ${attr} + && ( let lhs = pattern.${attr}; rhs = attrs.${attr}; in # If attrset check recursively - if isAttrs lhs then isAttrs rhs && matchAttrs lhs rhs - else lhs == rhs + if isAttrs lhs then isAttrs rhs && matchAttrs lhs rhs else lhs == rhs ) - ) - (attrNames pattern); + ) (attrNames pattern); /** Override only the attributes that are already present in the old set useful for deep-overriding. - # Inputs `old` @@ -1716,11 +1672,7 @@ rec { ::: */ - overrideExisting = - old: - new: - mapAttrs (name: value: new.${name} or value) old; - + overrideExisting = old: new: mapAttrs (name: value: new.${name} or value) old; /** Turns a list of strings into a human-readable description of those @@ -1728,7 +1680,6 @@ rec { not intended to be machine-readable. Create a new attribute set with `value` set at the nested attribute location specified in `attrPath`. - # Inputs `path` @@ -1756,16 +1707,13 @@ rec { */ showAttrPath = path: - if path == [] then "" - else concatMapStringsSep "." escapeNixIdentifier path; - + if path == [ ] then "" else concatMapStringsSep "." escapeNixIdentifier path; /** Get a package output. If no output is found, fallback to `.out` and then to the default. The function is idempotent: `getOutput "b" (getOutput "a" p) == getOutput "a" p`. - # Inputs `output` @@ -1793,10 +1741,9 @@ rec { ::: */ - getOutput = output: pkg: - if ! pkg ? outputSpecified || ! pkg.outputSpecified - then pkg.${output} or pkg.out or pkg - else pkg; + getOutput = + output: pkg: + if !pkg ? outputSpecified || !pkg.outputSpecified then pkg.${output} or pkg.out or pkg else pkg; /** Get the first of the `outputs` provided by the package, or the default. @@ -1836,10 +1783,7 @@ rec { outputs = builtins.filter (name: hasAttr name pkg) candidates; output = builtins.head outputs; in - if pkg.outputSpecified or false || outputs == [ ] then - pkg - else - pkg.${output}; + if pkg.outputSpecified or false || outputs == [ ] then pkg else pkg.${output}; /** Get a package's `bin` output. @@ -1870,7 +1814,6 @@ rec { */ getBin = getOutput "bin"; - /** Get a package's `lib` output. If the output does not exist, fallback to `.out` and then to the default. @@ -1927,8 +1870,11 @@ rec { ::: */ - getStatic = getFirstOutput [ "static" "lib" "out" ]; - + getStatic = getFirstOutput [ + "static" + "lib" + "out" + ]; /** Get a package's `dev` output. @@ -1986,8 +1932,11 @@ rec { ::: */ - getInclude = getFirstOutput [ "include" "dev" "out" ]; - + getInclude = getFirstOutput [ + "include" + "dev" + "out" + ]; /** Get a package's `man` output. @@ -2042,7 +1991,6 @@ rec { This function only affects a single attribute set; it does not apply itself recursively for nested attribute sets. - # Inputs `attrs` @@ -2070,14 +2018,11 @@ rec { ::: */ - recurseIntoAttrs = - attrs: - attrs // { recurseForDerivations = true; }; + recurseIntoAttrs = attrs: attrs // { recurseForDerivations = true; }; /** Undo the effect of recurseIntoAttrs. - # Inputs `attrs` @@ -2090,9 +2035,7 @@ rec { dontRecurseIntoAttrs :: AttrSet -> AttrSet ``` */ - dontRecurseIntoAttrs = - attrs: - attrs // { recurseForDerivations = false; }; + dontRecurseIntoAttrs = attrs: attrs // { recurseForDerivations = false; }; /** `unionOfDisjoint x y` is equal to `x // y // z` where the @@ -2100,7 +2043,6 @@ rec { `y`, and all values `assert` with an error message. This operator is commutative, unlike (//). - # Inputs `x` @@ -2117,25 +2059,26 @@ rec { unionOfDisjoint :: AttrSet -> AttrSet -> AttrSet ``` */ - unionOfDisjoint = x: y: + unionOfDisjoint = + x: y: let intersection = builtins.intersectAttrs x y; collisions = lib.concatStringsSep " " (builtins.attrNames intersection); - mask = builtins.mapAttrs (name: value: builtins.throw - "unionOfDisjoint: collision on ${name}; complete list: ${collisions}") - intersection; + mask = builtins.mapAttrs ( + name: value: builtins.throw "unionOfDisjoint: collision on ${name}; complete list: ${collisions}" + ) intersection; in - (x // y) // mask; + (x // y) // mask; # DEPRECATED - zipWithNames = warn - "lib.zipWithNames is a deprecated alias of lib.zipAttrsWithNames." zipAttrsWithNames; + zipWithNames = warn "lib.zipWithNames is a deprecated alias of lib.zipAttrsWithNames." zipAttrsWithNames; # DEPRECATED - zip = warn - "lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith; + zip = warn "lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith; # DEPRECATED - cartesianProductOfSets = warnIf (oldestSupportedReleaseIsAtLeast 2405) - "lib.cartesianProductOfSets is a deprecated alias of lib.cartesianProduct." cartesianProduct; + cartesianProductOfSets = + warnIf (oldestSupportedReleaseIsAtLeast 2405) + "lib.cartesianProductOfSets is a deprecated alias of lib.cartesianProduct." + cartesianProduct; } diff --git a/lib/default.nix b/lib/default.nix index f1822307d2ee..e671fcf9546d 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,212 +1,566 @@ -/* Library of low-level helper functions for nix expressions. - * - * Please implement (mostly) exhaustive unit tests - * for new functions in `./tests.nix`. - */ +/* + Library of low-level helper functions for nix expressions. + + Please implement (mostly) exhaustive unit tests + for new functions in `./tests.nix`. +*/ let # A copy of `lib.makeExtensible'` in order to document `extend`. # It has been leading to some trouble, so we have to document it specially. makeExtensible' = rattrs: - let self = rattrs self // { - /** - Patch the Nixpkgs library - - A function that applies patches onto the nixpkgs library. - Usage is discouraged for most scenarios. - - :::{.note} - The name `extends` is a bit misleading, as it doesn't actually extend the library, but rather patches it. - It is merely a consequence of being implemented by `makeExtensible`. - ::: - - # Inputs - - - An "extension function" `f` that returns attributes that will be updated in the returned Nixpkgs library. - - # Output - - A patched Nixpkgs library. - - :::{.warning} - This functionality is intended as an escape hatch for when the provided version of the Nixpkgs library has a flaw. - - If you were to use it to add new functionality, you will run into compatibility and interoperability issues. - ::: - */ - extend = f: lib.makeExtensible (lib.extends f rattrs); - }; - in self; - - lib = makeExtensible' (self: let - callLibs = file: import file { lib = self; }; - in { - - # often used, or depending on very little - trivial = callLibs ./trivial.nix; - fixedPoints = callLibs ./fixed-points.nix; - - # datatypes - attrsets = callLibs ./attrsets.nix; - lists = callLibs ./lists.nix; - strings = callLibs ./strings.nix; - stringsWithDeps = callLibs ./strings-with-deps.nix; - - # packaging - customisation = callLibs ./customisation.nix; - derivations = callLibs ./derivations.nix; - maintainers = import ../maintainers/maintainer-list.nix; - teams = callLibs ../maintainers/team-list.nix; - meta = callLibs ./meta.nix; - versions = callLibs ./versions.nix; - - # module system - modules = callLibs ./modules.nix; - options = callLibs ./options.nix; - types = callLibs ./types.nix; - - # constants - licenses = callLibs ./licenses.nix; - sourceTypes = callLibs ./source-types.nix; - systems = callLibs ./systems; - - # serialization - cli = callLibs ./cli.nix; - gvariant = callLibs ./gvariant.nix; - generators = callLibs ./generators.nix; - - # misc - asserts = callLibs ./asserts.nix; - debug = callLibs ./debug.nix; - misc = callLibs ./deprecated/misc.nix; - - # domain-specific - fetchers = callLibs ./fetchers.nix; - - # Eval-time filesystem handling - path = callLibs ./path; - filesystem = callLibs ./filesystem.nix; - fileset = callLibs ./fileset; - sources = callLibs ./sources.nix; - - # back-compat aliases - platforms = self.systems.doubles; - - # linux kernel configuration - kernel = callLibs ./kernel.nix; - - # network - network = callLibs ./network; - - # TODO: For consistency, all builtins should also be available from a sub-library; - # these are the only ones that are currently not - inherit (builtins) addErrorContext isPath trace typeOf unsafeGetAttrPos; - inherit (self.trivial) id const pipe concat or and xor bitAnd bitOr bitXor - bitNot boolToString mergeAttrs flip defaultTo mapNullable inNixShell isFloat min max - importJSON importTOML warn warnIf warnIfNot throwIf throwIfNot checkListOfEnum - info showWarnings nixpkgsVersion version isInOldestRelease oldestSupportedReleaseIsAtLeast - mod compare splitByAndCompare seq deepSeq lessThan add sub - functionArgs setFunctionArgs isFunction toFunction mirrorFunctionArgs - fromHexString toHexString toBaseDigits inPureEvalMode isBool isInt pathExists - genericClosure readFile; - inherit (self.fixedPoints) fix fix' converge extends composeExtensions - composeManyExtensions makeExtensible makeExtensibleWithCustomName - toExtension; - inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath - getAttrFromPath attrVals attrNames attrValues getAttrs catAttrs filterAttrs - filterAttrsRecursive foldlAttrs foldAttrs collect nameValuePair mapAttrs - mapAttrs' mapAttrsToList attrsToList concatMapAttrs mapAttrsRecursive - mapAttrsRecursiveCond genAttrs isDerivation toDerivation optionalAttrs - zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil - recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput getFirstOutput - getBin getLib getStatic getDev getInclude getMan chooseDevOutputs zipWithNames zip - recurseIntoAttrs dontRecurseIntoAttrs cartesianProduct cartesianProductOfSets - mapCartesianProduct updateManyAttrsByPath listToAttrs hasAttr getAttr isAttrs intersectAttrs removeAttrs; - inherit (self.lists) singleton forEach map foldr fold foldl foldl' imap0 imap1 - filter ifilter0 concatMap flatten remove findSingle findFirst any all count - optional optionals toList range replicate partition zipListsWith zipLists - reverseList listDfs toposort sort sortOn naturalSort compareLists - take drop dropEnd sublist last init - crossLists unique allUnique intersectLists - subtractLists mutuallyExclusive groupBy groupBy' concatLists genList - length head tail elem elemAt isList; - inherit (self.strings) concatStrings concatMapStrings concatImapStrings - stringLength substring isString replaceStrings - intersperse concatStringsSep concatMapStringsSep concatMapAttrsStringSep - concatImapStringsSep concatLines makeSearchPath makeSearchPathOutput - makeLibraryPath makeIncludePath makeBinPath optionalString - hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape - escapeShellArg escapeShellArgs - isStorePath isStringLike - isValidPosixName toShellVar toShellVars trim trimWith - escapeRegex escapeURL escapeXML replaceChars lowerChars - upperChars toLower toUpper toSentenceCase addContextFrom splitString - removePrefix removeSuffix versionOlder versionAtLeast - getName getVersion match split - cmakeOptionType cmakeBool cmakeFeature - mesonOption mesonBool mesonEnable - nameFromURL enableFeature enableFeatureAs withFeature - withFeatureAs fixedWidthString fixedWidthNumber - toInt toIntBase10 readPathsFromFile fileContents; - inherit (self.stringsWithDeps) textClosureList textClosureMap - noDepEntry fullDepEntry packEntry stringAfter; - inherit (self.customisation) overrideDerivation makeOverridable - callPackageWith callPackagesWith extendDerivation hydraJob - makeScope makeScopeWithSplicing makeScopeWithSplicing' - extendMkDerivation; - inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; - inherit (self.generators) mkLuaInline; - inherit (self.meta) addMetaAttrs dontDistribute setName updateName - appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio - hiPrioSet licensesSpdx getLicenseFromSpdxId getLicenseFromSpdxIdOr - getExe getExe'; - inherit (self.filesystem) pathType pathIsDirectory pathIsRegularFile - packagesFromDirectoryRecursive; - inherit (self.sources) cleanSourceFilter - cleanSource sourceByRegex sourceFilesBySuffices - commitIdFromGitRepo cleanSourceWith pathHasContext - canCleanSource pathIsGitRepo; - inherit (self.modules) evalModules setDefaultModuleLocation - unifyModuleSyntax applyModuleArgsIfFunction mergeModules - mergeModules' mergeOptionDecls mergeDefinitions - pushDownProperties dischargeProperties filterOverrides - sortProperties fixupOptionType mkIf mkAssert mkMerge mkOverride - mkOptionDefault mkDefault mkImageMediaOverride mkForce mkVMOverride - mkFixStrictness mkOrder mkBefore mkAfter mkAliasDefinitions - mkAliasAndWrapDefinitions fixMergeModules mkRemovedOptionModule - mkRenamedOptionModule mkRenamedOptionModuleWith - mkMergedOptionModule mkChangedOptionModule - mkAliasOptionModule mkDerivedConfig doRename - mkAliasOptionModuleMD; - evalOptionValue = lib.warn "External use of `lib.evalOptionValue` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." self.modules.evalOptionValue; - inherit (self.options) isOption mkEnableOption mkSinkUndeclaredOptions - mergeDefaultOption mergeOneOption mergeEqualOption mergeUniqueOption - getValues getFiles - optionAttrSetToDocList optionAttrSetToDocList' - scrubOptionValue literalExpression literalExample - showOption showOptionWithDefLocs showFiles - unknownModule mkOption mkPackageOption mkPackageOptionMD - literalMD; - inherit (self.types) isType setType defaultTypeMerge defaultFunctor - isOptionType mkOptionType; - inherit (self.asserts) - assertMsg assertOneOf; - inherit (self.debug) traceIf traceVal traceValFn - traceSeq traceSeqN traceValSeq - traceValSeqFn traceValSeqN traceValSeqNFn traceFnSeqN - runTests testAllTrue; - inherit (self.misc) maybeEnv defaultMergeArg defaultMerge foldArgs - maybeAttrNullable maybeAttr ifEnable checkFlag getValue - checkReqs uniqList uniqListExt condConcat lazyGenericClosure - innerModifySumArgs modifySumArgs innerClosePropagation - closePropagation mapAttrsFlatten nvs setAttr setAttrMerge - mergeAttrsWithFunc mergeAttrsConcatenateValues - mergeAttrsNoOverride mergeAttrByFunc mergeAttrsByFuncDefaults - mergeAttrsByFuncDefaultsClean mergeAttrBy - fakeHash fakeSha256 fakeSha512 - nixType imap; - inherit (self.versions) - splitVersion; - }); -in lib + let + self = rattrs self // { + /** + Patch the Nixpkgs library + + A function that applies patches onto the nixpkgs library. + Usage is discouraged for most scenarios. + + :::{.note} + The name `extends` is a bit misleading, as it doesn't actually extend the library, but rather patches it. + It is merely a consequence of being implemented by `makeExtensible`. + ::: + + # Inputs + + - An "extension function" `f` that returns attributes that will be updated in the returned Nixpkgs library. + + # Output + + A patched Nixpkgs library. + + :::{.warning} + This functionality is intended as an escape hatch for when the provided version of the Nixpkgs library has a flaw. + + If you were to use it to add new functionality, you will run into compatibility and interoperability issues. + ::: + */ + extend = f: lib.makeExtensible (lib.extends f rattrs); + }; + in + self; + + lib = makeExtensible' ( + self: + let + callLibs = file: import file { lib = self; }; + in + { + + # often used, or depending on very little + trivial = callLibs ./trivial.nix; + fixedPoints = callLibs ./fixed-points.nix; + + # datatypes + attrsets = callLibs ./attrsets.nix; + lists = callLibs ./lists.nix; + strings = callLibs ./strings.nix; + stringsWithDeps = callLibs ./strings-with-deps.nix; + + # packaging + customisation = callLibs ./customisation.nix; + derivations = callLibs ./derivations.nix; + maintainers = import ../maintainers/maintainer-list.nix; + teams = callLibs ../maintainers/team-list.nix; + meta = callLibs ./meta.nix; + versions = callLibs ./versions.nix; + + # module system + modules = callLibs ./modules.nix; + options = callLibs ./options.nix; + types = callLibs ./types.nix; + + # constants + licenses = callLibs ./licenses.nix; + sourceTypes = callLibs ./source-types.nix; + systems = callLibs ./systems; + + # serialization + cli = callLibs ./cli.nix; + gvariant = callLibs ./gvariant.nix; + generators = callLibs ./generators.nix; + + # misc + asserts = callLibs ./asserts.nix; + debug = callLibs ./debug.nix; + misc = callLibs ./deprecated/misc.nix; + + # domain-specific + fetchers = callLibs ./fetchers.nix; + + # Eval-time filesystem handling + path = callLibs ./path; + filesystem = callLibs ./filesystem.nix; + fileset = callLibs ./fileset; + sources = callLibs ./sources.nix; + + # back-compat aliases + platforms = self.systems.doubles; + + # linux kernel configuration + kernel = callLibs ./kernel.nix; + + # network + network = callLibs ./network; + + # TODO: For consistency, all builtins should also be available from a sub-library; + # these are the only ones that are currently not + inherit (builtins) + addErrorContext + isPath + trace + typeOf + unsafeGetAttrPos + ; + inherit (self.trivial) + id + const + pipe + concat + or + and + xor + bitAnd + bitOr + bitXor + bitNot + boolToString + mergeAttrs + flip + defaultTo + mapNullable + inNixShell + isFloat + min + max + importJSON + importTOML + warn + warnIf + warnIfNot + throwIf + throwIfNot + checkListOfEnum + info + showWarnings + nixpkgsVersion + version + isInOldestRelease + oldestSupportedReleaseIsAtLeast + mod + compare + splitByAndCompare + seq + deepSeq + lessThan + add + sub + functionArgs + setFunctionArgs + isFunction + toFunction + mirrorFunctionArgs + fromHexString + toHexString + toBaseDigits + inPureEvalMode + isBool + isInt + pathExists + genericClosure + readFile + ; + inherit (self.fixedPoints) + fix + fix' + converge + extends + composeExtensions + composeManyExtensions + makeExtensible + makeExtensibleWithCustomName + toExtension + ; + inherit (self.attrsets) + attrByPath + hasAttrByPath + setAttrByPath + getAttrFromPath + attrVals + attrNames + attrValues + getAttrs + catAttrs + filterAttrs + filterAttrsRecursive + foldlAttrs + foldAttrs + collect + nameValuePair + mapAttrs + mapAttrs' + mapAttrsToList + attrsToList + concatMapAttrs + mapAttrsRecursive + mapAttrsRecursiveCond + genAttrs + isDerivation + toDerivation + optionalAttrs + zipAttrsWithNames + zipAttrsWith + zipAttrs + recursiveUpdateUntil + recursiveUpdate + matchAttrs + mergeAttrsList + overrideExisting + showAttrPath + getOutput + getFirstOutput + getBin + getLib + getStatic + getDev + getInclude + getMan + chooseDevOutputs + zipWithNames + zip + recurseIntoAttrs + dontRecurseIntoAttrs + cartesianProduct + cartesianProductOfSets + mapCartesianProduct + updateManyAttrsByPath + listToAttrs + hasAttr + getAttr + isAttrs + intersectAttrs + removeAttrs + ; + inherit (self.lists) + singleton + forEach + map + foldr + fold + foldl + foldl' + imap0 + imap1 + filter + ifilter0 + concatMap + flatten + remove + findSingle + findFirst + any + all + count + optional + optionals + toList + range + replicate + partition + zipListsWith + zipLists + reverseList + listDfs + toposort + sort + sortOn + naturalSort + compareLists + take + drop + dropEnd + sublist + last + init + crossLists + unique + allUnique + intersectLists + subtractLists + mutuallyExclusive + groupBy + groupBy' + concatLists + genList + length + head + tail + elem + elemAt + isList + ; + inherit (self.strings) + concatStrings + concatMapStrings + concatImapStrings + stringLength + substring + isString + replaceStrings + intersperse + concatStringsSep + concatMapStringsSep + concatMapAttrsStringSep + concatImapStringsSep + concatLines + makeSearchPath + makeSearchPathOutput + makeLibraryPath + makeIncludePath + makeBinPath + optionalString + hasInfix + hasPrefix + hasSuffix + stringToCharacters + stringAsChars + escape + escapeShellArg + escapeShellArgs + isStorePath + isStringLike + isValidPosixName + toShellVar + toShellVars + trim + trimWith + escapeRegex + escapeURL + escapeXML + replaceChars + lowerChars + upperChars + toLower + toUpper + toSentenceCase + addContextFrom + splitString + removePrefix + removeSuffix + versionOlder + versionAtLeast + getName + getVersion + match + split + cmakeOptionType + cmakeBool + cmakeFeature + mesonOption + mesonBool + mesonEnable + nameFromURL + enableFeature + enableFeatureAs + withFeature + withFeatureAs + fixedWidthString + fixedWidthNumber + toInt + toIntBase10 + readPathsFromFile + fileContents + ; + inherit (self.stringsWithDeps) + textClosureList + textClosureMap + noDepEntry + fullDepEntry + packEntry + stringAfter + ; + inherit (self.customisation) + overrideDerivation + makeOverridable + callPackageWith + callPackagesWith + extendDerivation + hydraJob + makeScope + makeScopeWithSplicing + makeScopeWithSplicing' + extendMkDerivation + ; + inherit (self.derivations) lazyDerivation optionalDrvAttr warnOnInstantiate; + inherit (self.generators) mkLuaInline; + inherit (self.meta) + addMetaAttrs + dontDistribute + setName + updateName + appendToName + mapDerivationAttrset + setPrio + lowPrio + lowPrioSet + hiPrio + hiPrioSet + licensesSpdx + getLicenseFromSpdxId + getLicenseFromSpdxIdOr + getExe + getExe' + ; + inherit (self.filesystem) + pathType + pathIsDirectory + pathIsRegularFile + packagesFromDirectoryRecursive + ; + inherit (self.sources) + cleanSourceFilter + cleanSource + sourceByRegex + sourceFilesBySuffices + commitIdFromGitRepo + cleanSourceWith + pathHasContext + canCleanSource + pathIsGitRepo + ; + inherit (self.modules) + evalModules + setDefaultModuleLocation + unifyModuleSyntax + applyModuleArgsIfFunction + mergeModules + mergeModules' + mergeOptionDecls + mergeDefinitions + pushDownProperties + dischargeProperties + filterOverrides + sortProperties + fixupOptionType + mkIf + mkAssert + mkMerge + mkOverride + mkOptionDefault + mkDefault + mkImageMediaOverride + mkForce + mkVMOverride + mkFixStrictness + mkOrder + mkBefore + mkAfter + mkAliasDefinitions + mkAliasAndWrapDefinitions + fixMergeModules + mkRemovedOptionModule + mkRenamedOptionModule + mkRenamedOptionModuleWith + mkMergedOptionModule + mkChangedOptionModule + mkAliasOptionModule + mkDerivedConfig + doRename + mkAliasOptionModuleMD + ; + evalOptionValue = lib.warn "External use of `lib.evalOptionValue` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." self.modules.evalOptionValue; + inherit (self.options) + isOption + mkEnableOption + mkSinkUndeclaredOptions + mergeDefaultOption + mergeOneOption + mergeEqualOption + mergeUniqueOption + getValues + getFiles + optionAttrSetToDocList + optionAttrSetToDocList' + scrubOptionValue + literalExpression + literalExample + showOption + showOptionWithDefLocs + showFiles + unknownModule + mkOption + mkPackageOption + mkPackageOptionMD + literalMD + ; + inherit (self.types) + isType + setType + defaultTypeMerge + defaultFunctor + isOptionType + mkOptionType + ; + inherit (self.asserts) + assertMsg + assertOneOf + ; + inherit (self.debug) + traceIf + traceVal + traceValFn + traceSeq + traceSeqN + traceValSeq + traceValSeqFn + traceValSeqN + traceValSeqNFn + traceFnSeqN + runTests + testAllTrue + ; + inherit (self.misc) + maybeEnv + defaultMergeArg + defaultMerge + foldArgs + maybeAttrNullable + maybeAttr + ifEnable + checkFlag + getValue + checkReqs + uniqList + uniqListExt + condConcat + lazyGenericClosure + innerModifySumArgs + modifySumArgs + innerClosePropagation + closePropagation + mapAttrsFlatten + nvs + setAttr + setAttrMerge + mergeAttrsWithFunc + mergeAttrsConcatenateValues + mergeAttrsNoOverride + mergeAttrByFunc + mergeAttrsByFuncDefaults + mergeAttrsByFuncDefaultsClean + mergeAttrBy + fakeHash + fakeSha256 + fakeSha512 + nixType + imap + ; + inherit (self.versions) + splitVersion + ; + } + ); +in +lib diff --git a/lib/deprecated/misc.nix b/lib/deprecated/misc.nix index 34d1c01ce811..230efed40ffd 100644 --- a/lib/deprecated/misc.nix +++ b/lib/deprecated/misc.nix @@ -35,153 +35,212 @@ let inherit (lib.attrsets) removeAttrs mapAttrsToList; # returns default if env var is not set - maybeEnv = name: default: - let value = builtins.getEnv name; in + maybeEnv = + name: default: + let + value = builtins.getEnv name; + in if value == "" then default else value; - defaultMergeArg = x : y: if builtins.isAttrs y then - y - else - (y x); + defaultMergeArg = x: y: if builtins.isAttrs y then y else (y x); defaultMerge = x: y: x // (defaultMergeArg x y); - foldArgs = merger: f: init: x: - let arg = (merger init (defaultMergeArg init x)); - # now add the function with composed args already applied to the final attrs - base = (setAttrMerge "passthru" {} (f arg) - ( z: z // { - function = foldArgs merger f arg; - args = (attrByPath ["passthru" "args"] {} z) // x; - } )); - withStdOverrides = base // { - override = base.passthru.function; - }; - in - withStdOverrides; - + foldArgs = + merger: f: init: x: + let + arg = (merger init (defaultMergeArg init x)); + # now add the function with composed args already applied to the final attrs + base = ( + setAttrMerge "passthru" { } (f arg) ( + z: + z + // { + function = foldArgs merger f arg; + args = (attrByPath [ "passthru" "args" ] { } z) // x; + } + ) + ); + withStdOverrides = base // { + override = base.passthru.function; + }; + in + withStdOverrides; # shortcut for attrByPath ["name"] default attrs maybeAttrNullable = maybeAttr; # shortcut for attrByPath ["name"] default attrs - maybeAttr = name: default: attrs: attrs.${name} or default; - + maybeAttr = + name: default: attrs: + attrs.${name} or default; # Return the second argument if the first one is true or the empty version # of the second argument. - ifEnable = cond: val: - if cond then val - else if builtins.isList val then [] - else if builtins.isAttrs val then {} + ifEnable = + cond: val: + if cond then + val + else if builtins.isList val then + [ ] + else if builtins.isAttrs val then + { } # else if builtins.isString val then "" - else if val == true || val == false then false - else null; - + else if val == true || val == false then + false + else + null; # Return true only if there is an attribute and it is true. - checkFlag = attrSet: name: - if name == "true" then true else - if name == "false" then false else - if (elem name (attrByPath ["flags"] [] attrSet)) then true else - attrByPath [name] false attrSet ; - + checkFlag = + attrSet: name: + if name == "true" then + true + else if name == "false" then + false + else if (elem name (attrByPath [ "flags" ] [ ] attrSet)) then + true + else + attrByPath [ name ] false attrSet; # Input : attrSet, [ [name default] ... ], name # Output : its value or default. - getValue = attrSet: argList: name: - ( attrByPath [name] (if checkFlag attrSet name then true else - if argList == [] then null else - let x = builtins.head argList; in - if (head x) == name then - (head (tail x)) - else (getValue attrSet - (tail argList) name)) attrSet ); - + getValue = + attrSet: argList: name: + (attrByPath [ name ] ( + if checkFlag attrSet name then + true + else if argList == [ ] then + null + else + let + x = builtins.head argList; + in + if (head x) == name then (head (tail x)) else (getValue attrSet (tail argList) name) + ) attrSet); # Input : attrSet, [[name default] ...], [ [flagname reqs..] ... ] # Output : are reqs satisfied? It's asserted. - checkReqs = attrSet: argList: condList: - ( - foldr and true - (map (x: let name = (head x); in - - ((checkFlag attrSet name) -> - (foldr and true - (map (y: let val=(getValue attrSet argList y); in - (val!=null) && (val!=false)) - (tail x))))) condList)); + checkReqs = + attrSet: argList: condList: + (foldr and true ( + map ( + x: + let + name = (head x); + in + ( + (checkFlag attrSet name) + -> (foldr and true ( + map ( + y: + let + val = (getValue attrSet argList y); + in + (val != null) && (val != false) + ) (tail x) + )) + ) + ) condList + )); # This function has O(n^2) performance. - uniqList = { inputList, acc ? [] }: - let go = xs: acc: - if xs == [] - then [] - else let x = head xs; - y = if elem x acc then [] else [x]; - in y ++ go (tail xs) (y ++ acc); - in go inputList acc; - - uniqListExt = { inputList, - outputList ? [], - getter ? (x: x), - compare ? (x: y: x==y) }: - if inputList == [] then outputList else - let x = head inputList; - isX = y: (compare (getter y) (getter x)); - newOutputList = outputList ++ - (if any isX outputList then [] else [x]); - in uniqListExt { outputList = newOutputList; - inputList = (tail inputList); - inherit getter compare; - }; - - condConcat = name: list: checker: - if list == [] then name else - if checker (head list) then - condConcat - (name + (head (tail list))) - (tail (tail list)) - checker - else condConcat - name (tail (tail list)) checker; - - lazyGenericClosure = {startSet, operator}: + uniqList = + { + inputList, + acc ? [ ], + }: + let + go = + xs: acc: + if xs == [ ] then + [ ] + else + let + x = head xs; + y = if elem x acc then [ ] else [ x ]; + in + y ++ go (tail xs) (y ++ acc); + in + go inputList acc; + + uniqListExt = + { + inputList, + outputList ? [ ], + getter ? (x: x), + compare ? (x: y: x == y), + }: + if inputList == [ ] then + outputList + else + let + x = head inputList; + isX = y: (compare (getter y) (getter x)); + newOutputList = outputList ++ (if any isX outputList then [ ] else [ x ]); + in + uniqListExt { + outputList = newOutputList; + inputList = (tail inputList); + inherit getter compare; + }; + + condConcat = + name: list: checker: + if list == [ ] then + name + else if checker (head list) then + condConcat (name + (head (tail list))) (tail (tail list)) checker + else + condConcat name (tail (tail list)) checker; + + lazyGenericClosure = + { startSet, operator }: let - work = list: doneKeys: result: - if list == [] then + work = + list: doneKeys: result: + if list == [ ] then result else - let x = head list; key = x.key; in + let + x = head list; + key = x.key; + in if elem key doneKeys then work (tail list) doneKeys result else - work (tail list ++ operator x) ([key] ++ doneKeys) ([x] ++ result); + work (tail list ++ operator x) ([ key ] ++ doneKeys) ([ x ] ++ result); in - work startSet [] []; - - innerModifySumArgs = f: x: a: b: if b == null then (f a b) // x else - innerModifySumArgs f x (a // b); - modifySumArgs = f: x: innerModifySumArgs f x {}; - - - innerClosePropagation = acc: xs: - if xs == [] - then acc - else let y = head xs; - ys = tail xs; - in if ! isAttrs y - then innerClosePropagation acc ys - else let acc' = [y] ++ acc; - in innerClosePropagation - acc' - (uniqList { inputList = (maybeAttrNullable "propagatedBuildInputs" [] y) - ++ (maybeAttrNullable "propagatedNativeBuildInputs" [] y) - ++ ys; - acc = acc'; - } - ); - - closePropagationSlow = list: (uniqList {inputList = (innerClosePropagation [] list);}); + work startSet [ ] [ ]; + + innerModifySumArgs = + f: x: a: b: + if b == null then (f a b) // x else innerModifySumArgs f x (a // b); + modifySumArgs = f: x: innerModifySumArgs f x { }; + + innerClosePropagation = + acc: xs: + if xs == [ ] then + acc + else + let + y = head xs; + ys = tail xs; + in + if !isAttrs y then + innerClosePropagation acc ys + else + let + acc' = [ y ] ++ acc; + in + innerClosePropagation acc' (uniqList { + inputList = + (maybeAttrNullable "propagatedBuildInputs" [ ] y) + ++ (maybeAttrNullable "propagatedNativeBuildInputs" [ ] y) + ++ ys; + acc = acc'; + }); + + closePropagationSlow = list: (uniqList { inputList = (innerClosePropagation [ ] list); }); # This is an optimisation of closePropagation which avoids the O(n^2) behavior # Using a list of derivations, it generates the full closure of the propagatedXXXBuildInputs @@ -189,28 +248,35 @@ let # attribute of each derivation. # On some benchmarks, it performs up to 15 times faster than closePropagation. # See https://github.com/NixOS/nixpkgs/pull/194391 for details. - closePropagationFast = list: - builtins.map (x: x.val) (builtins.genericClosure { - startSet = builtins.map (x: { - key = x.outPath; - val = x; - }) (builtins.filter (x: x != null) list); - operator = item: - if !builtins.isAttrs item.val then - [ ] - else - builtins.concatMap (x: - if x != null then [{ - key = x.outPath; - val = x; - }] else - [ ]) ((item.val.propagatedBuildInputs or [ ]) - ++ (item.val.propagatedNativeBuildInputs or [ ])); - }); - - closePropagation = if builtins ? genericClosure - then closePropagationFast - else closePropagationSlow; + closePropagationFast = + list: + builtins.map (x: x.val) ( + builtins.genericClosure { + startSet = builtins.map (x: { + key = x.outPath; + val = x; + }) (builtins.filter (x: x != null) list); + operator = + item: + if !builtins.isAttrs item.val then + [ ] + else + builtins.concatMap ( + x: + if x != null then + [ + { + key = x.outPath; + val = x; + } + ] + else + [ ] + ) ((item.val.propagatedBuildInputs or [ ]) ++ (item.val.propagatedNativeBuildInputs or [ ])); + } + ); + + closePropagation = if builtins ? genericClosure then closePropagationFast else closePropagationSlow; # calls a function (f attr value ) for each record item. returns a list mapAttrsFlatten = warn "lib.misc.mapAttrsFlatten is deprecated, please use lib.attrsets.mapAttrsToList instead." mapAttrsToList; @@ -218,26 +284,29 @@ let # attribute set containing one attribute nvs = name: value: listToAttrs [ (nameValuePair name value) ]; # adds / replaces an attribute of an attribute set - setAttr = set: name: v: set // (nvs name v); + setAttr = + set: name: v: + set // (nvs name v); # setAttrMerge (similar to mergeAttrsWithFunc but only merges the values of a particular name) # setAttrMerge "a" [] { a = [2];} (x: x ++ [3]) -> { a = [2 3]; } # setAttrMerge "a" [] { } (x: x ++ [3]) -> { a = [ 3]; } - setAttrMerge = name: default: attrs: f: + setAttrMerge = + name: default: attrs: f: setAttr attrs name (f (maybeAttr name default attrs)); # Using f = a: b = b the result is similar to // # merge attributes with custom function handling the case that the attribute # exists in both sets - mergeAttrsWithFunc = f: set1: set2: - foldr (n: set: if set ? ${n} - then setAttr set n (f set.${n} set2.${n}) - else set ) - (set2 // set1) (attrNames set2); + mergeAttrsWithFunc = + f: set1: set2: + foldr (n: set: if set ? ${n} then setAttr set n (f set.${n} set2.${n}) else set) (set2 // set1) ( + attrNames set2 + ); # merging two attribute set concatenating the values of same attribute names # eg { a = 7; } { a = [ 2 3 ]; } becomes { a = [ 7 2 3 ]; } - mergeAttrsConcatenateValues = mergeAttrsWithFunc ( a: b: (toList a) ++ (toList b) ); + mergeAttrsConcatenateValues = mergeAttrsWithFunc (a: b: (toList a) ++ (toList b)); # merges attributes using //, if a name exists in both attributes # an error will be triggered unless its listed in mergeLists @@ -246,20 +315,31 @@ let # merging buildPhase doesn't really make sense. The cases will be rare where appending /prefixing will fit your needs? # in these cases the first buildPhase will override the second one # ! deprecated, use mergeAttrByFunc instead - mergeAttrsNoOverride = { mergeLists ? ["buildInputs" "propagatedBuildInputs"], - overrideSnd ? [ "buildPhase" ] - }: attrs1: attrs2: - foldr (n: set: - setAttr set n ( if set ? ${n} - then # merge - if elem n mergeLists # attribute contains list, merge them by concatenating - then attrs2.${n} ++ attrs1.${n} - else if elem n overrideSnd - then attrs1.${n} - else throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined" - else attrs2.${n} # add attribute not existing in attr1 - )) attrs1 (attrNames attrs2); - + mergeAttrsNoOverride = + { + mergeLists ? [ + "buildInputs" + "propagatedBuildInputs" + ], + overrideSnd ? [ "buildPhase" ], + }: + attrs1: attrs2: + foldr ( + n: set: + setAttr set n ( + if set ? ${n} then # merge + if + elem n mergeLists # attribute contains list, merge them by concatenating + then + attrs2.${n} ++ attrs1.${n} + else if elem n overrideSnd then + attrs1.${n} + else + throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined" + else + attrs2.${n} # add attribute not existing in attr1 + ) + ) attrs1 (attrNames attrs2); # example usage: # mergeAttrByFunc { @@ -272,48 +352,82 @@ let # { mergeAttrsBy = [...]; buildInputs = [ a b c d ]; } # is used by defaultOverridableDelayableArgs and can be used when composing using # foldArgs, composedArgsAndFun or applyAndFun. Example: composableDerivation in all-packages.nix - mergeAttrByFunc = x: y: + mergeAttrByFunc = + x: y: let - mergeAttrBy2 = { mergeAttrBy = mergeAttrs; } - // (maybeAttr "mergeAttrBy" {} x) - // (maybeAttr "mergeAttrBy" {} y); in - foldr mergeAttrs {} [ - x y - (mapAttrs ( a: v: # merge special names using given functions - if x ? ${a} - then if y ? ${a} - then v x.${a} y.${a} # both have attr, use merge func - else x.${a} # only x has attr - else y.${a} # only y has attr) - ) (removeAttrs mergeAttrBy2 - # don't merge attrs which are neither in x nor y - (filter (a: ! x ? ${a} && ! y ? ${a}) - (attrNames mergeAttrBy2)) - ) + mergeAttrBy2 = + { mergeAttrBy = mergeAttrs; } // (maybeAttr "mergeAttrBy" { } x) // (maybeAttr "mergeAttrBy" { } y); + in + foldr mergeAttrs { } [ + x + y + (mapAttrs + ( + a: v: # merge special names using given functions + if x ? ${a} then + if y ? ${a} then + v x.${a} y.${a} # both have attr, use merge func + else + x.${a} # only x has attr + else + y.${a} # only y has attr) + ) + ( + removeAttrs mergeAttrBy2 + # don't merge attrs which are neither in x nor y + (filter (a: !x ? ${a} && !y ? ${a}) (attrNames mergeAttrBy2)) + ) ) ]; mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; }; - mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) ["mergeAttrBy"]; + mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) [ "mergeAttrBy" ]; # sane defaults (same name as attr name so that inherit can be used) mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; } - listToAttrs (map (n: nameValuePair n concat) - [ "nativeBuildInputs" "buildInputs" "propagatedBuildInputs" "configureFlags" "prePhases" "postAll" "patches" ]) - // listToAttrs (map (n: nameValuePair n mergeAttrs) [ "passthru" "meta" "cfg" "flags" ]) - // listToAttrs (map (n: nameValuePair n (a: b: "${a}\n${b}") ) [ "preConfigure" "postInstall" ]) - ; - - nixType = x: - if isAttrs x then - if x ? outPath then "derivation" - else "attrs" - else if isFunction x then "function" - else if isList x then "list" - else if x == true then "bool" - else if x == false then "bool" - else if x == null then "null" - else if isInt x then "int" - else "string"; + listToAttrs ( + map (n: nameValuePair n concat) [ + "nativeBuildInputs" + "buildInputs" + "propagatedBuildInputs" + "configureFlags" + "prePhases" + "postAll" + "patches" + ] + ) + // listToAttrs ( + map (n: nameValuePair n mergeAttrs) [ + "passthru" + "meta" + "cfg" + "flags" + ] + ) + // listToAttrs ( + map (n: nameValuePair n (a: b: "${a}\n${b}")) [ + "preConfigure" + "postInstall" + ] + ); + + nixType = + x: + if isAttrs x then + if x ? outPath then "derivation" else "attrs" + else if isFunction x then + "function" + else if isList x then + "list" + else if x == true then + "bool" + else if x == false then + "bool" + else if x == null then + "null" + else if isInt x then + "int" + else + "string"; /** # Deprecated diff --git a/lib/filesystem.nix b/lib/filesystem.nix index c3e44137c42d..49e4f8363513 100644 --- a/lib/filesystem.nix +++ b/lib/filesystem.nix @@ -59,23 +59,26 @@ in pathType = builtins.readFileType or # Nix <2.14 compatibility shim - (path: - if ! pathExists path + ( + path: + if + !pathExists path # Fail irrecoverably to mimic the historic behavior of this function and # the new builtins.readFileType - then abort "lib.filesystem.pathType: Path ${toString path} does not exist." + then + abort "lib.filesystem.pathType: Path ${toString path} does not exist." # The filesystem root is the only path where `dirOf / == /` and # `baseNameOf /` is not valid. We can detect this and directly return # "directory", since we know the filesystem root can't be anything else. - else if dirOf path == path - then "directory" - else (readDir (dirOf path)).${baseNameOf path} + else if dirOf path == path then + "directory" + else + (readDir (dirOf path)).${baseNameOf path} ); /** Whether a path exists and is a directory. - # Inputs `path` @@ -105,13 +108,11 @@ in ::: */ - pathIsDirectory = path: - pathExists path && pathType path == "directory"; + pathIsDirectory = path: pathExists path && pathType path == "directory"; /** Whether a path exists and is a regular file, meaning not a symlink or any other special file type. - # Inputs `path` @@ -141,15 +142,13 @@ in ::: */ - pathIsRegularFile = path: - pathExists path && pathType path == "regular"; + pathIsRegularFile = path: pathExists path && pathType path == "regular"; /** A map of all haskell packages defined in the given path, identified by having a cabal file with the same name as the directory itself. - # Inputs `root` @@ -164,25 +163,25 @@ in */ haskellPathsInDir = root: - let # Files in the root - root-files = builtins.attrNames (builtins.readDir root); - # Files with their full paths - root-files-with-paths = - map (file: - { name = file; value = root + "/${file}"; } - ) root-files; - # Subdirectories of the root with a cabal file. - cabal-subdirs = - builtins.filter ({ name, value }: - builtins.pathExists (value + "/${name}.cabal") - ) root-files-with-paths; - in builtins.listToAttrs cabal-subdirs; + let + # Files in the root + root-files = builtins.attrNames (builtins.readDir root); + # Files with their full paths + root-files-with-paths = map (file: { + name = file; + value = root + "/${file}"; + }) root-files; + # Subdirectories of the root with a cabal file. + cabal-subdirs = builtins.filter ( + { name, value }: builtins.pathExists (value + "/${name}.cabal") + ) root-files-with-paths; + in + builtins.listToAttrs cabal-subdirs; /** Find the first directory containing a file matching 'pattern' upward from a given 'file'. Returns 'null' if no directories contain a file matching 'pattern'. - # Inputs `pattern` @@ -200,30 +199,33 @@ in ``` */ locateDominatingFile = - pattern: - file: - let go = path: - let files = builtins.attrNames (builtins.readDir path); - matches = builtins.filter (match: match != null) - (map (builtins.match pattern) files); - in - if builtins.length matches != 0 - then { inherit path matches; } - else if path == /. - then null - else go (dirOf path); - parent = dirOf file; - isDir = - let base = baseNameOf file; - type = (builtins.readDir parent).${base} or null; - in file == /. || type == "directory"; - in go (if isDir then file else parent); - + pattern: file: + let + go = + path: + let + files = builtins.attrNames (builtins.readDir path); + matches = builtins.filter (match: match != null) (map (builtins.match pattern) files); + in + if builtins.length matches != 0 then + { inherit path matches; } + else if path == /. then + null + else + go (dirOf path); + parent = dirOf file; + isDir = + let + base = baseNameOf file; + type = (builtins.readDir parent).${base} or null; + in + file == /. || type == "directory"; + in + go (if isDir then file else parent); /** Given a directory, return a flattened list of all files within it recursively. - # Inputs `dir` @@ -238,12 +240,15 @@ in */ listFilesRecursive = dir: - lib.flatten (lib.mapAttrsToList (name: type: - if type == "directory" then - lib.filesystem.listFilesRecursive (dir + "/${name}") - else - dir + "/${name}" - ) (builtins.readDir dir)); + lib.flatten ( + lib.mapAttrsToList ( + name: type: + if type == "directory" then + lib.filesystem.listFilesRecursive (dir + "/${name}") + else + dir + "/${name}" + ) (builtins.readDir dir) + ); /** Transform a directory tree containing package files suitable for @@ -373,29 +378,49 @@ in */ packagesFromDirectoryRecursive = let - inherit (lib) concatMapAttrs id makeScope recurseIntoAttrs removeSuffix; + inherit (lib) + concatMapAttrs + id + makeScope + recurseIntoAttrs + removeSuffix + ; inherit (lib.path) append; # Generate an attrset corresponding to a given directory. # This function is outside `packagesFromDirectoryRecursive`'s lambda expression, # to prevent accidentally using its parameters. - processDir = { callPackage, directory, ... }@args: - concatMapAttrs (name: type: + processDir = + { callPackage, directory, ... }@args: + concatMapAttrs ( + name: type: # for each directory entry - let path = append directory name; in - if type == "directory" then { - # recurse into directories - "${name}" = packagesFromDirectoryRecursive (args // { - directory = path; - }); - } else if type == "regular" && hasSuffix ".nix" name then { - # call .nix files - "${removeSuffix ".nix" name}" = callPackage path {}; - } else if type == "regular" then { - # ignore non-nix files - } else throw '' - lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} - '' + let + path = append directory name; + in + if type == "directory" then + { + # recurse into directories + "${name}" = packagesFromDirectoryRecursive ( + args + // { + directory = path; + } + ); + } + else if type == "regular" && hasSuffix ".nix" name then + { + # call .nix files + "${removeSuffix ".nix" name}" = callPackage path { }; + } + else if type == "regular" then + { + # ignore non-nix files + } + else + throw '' + lib.filesystem.packagesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString path} + '' ) (builtins.readDir directory); in { @@ -408,20 +433,25 @@ in in if pathExists defaultPath then # if `${directory}/package.nix` exists, call it directly - callPackage defaultPath {} + callPackage defaultPath { } else if args ? newScope then # Create a new scope and mark it `recurseForDerivations`. # This lets the packages refer to each other. # See: # [lib.makeScope](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) and # [lib.recurseIntoAttrs](https://nixos.org/manual/nixpkgs/unstable/#function-library-lib.customisation.makeScope) - recurseIntoAttrs (makeScope newScope (self: - # generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` - processDir (args // { - inherit (self) callPackage newScope; - }) - )) + recurseIntoAttrs ( + makeScope newScope ( + self: + # generate the attrset representing the directory, using the new scope's `callPackage` and `newScope` + processDir ( + args + // { + inherit (self) callPackage newScope; + } + ) + ) + ) else - processDir args - ; + processDir args; } diff --git a/lib/generators.nix b/lib/generators.nix index eb8d76626e71..9f9fb834ca07 100644 --- a/lib/generators.nix +++ b/lib/generators.nix @@ -35,7 +35,7 @@ let filter flatten foldl - functionArgs # Note: not the builtin; considers `__functor` in attrsets. + functionArgs # Note: not the builtin; considers `__functor` in attrsets. gvariant hasInfix head @@ -45,7 +45,7 @@ let isBool isDerivation isFloat - isFunction # Note: not the builtin; considers `__functor` in attrsets. + isFunction # Note: not the builtin; considers `__functor` in attrsets. isInt isList isPath @@ -74,7 +74,8 @@ let ; ## -- HELPER FUNCTIONS & DEFAULTS -- -in rec { +in +rec { /** Convert a value to a sensible default string representation. The builtin `toString` function has some strange defaults, @@ -88,32 +89,44 @@ in rec { `v` : 2\. Function argument */ - mkValueStringDefault = {}: v: - let err = t: v: abort - ("generators.mkValueStringDefault: " + - "${t} not supported: ${toPretty {} v}"); - in if isInt v then toString v + mkValueStringDefault = + { }: + v: + let + err = t: v: abort ("generators.mkValueStringDefault: " + "${t} not supported: ${toPretty { } v}"); + in + if isInt v then + toString v # convert derivations to store paths - else if isDerivation v then toString v + else if isDerivation v then + toString v # we default to not quoting strings - else if isString v then v + else if isString v then + v # isString returns "1", which is not a good default - else if true == v then "true" + else if true == v then + "true" # here it returns to "", which is even less of a good default - else if false == v then "false" - else if null == v then "null" + else if false == v then + "false" + else if null == v then + "null" # if you have lists you probably want to replace this - else if isList v then err "lists" v + else if isList v then + err "lists" v # same as for lists, might want to replace - else if isAttrs v then err "attrsets" v + else if isAttrs v then + err "attrsets" v # functions can’t be printed of course - else if isFunction v then err "functions" v + else if isFunction v then + err "functions" v # Floats currently can't be converted to precise strings, # condition warning on nix version once this isn't a problem anymore # See https://github.com/NixOS/nix/pull/3480 - else if isFloat v then floatToString v - else err "this value is" (toString v); - + else if isFloat v then + floatToString v + else + err "this value is" (toString v); /** Generate a line of key k and value v, separated by @@ -145,15 +158,15 @@ in rec { : 4\. Function argument */ - mkKeyValueDefault = { - mkValueString ? mkValueStringDefault {} - }: sep: k: v: - "${escape [sep] k}${sep}${mkValueString v}"; - + mkKeyValueDefault = + { + mkValueString ? mkValueStringDefault { }, + }: + sep: k: v: + "${escape [ sep ] k}${sep}${mkValueString v}"; ## -- FILE FORMAT GENERATORS -- - /** Generate a key-value-style config file from an attrset. @@ -169,19 +182,22 @@ in rec { : indent (optional, default: `""`) : Initial indentation level - */ - toKeyValue = { - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false, - indent ? "" - }: - let mkLine = k: v: indent + mkKeyValue k v + "\n"; - mkLines = if listsAsDuplicateKeys - then k: v: map (mkLine k) (if isList v then v else [v]) - else k: v: [ (mkLine k v) ]; - in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs)); - + toKeyValue = + { + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + indent ? "", + }: + let + mkLine = k: v: indent + mkKeyValue k v + "\n"; + mkLines = + if listsAsDuplicateKeys then + k: v: map (mkLine k) (if isList v then v else [ v ]) + else + k: v: [ (mkLine k v) ]; + in + attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs)); /** Generate an INI-style config file from an @@ -225,22 +241,27 @@ in rec { ::: */ - toINI = { - mkSectionName ? (name: escape [ "[" "]" ] name), - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false - }: attrsOfAttrs: + toINI = + { + mkSectionName ? (name: escape [ "[" "]" ] name), + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + }: + attrsOfAttrs: let - # map function to string for each key val - mapAttrsToStringsSep = sep: mapFn: attrs: - concatStringsSep sep - (mapAttrsToList mapFn attrs); - mkSection = sectName: sectValues: '' + # map function to string for each key val + mapAttrsToStringsSep = + sep: mapFn: attrs: + concatStringsSep sep (mapAttrsToList mapFn attrs); + mkSection = + sectName: sectValues: + '' [${mkSectionName sectName}] - '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; + '' + + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; in - # map input to ini sections - mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; + # map input to ini sections + mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; /** Generate an INI-style config file from an attrset @@ -303,15 +324,22 @@ in rec { `generators.toINI` directly, which only takes the part in `sections`. */ - toINIWithGlobalSection = { - mkSectionName ? (name: escape [ "[" "]" ] name), - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false - }: { globalSection, sections ? {} }: - ( if globalSection == {} - then "" - else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) - + "\n") + toINIWithGlobalSection = + { + mkSectionName ? (name: escape [ "[" "]" ] name), + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + }: + { + globalSection, + sections ? { }, + }: + ( + if globalSection == { } then + "" + else + (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) + "\n" + ) + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections); /** @@ -349,50 +377,57 @@ in rec { : Key-value pairs to be converted to a git-config file. See: https://git-scm.com/docs/git-config#_variables for possible values. - */ - toGitINI = attrs: + toGitINI = + attrs: let - mkSectionName = name: + mkSectionName = + name: let containsQuote = hasInfix ''"'' name; sections = splitString "." name; section = head sections; subsections = tail sections; subsection = concatStringsSep "." subsections; - in if containsQuote || subsections == [ ] then - name - else - ''${section} "${subsection}"''; + in + if containsQuote || subsections == [ ] then name else ''${section} "${subsection}"''; - mkValueString = v: + mkValueString = + v: let - escapedV = '' - "${ - replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v - }"''; - in mkValueStringDefault { } (if isString v then escapedV else v); + escapedV = ''"${replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v}"''; + in + mkValueStringDefault { } (if isString v then escapedV else v); # generation for multiple ini values - mkKeyValue = k: v: - let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k; - in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v)); + mkKeyValue = + k: v: + let + mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k; + in + concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v)); # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI - gitFlattenAttrs = let - recurse = path: value: - if isAttrs value && !isDerivation value then - mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value - else if length path > 1 then { - ${concatStringsSep "." (reverseList (tail path))}.${head path} = value; - } else { - ${head path} = value; - }; - in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs)); + gitFlattenAttrs = + let + recurse = + path: value: + if isAttrs value && !isDerivation value then + mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value + else if length path > 1 then + { + ${concatStringsSep "." (reverseList (tail path))}.${head path} = value; + } + else + { + ${head path} = value; + }; + in + attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs)); toINI_ = toINI { inherit mkKeyValue mkSectionName; }; in - toINI_ (gitFlattenAttrs attrs); + toINI_ (gitFlattenAttrs attrs); /** mkKeyValueDefault wrapper that handles dconf INI quirks. @@ -427,35 +462,39 @@ in rec { withRecursion = { depthLimit, - throwOnDepthLimit ? true + throwOnDepthLimit ? true, }: - assert isInt depthLimit; - let - specialAttrs = [ - "__functor" - "__functionArgs" - "__toString" - "__pretty" - ]; - stepIntoAttr = evalNext: name: - if elem name specialAttrs - then id - else evalNext; - transform = depth: - if depthLimit != null && depth > depthLimit then - if throwOnDepthLimit - then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!" - else const "" - else id; - mapAny = depth: v: - let - evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); - in - if isAttrs v then mapAttrs (stepIntoAttr evalNext) v - else if isList v then map evalNext v - else transform (depth + 1) v; - in - mapAny 0; + assert isInt depthLimit; + let + specialAttrs = [ + "__functor" + "__functionArgs" + "__toString" + "__pretty" + ]; + stepIntoAttr = evalNext: name: if elem name specialAttrs then id else evalNext; + transform = + depth: + if depthLimit != null && depth > depthLimit then + if throwOnDepthLimit then + throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!" + else + const "" + else + id; + mapAny = + depth: v: + let + evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); + in + if isAttrs v then + mapAttrs (stepIntoAttr evalNext) v + else if isList v then + map evalNext v + else + transform (depth + 1) v; + in + mapAny 0; /** Pretty print a value, akin to `builtins.trace`. @@ -481,68 +520,96 @@ in rec { Value : The value to be pretty printed */ - toPretty = { - allowPrettyValues ? false, - multiline ? true, - indent ? "" - }: + toPretty = + { + allowPrettyValues ? false, + multiline ? true, + indent ? "", + }: let - go = indent: v: - let introSpace = if multiline then "\n${indent} " else " "; - outroSpace = if multiline then "\n${indent}" else " "; - in if isInt v then toString v - # toString loses precision on floats, so we use toJSON instead. This isn't perfect - # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for - # pretty-printing purposes this is acceptable. - else if isFloat v then builtins.toJSON v - else if isString v then - let - lines = filter (v: ! isList v) (split "\n" v); - escapeSingleline = escape [ "\\" "\"" "\${" ]; - escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ]; - singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\""; - multilineResult = let - escapedLines = map escapeMultiline lines; - # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer" - # indentation level. Otherwise, '' is appended to the last line. - lastLine = last escapedLines; - in "''" + introSpace + concatStringsSep introSpace (init escapedLines) - + (if lastLine == "" then outroSpace else introSpace + lastLine) + "''"; - in - if multiline && length lines > 1 then multilineResult else singlelineResult - else if true == v then "true" - else if false == v then "false" - else if null == v then "null" - else if isPath v then toString v - else if isList v then - if v == [] then "[ ]" - else "[" + introSpace - + concatMapStringsSep introSpace (go (indent + " ")) v - + outroSpace + "]" - else if isFunction v then - let fna = functionArgs v; - showFnas = concatStringsSep ", " (mapAttrsToList - (name: hasDefVal: if hasDefVal then name + "?" else name) - fna); - in if fna == {} then "" - else "" - else if isAttrs v then - # apply pretty values if allowed - if allowPrettyValues && v ? __pretty && v ? val - then v.__pretty v.val - else if v == {} then "{ }" - else if v ? type && v.type == "derivation" then - "" - else "{" + introSpace - + concatStringsSep introSpace (mapAttrsToList - (name: value: + go = + indent: v: + let + introSpace = if multiline then "\n${indent} " else " "; + outroSpace = if multiline then "\n${indent}" else " "; + in + if isInt v then + toString v + # toString loses precision on floats, so we use toJSON instead. This isn't perfect + # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for + # pretty-printing purposes this is acceptable. + else if isFloat v then + builtins.toJSON v + else if isString v then + let + lines = filter (v: !isList v) (split "\n" v); + escapeSingleline = escape [ + "\\" + "\"" + "\${" + ]; + escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ]; + singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\""; + multilineResult = + let + escapedLines = map escapeMultiline lines; + # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer" + # indentation level. Otherwise, '' is appended to the last line. + lastLine = last escapedLines; + in + "''" + + introSpace + + concatStringsSep introSpace (init escapedLines) + + (if lastLine == "" then outroSpace else introSpace + lastLine) + + "''"; + in + if multiline && length lines > 1 then multilineResult else singlelineResult + else if true == v then + "true" + else if false == v then + "false" + else if null == v then + "null" + else if isPath v then + toString v + else if isList v then + if v == [ ] then + "[ ]" + else + "[" + introSpace + concatMapStringsSep introSpace (go (indent + " ")) v + outroSpace + "]" + else if isFunction v then + let + fna = functionArgs v; + showFnas = concatStringsSep ", " ( + mapAttrsToList (name: hasDefVal: if hasDefVal then name + "?" else name) fna + ); + in + if fna == { } then "" else "" + else if isAttrs v then + # apply pretty values if allowed + if allowPrettyValues && v ? __pretty && v ? val then + v.__pretty v.val + else if v == { } then + "{ }" + else if v ? type && v.type == "derivation" then + "" + else + "{" + + introSpace + + concatStringsSep introSpace ( + mapAttrsToList ( + name: value: "${escapeNixIdentifier name} = ${ - addErrorContext "while evaluating an attribute `${name}`" - (go (indent + " ") value) - };") v) - + outroSpace + "}" - else abort "generators.toPretty: should never happen (v = ${v})"; - in go indent; + addErrorContext "while evaluating an attribute `${name}`" (go (indent + " ") value) + };" + ) v + ) + + outroSpace + + "}" + else + abort "generators.toPretty: should never happen (v = ${v})"; + in + go indent; /** Translate a simple Nix expression to [Plist notation](https://en.wikipedia.org/wiki/Property_list). @@ -557,61 +624,90 @@ in rec { Value : The value to be converted to Plist */ - toPlist = { - escape ? false - }: v: let - expr = ind: x: - if x == null then "" else - if isBool x then bool ind x else - if isInt x then int ind x else - if isString x then str ind x else - if isList x then list ind x else - if isAttrs x then attrs ind x else - if isPath x then str ind (toString x) else - if isFloat x then float ind x else - abort "generators.toPlist: should never happen (v = ${v})"; - - literal = ind: x: ind + x; - - maybeEscapeXML = if escape then escapeXML else x: x; - - bool = ind: x: literal ind (if x then "" else ""); - int = ind: x: literal ind "${toString x}"; - str = ind: x: literal ind "${maybeEscapeXML x}"; - key = ind: x: literal ind "${maybeEscapeXML x}"; - float = ind: x: literal ind "${toString x}"; - - indent = ind: expr "\t${ind}"; - - item = ind: concatMapStringsSep "\n" (indent ind); - - list = ind: x: concatStringsSep "\n" [ - (literal ind "") - (item ind x) - (literal ind "") - ]; - - attrs = ind: x: concatStringsSep "\n" [ - (literal ind "") - (attr ind x) - (literal ind "") - ]; - - attr = let attrFilter = name: value: name != "_module" && value != null; - in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList - (name: value: optionals (attrFilter name value) [ - (key "\t${ind}" name) - (expr "\t${ind}" value) - ]) x)); - - in - # TODO: As discussed in #356502, deprecated functionality should be removed sometime after 25.11. - lib.warnIf (!escape && lib.oldestSupportedReleaseIsAtLeast 2505) "Using `lib.generators.toPlist` without `escape = true` is deprecated" '' - - - - ${expr "" v} - ''; + toPlist = + { + escape ? false, + }: + v: + let + expr = + ind: x: + if x == null then + "" + else if isBool x then + bool ind x + else if isInt x then + int ind x + else if isString x then + str ind x + else if isList x then + list ind x + else if isAttrs x then + attrs ind x + else if isPath x then + str ind (toString x) + else if isFloat x then + float ind x + else + abort "generators.toPlist: should never happen (v = ${v})"; + + literal = ind: x: ind + x; + + maybeEscapeXML = if escape then escapeXML else x: x; + + bool = ind: x: literal ind (if x then "" else ""); + int = ind: x: literal ind "${toString x}"; + str = ind: x: literal ind "${maybeEscapeXML x}"; + key = ind: x: literal ind "${maybeEscapeXML x}"; + float = ind: x: literal ind "${toString x}"; + + indent = ind: expr "\t${ind}"; + + item = ind: concatMapStringsSep "\n" (indent ind); + + list = + ind: x: + concatStringsSep "\n" [ + (literal ind "") + (item ind x) + (literal ind "") + ]; + + attrs = + ind: x: + concatStringsSep "\n" [ + (literal ind "") + (attr ind x) + (literal ind "") + ]; + + attr = + let + attrFilter = name: value: name != "_module" && value != null; + in + ind: x: + concatStringsSep "\n" ( + flatten ( + mapAttrsToList ( + name: value: + optionals (attrFilter name value) [ + (key "\t${ind}" name) + (expr "\t${ind}" value) + ] + ) x + ) + ); + + in + # TODO: As discussed in #356502, deprecated functionality should be removed sometime after 25.11. + lib.warnIf (!escape && lib.oldestSupportedReleaseIsAtLeast 2505) + "Using `lib.generators.toPlist` without `escape = true` is deprecated" + '' + + + + ${expr "" v} + ''; /** Translate a simple Nix expression to Dhall notation. @@ -629,13 +725,14 @@ in rec { : The value to be converted to Dhall */ - toDhall = { }@args: v: - let concatItems = concatStringsSep ", "; - in if isAttrs v then - "{ ${ - concatItems (mapAttrsToList - (key: value: "${key} = ${toDhall args value}") v) - } }" + toDhall = + { }@args: + v: + let + concatItems = concatStringsSep ", "; + in + if isAttrs v then + "{ ${concatItems (mapAttrsToList (key: value: "${key} = ${toDhall args value}") v)} }" else if isList v then "[ ${concatItems (map (toDhall args) v)} ]" else if isInt v then @@ -663,7 +760,6 @@ in rec { Regardless of multiline parameter there is no trailing newline. - # Inputs Structured function argument @@ -711,11 +807,13 @@ in rec { ::: */ - toLua = { - multiline ? true, - indent ? "", - asBindings ? false, - }@args: v: + toLua = + { + multiline ? true, + indent ? "", + asBindings ? false, + }@args: + v: let innerIndent = "${indent} "; introSpace = if multiline then "\n${innerIndent}" else " "; @@ -725,13 +823,16 @@ in rec { asBindings = false; }; concatItems = concatStringsSep ",${introSpace}"; - isLuaInline = { _type ? null, ... }: _type == "lua-inline"; + isLuaInline = + { + _type ? null, + ... + }: + _type == "lua-inline"; generatedBindings = - assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}"; - concatStrings ( - mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v - ); + assert assertMsg (badVarNames == [ ]) "Bad Lua var names: ${toPretty { } badVarNames}"; + concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v); # https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*"; @@ -746,8 +847,12 @@ in rec { else if isPath v || isDerivation v then toJSON "${v}" else if isList v then - (if v == [ ] then "{}" else - "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}") + ( + if v == [ ] then + "{}" + else + "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}" + ) else if isAttrs v then ( if isLuaInline v then @@ -755,9 +860,9 @@ in rec { else if v == { } then "{}" else - "{${introSpace}${concatItems ( - mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v - )}${outroSpace}}" + "{${introSpace}${ + concatItems (mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v) + }${outroSpace}}" ) else abort "generators.toLua: type ${typeOf v} is unsupported"; @@ -765,7 +870,6 @@ in rec { /** Mark string as Lua expression to be inlined when processed by toLua. - # Inputs `expr` @@ -778,8 +882,12 @@ in rec { mkLuaInline :: String -> AttrSet ``` */ - mkLuaInline = expr: { _type = "lua-inline"; inherit expr; }; -} // { + mkLuaInline = expr: { + _type = "lua-inline"; + inherit expr; + }; +} +// { /** Generates JSON from an arbitrary (non-function) value. For more information see the documentation of the builtin. @@ -794,7 +902,7 @@ in rec { : The value to be converted to JSON */ - toJSON = {}: lib.strings.toJSON; + toJSON = { }: lib.strings.toJSON; /** YAML has been a strict superset of JSON since 1.2, so we @@ -812,5 +920,5 @@ in rec { : The value to be converted to YAML */ - toYAML = {}: lib.strings.toJSON; + toYAML = { }: lib.strings.toJSON; } diff --git a/lib/licenses.nix b/lib/licenses.nix index 5cbe345a87d2..c202d8d07180 100644 --- a/lib/licenses.nix +++ b/lib/licenses.nix @@ -2,1450 +2,1464 @@ let inherit (lib) optionalAttrs; - mkLicense = lname: { - shortName ? lname, - # Most of our licenses are Free, explicitly declare unfree additions as such! - free ? true, - deprecated ? false, - spdxId ? null, - url ? null, - fullName ? null, - redistributable ? free - }@attrs: { - inherit shortName free deprecated redistributable; - } // optionalAttrs (attrs ? spdxId) { - inherit spdxId; - url = "https://spdx.org/licenses/${spdxId}.html"; - } // optionalAttrs (attrs ? url) { - inherit url; - } // optionalAttrs (attrs ? fullName) { - inherit fullName; - }; + mkLicense = + lname: + { + shortName ? lname, + # Most of our licenses are Free, explicitly declare unfree additions as such! + free ? true, + deprecated ? false, + spdxId ? null, + url ? null, + fullName ? null, + redistributable ? free, + }@attrs: + { + inherit + shortName + free + deprecated + redistributable + ; + } + // optionalAttrs (attrs ? spdxId) { + inherit spdxId; + url = "https://spdx.org/licenses/${spdxId}.html"; + } + // optionalAttrs (attrs ? url) { + inherit url; + } + // optionalAttrs (attrs ? fullName) { + inherit fullName; + }; in -lib.mapAttrs mkLicense ({ - /** - License identifiers from spdx.org where possible. - If you cannot find your license here, then look for a similar license or - add it to this list. The URL mentioned above is a good source for inspiration. - */ - - abstyles = { - spdxId = "Abstyles"; - fullName = "Abstyles License"; - }; - - acsl14 = { - fullName = "Anti-Capitalist Software License v1.4"; - url = "https://anticapitalist.software/"; +lib.mapAttrs mkLicense ( + { /** - restrictions on corporations apply for both use and redistribution + License identifiers from spdx.org where possible. + If you cannot find your license here, then look for a similar license or + add it to this list. The URL mentioned above is a good source for inspiration. */ - free = false; - redistributable = false; - }; - - activision = { - # https://doomwiki.org/wiki/Raven_source_code_licensing - fullName = "Activision EULA"; - url = "https://www.doomworld.com/eternity/activision_eula.txt"; - free = false; - }; - - afl20 = { - spdxId = "AFL-2.0"; - fullName = "Academic Free License v2.0"; - }; - - afl21 = { - spdxId = "AFL-2.1"; - fullName = "Academic Free License v2.1"; - }; - - afl3 = { - spdxId = "AFL-3.0"; - fullName = "Academic Free License v3.0"; - }; - - agpl3Only = { - spdxId = "AGPL-3.0-only"; - fullName = "GNU Affero General Public License v3.0 only"; - }; - - agpl3Plus = { - spdxId = "AGPL-3.0-or-later"; - fullName = "GNU Affero General Public License v3.0 or later"; - }; - - aladdin = { - spdxId = "Aladdin"; - fullName = "Aladdin Free Public License"; - free = false; - }; - - amazonsl = { - fullName = "Amazon Software License"; - url = "https://aws.amazon.com/asl/"; - free = false; - }; - - amd = { - fullName = "AMD License Agreement"; - url = "https://developer.amd.com/amd-license-agreement/"; - free = false; - }; - - aml = { - spdxId = "AML"; - fullName = "Apple MIT License"; - }; - - ampas = { - spdxId = "AMPAS"; - fullName = "Academy of Motion Picture Arts and Sciences BSD"; - }; - - aom = { - fullName = "Alliance for Open Media Patent License 1.0"; - url = "https://aomedia.org/license/patent-license/"; - }; - - apple-psl10 = { - spdxId = "APSL-1.0"; - fullName = "Apple Public Source License 1.0"; - }; - - apple-psl20 = { - spdxId = "APSL-2.0"; - fullName = "Apple Public Source License 2.0"; - }; - - arphicpl = { - spdxId = "Arphic-1999"; - fullName = "Arphic Public License"; - }; - - artistic1 = { - spdxId = "Artistic-1.0"; - 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"; - }; - - asl20 = { - spdxId = "Apache-2.0"; - fullName = "Apache License 2.0"; - }; - - bitstreamVera = { - spdxId = "Bitstream-Vera"; - fullName = "Bitstream Vera Font License"; - }; - - bitTorrent10 = { - spdxId = "BitTorrent-1.0"; - fullName = " BitTorrent Open Source License v1.0"; - }; - - bitTorrent11 = { - spdxId = "BitTorrent-1.1"; - fullName = " BitTorrent Open Source License v1.1"; - }; - - bola11 = { - url = "https://blitiri.com.ar/p/bola/"; - fullName = "Buena Onda License Agreement 1.1"; - }; - - boost = { - spdxId = "BSL-1.0"; - fullName = "Boost Software License 1.0"; - }; - - beerware = { - spdxId = "Beerware"; - fullName = "Beerware License"; - }; - - blueOak100 = { - spdxId = "BlueOak-1.0.0"; - fullName = "Blue Oak Model License 1.0.0"; - }; - - bsd0 = { - spdxId = "0BSD"; - fullName = "BSD Zero Clause License"; - }; - - bsd1 = { - spdxId = "BSD-1-Clause"; - fullName = "BSD 1-Clause License"; - }; - - bsd2 = { - spdxId = "BSD-2-Clause"; - fullName = ''BSD 2-clause "Simplified" License''; - }; - - bsd2Patent = { - spdxId = "BSD-2-Clause-Patent"; - fullName = "BSD-2-Clause Plus Patent License"; - }; - - bsd2WithViews = { - spdxId = "BSD-2-Clause-Views"; - fullName = "BSD 2-Clause with views sentence"; - }; - - bsd3 = { - spdxId = "BSD-3-Clause"; - fullName = ''BSD 3-clause "New" or "Revised" License''; - }; - - bsd3Clear = { - spdxId = "BSD-3-Clause-Clear"; - fullName = "BSD 3-Clause Clear License"; - }; - - bsd3Lbnl = { - spdxId = "BSD-3-Clause-LBNL"; - fullName = "Lawrence Berkeley National Labs BSD variant license"; - }; - - bsdAxisNoDisclaimerUnmodified = { - fullName = "BSD-Axis without Warranty Disclaimer with Unmodified requirement"; - url = "https://scancode-licensedb.aboutcode.org/bsd-no-disclaimer-unmodified.html"; - }; - - bsdOriginal = { - spdxId = "BSD-4-Clause"; - fullName = ''BSD 4-clause "Original" or "Old" License''; - }; - - bsdOriginalShortened = { - spdxId = "BSD-4-Clause-Shortened"; - fullName = "BSD 4 Clause Shortened"; - }; - - bsdOriginalUC = { - spdxId = "BSD-4-Clause-UC"; - fullName = "BSD 4-Clause University of California-Specific"; - }; - - bsdProtection = { - spdxId = "BSD-Protection"; - fullName = "BSD Protection License"; - }; - - bsl11 = { - spdxId = "BUSL-1.1"; - fullName = "Business Source License 1.1"; - free = false; - redistributable = true; - }; - - caossl = { - fullName = "Computer Associates Open Source Licence Version 1.0"; - url = "http://jxplorer.org/licence.html"; - }; - - cal10 = { - spdxId = "CAL-1.0"; - fullName = "Cryptographic Autonomy License version 1.0 (CAL-1.0)"; - }; - - caldera = { - spdxId = "Caldera"; - fullName = "Caldera License"; - }; - - capec = { - fullName = "Common Attack Pattern Enumeration and Classification"; - url = "https://capec.mitre.org/about/termsofuse.html"; - }; - - clArtistic = { - spdxId = "ClArtistic"; - fullName = "Clarified Artistic License"; - }; - - cc0 = { - spdxId = "CC0-1.0"; - fullName = "Creative Commons Zero v1.0 Universal"; - }; - - cc-by-nc-nd-30 = { - spdxId = "CC-BY-NC-ND-3.0"; - fullName = "Creative Commons Attribution Non Commercial No Derivative Works 3.0 Unported"; - free = false; - }; - - cc-by-nc-nd-40 = { - spdxId = "CC-BY-NC-ND-4.0"; - fullName = "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International"; - free = false; - }; - - cc-by-nc-sa-20 = { - spdxId = "CC-BY-NC-SA-2.0"; - fullName = "Creative Commons Attribution Non Commercial Share Alike 2.0"; - free = false; - }; - - cc-by-nc-sa-25 = { - spdxId = "CC-BY-NC-SA-2.5"; - fullName = "Creative Commons Attribution Non Commercial Share Alike 2.5"; - free = false; - }; - - cc-by-nc-sa-30 = { - spdxId = "CC-BY-NC-SA-3.0"; - fullName = "Creative Commons Attribution Non Commercial Share Alike 3.0"; - free = false; - }; - - cc-by-nc-sa-40 = { - spdxId = "CC-BY-NC-SA-4.0"; - fullName = "Creative Commons Attribution Non Commercial Share Alike 4.0"; - free = false; - }; - - cc-by-nc-30 = { - spdxId = "CC-BY-NC-3.0"; - fullName = "Creative Commons Attribution Non Commercial 3.0 Unported"; - free = false; - }; - - cc-by-nc-40 = { - spdxId = "CC-BY-NC-4.0"; - fullName = "Creative Commons Attribution Non Commercial 4.0 International"; - free = false; - }; - - cc-by-nd-30 = { - spdxId = "CC-BY-ND-3.0"; - fullName = "Creative Commons Attribution-No Derivative Works v3.00"; - free = false; - }; - - cc-by-nd-40 = { - spdxId = "CC-BY-ND-4.0"; - fullName = "Creative Commons Attribution-No Derivative Works v4.0"; - 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-20 = { - spdxId = "CC-BY-2.0"; - fullName = "Creative Commons Attribution 2.0"; - }; - - cc-by-30 = { - spdxId = "CC-BY-3.0"; - fullName = "Creative Commons Attribution 3.0"; - }; - - cc-by-sa-30 = { - spdxId = "CC-BY-SA-3.0"; - fullName = "Creative Commons Attribution Share Alike 3.0"; - }; - - cc-by-40 = { - spdxId = "CC-BY-4.0"; - fullName = "Creative Commons Attribution 4.0"; - }; - - cc-by-sa-40 = { - spdxId = "CC-BY-SA-4.0"; - fullName = "Creative Commons Attribution Share Alike 4.0"; - }; - - cc-sa-10 = { - shortName = "CC-SA-1.0"; - fullName = "Creative Commons Share Alike 1.0"; - url = "https://creativecommons.org/licenses/sa/1.0"; - }; - - cddl = { - spdxId = "CDDL-1.0"; - fullName = "Common Development and Distribution License 1.0"; - }; - - cecill20 = { - spdxId = "CECILL-2.0"; - fullName = "CeCILL Free Software License Agreement v2.0"; - }; - - cecill21 = { - spdxId = "CECILL-2.1"; - fullName = "CeCILL Free Software License Agreement v2.1"; - }; - - cecill-b = { - spdxId = "CECILL-B"; - fullName = "CeCILL-B Free Software License Agreement"; - }; - - cecill-c = { - spdxId = "CECILL-C"; - fullName = "CeCILL-C Free Software License Agreement"; - }; - - cockroachdb-community-license = { - fullName = "CockroachDB Community License Agreement"; - url = "https://www.cockroachlabs.com/cockroachdb-community-license/"; - free = false; - }; - - cpal10 = { - spdxId = "CPAL-1.0"; - fullName = "Common Public Attribution License 1.0"; - }; - - commons-clause = { - fullName = "Commons Clause License"; - url = "https://commonsclause.com/"; - free = false; - }; - - cpl10 = { - spdxId = "CPL-1.0"; - fullName = "Common Public License 1.0"; - }; - - curl = { - spdxId = "curl"; - fullName = "curl License"; - }; - - doc = { - spdxId = "DOC"; - fullName = "DOC License"; - }; - - drl10 = { - spdxId = "DRL-1.0"; - fullName = "Detection Rule License 1.0"; - }; - - dtoa = { - spdxId = "dtoa"; - fullName = "dtoa License"; - }; - - eapl = { - fullName = "EPSON AVASYS PUBLIC LICENSE"; - url = "https://avasys.jp/hp/menu000000700/hpg000000603.htm"; - free = false; - }; - - ecl20 = { - fullName = "Educational Community License, Version 2.0"; - shortName = "ECL 2.0"; - spdxId = "ECL-2.0"; - }; - - efl10 = { - spdxId = "EFL-1.0"; - fullName = "Eiffel Forum License v1.0"; - }; - - efl20 = { - spdxId = "EFL-2.0"; - fullName = "Eiffel Forum License v2.0"; - }; - - elastic20 = { - spdxId = "Elastic-2.0"; - fullName = "Elastic License 2.0"; - free = false; - }; - - epl10 = { - spdxId = "EPL-1.0"; - fullName = "Eclipse Public License 1.0"; - }; - - epl20 = { - spdxId = "EPL-2.0"; - fullName = "Eclipse Public License 2.0"; - }; - - epson = { - fullName = "Seiko Epson Corporation Software License Agreement for Linux"; - url = "https://download.ebz.epson.net/dsc/du/02/eula/global/LINUX_EN.html"; - free = false; - }; - - eupl11 = { - spdxId = "EUPL-1.1"; - fullName = "European Union Public License 1.1"; - }; - - eupl12 = { - spdxId = "EUPL-1.2"; - fullName = "European Union Public License 1.2"; - }; - - fdl11Only = { - spdxId = "GFDL-1.1-only"; - fullName = "GNU Free Documentation License v1.1 only"; - }; - - fdl11Plus = { - spdxId = "GFDL-1.1-or-later"; - fullName = "GNU Free Documentation License v1.1 or later"; - }; - - fdl12Only = { - spdxId = "GFDL-1.2-only"; - fullName = "GNU Free Documentation License v1.2 only"; - }; - - fdl12Plus = { - spdxId = "GFDL-1.2-or-later"; - fullName = "GNU Free Documentation License v1.2 or later"; - }; - - fdl13Only = { - spdxId = "GFDL-1.3-only"; - fullName = "GNU Free Documentation License v1.3 only"; - }; - - fdl13Plus = { - spdxId = "GFDL-1.3-or-later"; - fullName = "GNU Free Documentation License v1.3 or later"; - }; - - ffsl = { - fullName = "Floodgap Free Software License"; - url = "https://www.floodgap.com/software/ffsl/license.html"; - free = false; - }; - - fraunhofer-fdk = { - fullName = "Fraunhofer FDK AAC Codec Library"; - spdxId = "FDK-AAC"; - }; - - free = { - fullName = "Unspecified free software license"; - }; - - fsl11Mit = { - fullName = "Functional Source License, Version 1.1, MIT Future License"; - url = "https://fsl.software/FSL-1.1-MIT.template.md"; - free = false; - redistributable = true; - }; - - fsl11Asl20 = { - fullName = "Functional Source License, Version 1.1, Apache 2.0 Future License"; - url = "https://fsl.software/FSL-1.1-Apache-2.0.template.md"; - free = false; - redistributable = true; - }; - - ftl = { - spdxId = "FTL"; - fullName = "Freetype Project License"; - }; - - g4sl = { - fullName = "Geant4 Software License"; - url = "https://geant4.web.cern.ch/geant4/license/LICENSE.html"; - }; - - geogebra = { - fullName = "GeoGebra Non-Commercial License Agreement"; - url = "https://www.geogebra.org/license"; - free = false; - }; - - generaluser = { - fullName = "GeneralUser GS License v2.0"; - url = "https://www.schristiancollins.com/generaluser.php"; # license included in sources - }; - - gfl = { - fullName = "GUST Font License"; - url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-LICENSE.txt"; - }; - - gfsl = { - fullName = "GUST Font Source License"; - url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-SOURCE-LICENSE.txt"; - }; - - gpl1Only = { - spdxId = "GPL-1.0-only"; - fullName = "GNU General Public License v1.0 only"; - }; - - gpl1Plus = { - spdxId = "GPL-1.0-or-later"; - fullName = "GNU General Public License v1.0 or later"; - }; - - gpl2Only = { - spdxId = "GPL-2.0-only"; - fullName = "GNU General Public License v2.0 only"; - }; - - gpl2Classpath = { - spdxId = "GPL-2.0-with-classpath-exception"; - fullName = "GNU General Public License v2.0 only (with Classpath exception)"; - }; - - gpl2ClasspathPlus = { - fullName = "GNU General Public License v2.0 or later (with Classpath exception)"; - url = "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception"; - }; - - gpl2Oss = { - fullName = "GNU General Public License version 2 only (with OSI approved licenses linking exception)"; - url = "https://www.mysql.com/about/legal/licensing/foss-exception"; - }; - - gpl2Plus = { - spdxId = "GPL-2.0-or-later"; - fullName = "GNU General Public License v2.0 or later"; - }; - - gpl3Only = { - spdxId = "GPL-3.0-only"; - fullName = "GNU General Public License v3.0 only"; - }; - - gpl3Plus = { - spdxId = "GPL-3.0-or-later"; - fullName = "GNU General Public License v3.0 or later"; - }; - - gpl3ClasspathPlus = { - fullName = "GNU General Public License v3.0 or later (with Classpath exception)"; - url = "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception"; - }; - - giftware = { - spdxId = "Giftware"; - fullName = "Giftware License"; - }; - - hpnd = { - spdxId = "HPND"; - fullName = "Historic Permission Notice and Disclaimer"; - }; - - hpndSellVariant = { - fullName = "Historical Permission Notice and Disclaimer - sell variant"; - spdxId = "HPND-sell-variant"; - }; - - hpndUc = { - spdxId = "HPND-UC"; - fullName = "Historical Permission Notice and Disclaimer - University of California variant"; - }; - - # Intel's license, seems free - iasl = { - spdxId = "Intel-ACPI"; - fullName = "Intel ACPI Software License Agreement"; - }; - - icu = { - spdxId = "ICU"; - fullName = "ICU"; - }; - - ijg = { - spdxId = "IJG"; - fullName = "Independent JPEG Group License"; - }; - - imagemagick = { - fullName = "ImageMagick License"; - spdxId = "ImageMagick"; - }; - - imlib2 = { - spdxId = "Imlib2"; - fullName = "Imlib2 License"; - }; - - info-zip = { - spdxId = "Info-ZIP"; - fullName = "Info-ZIP License"; - }; - - inria-compcert = { - fullName = "INRIA Non-Commercial License Agreement for the CompCert verified compiler"; - url = "https://compcert.org/doc/LICENSE.txt"; - free = false; - }; - - inria-icesl = { - fullName = "End User License Agreement for IceSL Software"; - url = "https://icesl.loria.fr/assets/pdf/EULA_IceSL_binary.pdf"; - free = false; - }; - - inria-zelus = { - fullName = "INRIA Non-Commercial License Agreement for the Zélus compiler"; - url = "https://github.com/INRIA/zelus/raw/829f2b97cba93b0543a9ca0272269e6b8fdad356/LICENSE"; - free = false; - }; - - ipa = { - spdxId = "IPA"; - fullName = "IPA Font License"; - }; - - ipl10 = { - spdxId = "IPL-1.0"; - fullName = "IBM Public License v1.0"; - }; - - isc = { - spdxId = "ISC"; - fullName = "ISC License"; - }; - - databricks = { - fullName = "Databricks License"; - url = "https://www.databricks.com/legal/db-license"; - free = false; - }; - - databricks-dbx = { - fullName = "DataBricks eXtensions aka dbx License"; - url = "https://github.com/databrickslabs/dbx/blob/743b579a4ac44531f764c6e522dbe5a81a7dc0e4/LICENSE"; - free = false; - redistributable = false; - }; - - databricks-license = { - fullName = "Databricks License"; - url = "https://www.databricks.com/legal/db-license"; - free = false; - }; - - fair = { - fullName = "Fair License"; - spdxId = "Fair"; - 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"; - }; - - lal13 = { - spdxId = "LAL-1.3"; - fullName = "Licence Art Libre 1.3"; - }; - - lens = { - fullName = "Lens Terms of Service Agreement"; - url = "https://k8slens.dev/legal/tos"; - free = false; - }; - - lgpl2Only = { - spdxId = "LGPL-2.0-only"; - fullName = "GNU Library General Public License v2 only"; - }; - - lgpl2Plus = { - spdxId = "LGPL-2.0-or-later"; - fullName = "GNU Library General Public License v2 or later"; - }; - - lgpl21Only = { - spdxId = "LGPL-2.1-only"; - fullName = "GNU Lesser General Public License v2.1 only"; - }; - - lgpl21Plus = { - spdxId = "LGPL-2.1-or-later"; - fullName = "GNU Lesser General Public License v2.1 or later"; - }; - - lgpl3Only = { - spdxId = "LGPL-3.0-only"; - fullName = "GNU Lesser General Public License v3.0 only"; - }; - - lgpl3Plus = { - spdxId = "LGPL-3.0-or-later"; - fullName = "GNU Lesser General Public License v3.0 or later"; - }; - - lgpllr = { - spdxId = "LGPLLR"; - fullName = "Lesser General Public License For Linguistic Resources"; - }; - - libpng = { - spdxId = "Libpng"; - fullName = "libpng License"; - }; - - libpng2 = { - spdxId = "libpng-2.0"; # Used since libpng 1.6.36. - fullName = "PNG Reference Library version 2"; - }; - - libtiff = { - spdxId = "libtiff"; - fullName = "libtiff License"; - }; - - llgpl21 = { - fullName = "Lisp LGPL; GNU Lesser General Public License version 2.1 with Franz Inc. preamble for clarification of LGPL terms in context of Lisp"; - url = "https://opensource.franz.com/preamble.html"; - }; - - llvm-exception = { - spdxId = "LLVM-exception"; - fullName = "LLVM Exception"; # LLVM exceptions to the Apache 2.0 License - }; - - 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"; - }; - - lpl-102 = { - spdxId = "LPL-1.02"; - fullName = "Lucent Public License v1.02"; - }; - - miros = { - spdxId = "MirOS"; - fullName = "MirOS License"; - }; - - mit = { - spdxId = "MIT"; - fullName = "MIT License"; - }; - - mit-cmu = { - spdxId = "MIT-CMU"; - fullName = "CMU License"; - }; - - mit-feh = { - spdxId = "MIT-feh"; - fullName = "feh License"; - }; - - mit-modern = { - # Also known as Zsh license - spdxId = "MIT-Modern-Variant"; - fullName = "MIT License Modern Variant"; - }; - - mitAdvertising = { - spdxId = "MIT-advertising"; - fullName = "Enlightenment License (e16)"; - }; - - mit0 = { - spdxId = "MIT-0"; - fullName = "MIT No Attribution"; - }; - - mpl10 = { - spdxId = "MPL-1.0"; - fullName = "Mozilla Public License 1.0"; - }; - - mpl11 = { - spdxId = "MPL-1.1"; - fullName = "Mozilla Public License 1.1"; - }; - - mpl20 = { - spdxId = "MPL-2.0"; - fullName = "Mozilla Public License 2.0"; - }; - - mplus = { - spdxId = "mplus"; - fullName = "M+ Font License"; - }; - - mspl = { - spdxId = "MS-PL"; - fullName = "Microsoft Public License"; - }; - - mulan-psl2 = { - spdxId = "MulanPSL-2.0"; - fullName = "Mulan Permissive Software License, Version 2"; - }; - - naist-2003 = { - spdxId = "NAIST-2003"; - fullName = "Nara Institute of Science and Technology License (2003)"; - }; - - nasa13 = { - spdxId = "NASA-1.3"; - fullName = "NASA Open Source Agreement 1.3"; - free = false; - }; - - ncbiPd = { - spdxId = "NCBI-PD"; - fullName = "NCBI Public Domain Notice"; - # Due to United States copyright law, anything with this "license" does not have a copyright in the - # jurisdiction of the United States. However, other jurisdictions may assign the United States - # government copyright to the work, and the license explicitly states that in such a case, no license - # is granted. This is nonfree and nonredistributable in most jurisdictions other than the United States. - free = false; - redistributable = false; - }; - - ncsa = { - spdxId = "NCSA"; - fullName = "University of Illinois/NCSA Open Source License"; - }; - - ncul1 = { - spdxId = "NCUL1"; - fullName = "Netdata Cloud UI License v1.0"; - free = false; - redistributable = true; # Only if used in Netdata products. - }; - - nistSoftware = { - spdxId = "NIST-Software"; - fullName = "NIST Software License"; - }; - - nlpl = { - spdxId = "NLPL"; - fullName = "No Limit Public License"; - }; - - nposl3 = { - spdxId = "NPOSL-3.0"; - fullName = "Non-Profit Open Software License 3.0"; - }; - - nvidiaCuda = { - shortName = "CUDA EULA"; - fullName = "CUDA Toolkit End User License Agreement (EULA)"; - url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; - free = false; - }; - - nvidiaCudaRedist = { - shortName = "CUDA EULA"; - fullName = "CUDA Toolkit End User License Agreement (EULA)"; - url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; - free = false; - redistributable = true; - }; - - obsidian = { - fullName = "Obsidian End User Agreement"; - url = "https://obsidian.md/eula"; - free = false; - }; - - ocamlLgplLinkingException = { - spdxId = "OCaml-LGPL-linking-exception"; - fullName = "OCaml LGPL Linking Exception"; - }; - - ocamlpro_nc = { - fullName = "OCamlPro Non Commercial license version 1"; - url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf"; - free = false; - }; - - odbl = { - spdxId = "ODbL-1.0"; - fullName = "Open Data Commons Open Database License v1.0"; - }; - - ofl = { - spdxId = "OFL-1.1"; - fullName = "SIL Open Font License 1.1"; - }; - - oml = { - spdxId = "OML"; - fullName = "Open Market License"; - }; - - openldap = { - spdxId = "OLDAP-2.8"; - fullName = "Open LDAP Public License v2.8"; - }; - - openssl = { - spdxId = "OpenSSL"; - 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"; - }; - - osl21 = { - spdxId = "OSL-2.1"; - fullName = "Open Software License 2.1"; - }; - - osl3 = { - spdxId = "OSL-3.0"; - fullName = "Open Software License 3.0"; - }; - - parity70 = { - spdxId = "Parity-7.0.0"; - fullName = "Parity Public License 7.0.0"; - }; - - php301 = { - spdxId = "PHP-3.01"; - fullName = "PHP License v3.01"; - }; - - postgresql = { - spdxId = "PostgreSQL"; - fullName = "PostgreSQL License"; - }; - - postman = { - fullName = "Postman EULA"; - url = "https://www.getpostman.com/licenses/postman_base_app"; - free = false; - }; - - psfl = { - spdxId = "Python-2.0"; - fullName = "Python Software Foundation License version 2"; - }; - - publicDomain = { - fullName = "Public Domain"; - }; - - purdueBsd = { - fullName = "Purdue BSD-Style License"; # also known as lsof license - url = "https://enterprise.dejacode.com/licenses/public/purdue-bsd"; - }; - - prosperity30 = { - fullName = "Prosperity-3.0.0"; - free = false; - url = "https://prosperitylicense.com/versions/3.0.0.html"; - }; - - qhull = { - spdxId = "Qhull"; - fullName = "Qhull License"; - }; - - qpl = { - spdxId = "QPL-1.0"; - fullName = "Q Public License 1.0"; - }; - - qwt = { - fullName = "Qwt License, Version 1.0"; - url = "https://qwt.sourceforge.io/qwtlicense.html"; - }; - - radiance = { - fullName = "The Radiance Software License, Version 2.0"; - url = "https://github.com/LBNL-ETA/Radiance/blob/master/License.txt"; - }; - - ruby = { - spdxId = "Ruby"; - fullName = "Ruby License"; - }; - - sendmail = { - spdxId = "Sendmail"; - fullName = "Sendmail License"; - }; - - sgi-b-20 = { - spdxId = "SGI-B-2.0"; - fullName = "SGI Free Software License B v2.0"; - }; - - # Gentoo seems to treat it as a license: - # https://gitweb.gentoo.org/repo/gentoo.git/tree/licenses/SGMLUG?id=7d999af4a47bf55e53e54713d98d145f935935c1 - sgmlug = { - fullName = "SGML UG SGML Parser Materials license"; - }; - - sleepycat = { - spdxId = "Sleepycat"; - fullName = "Sleepycat License"; - }; - - smail = { - shortName = "smail"; - fullName = "SMAIL General Public License"; - url = "https://sources.debian.org/copyright/license/debianutils/4.9.1/"; - }; - - smlnj = { - spdxId = "SMLNJ"; - fullName = "Standard ML of New Jersey License"; - }; - - sspl = { - shortName = "SSPL"; - fullName = "Server Side Public License"; - url = "https://www.mongodb.com/licensing/server-side-public-license"; - free = false; - # NOTE Debatable. - # The license a slightly modified AGPL but still considered unfree by the - # OSI for what seem like political reasons - redistributable = true; # Definitely redistributable though, it's an AGPL derivative - }; - - stk = { - shortName = "stk"; - fullName = "Synthesis Tool Kit 4.3"; - url = "https://github.com/thestk/stk/blob/master/LICENSE"; - }; - - sudo = { - shortName = "sudo"; - fullName = "Sudo License (ISC-style)"; - url = "https://www.sudo.ws/about/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" - }; - - teamspeak = { - fullName = "Teamspeak client license"; - url = "https://www.teamspeak.com/en/privacy-and-terms/"; - free = false; - redistributable = true; # we got a permit to redistribute it: - # License issues: - # Date: Mon, 10 Dec 2007 19:55:16 -0500 - # From: TeamSpeak Sales - # To: 'Marc Weber' - # Subject: RE: teamspeak on nix? - # - # Yes, that would be fine. As long as you are not renting servers or selling - # TeamSpeak then you are more than welcome to distribute it. - # - # Thank you, - # - # TeamSpeak Sales Team - # ________________________________ - # e-Mail: sales@tritoncia.com - # TeamSpeak: http://www.TeamSpeak.com - # Account Login: https://sales.TritonCIA.com/users - # - # - # - # -----Original Message----- - # From: Marc Weber [mailto:marco-oweber@gmx.de] - # Sent: Monday, December 10, 2007 5:03 PM - # To: sales@tritoncia.com - # Subject: teamspeak on nix? - # - # Hello, - # - # nix is very young software distribution system (http://nix.cs.uu.nl/) - # I'd like to ask wether you permit us to add teamspeak (server/ client?) - # - # Sincerly - # Marc Weber (small nix contributor) - }; - - tsl = { - shortName = "TSL"; - fullName = "Timescale License Agreegment"; - url = "https://github.com/timescale/timescaledb/blob/main/tsl/LICENSE-TIMESCALE"; - free = false; - }; - - tcltk = { - spdxId = "TCL"; - fullName = "TCL/TK License"; - }; - - tost = { - fullName = "Tomorrow Open Source Technology License 1.0"; - url = "https://github.com/PixarAnimationStudios/OpenUSD/blob/release/LICENSE.txt"; - }; - - ucd = { - fullName = "Unicode Character Database License"; - url = "https://fedoraproject.org/wiki/Licensing:UCD"; - }; - - ufl = { - spdxId = "Ubuntu-font-1.0"; - fullName = "Ubuntu Font License 1.0"; - }; - - unfree = { - fullName = "Unfree"; - free = false; - }; - - unfreeRedistributable = { - fullName = "Unfree redistributable"; - free = false; - redistributable = true; - }; - - unfreeRedistributableFirmware = { - fullName = "Unfree redistributable firmware"; - redistributable = true; - # Note: we currently consider these "free" for inclusion in the - # channel and NixOS images. - }; - - unicode-30 = { - spdxId = "Unicode-3.0"; - fullName = "Unicode License v3"; - }; - - unicode-dfs-2015 = { - spdxId = "Unicode-DFS-2015"; - fullName = "Unicode License Agreement - Data Files and Software (2015)"; - }; - - unicode-dfs-2016 = { - spdxId = "Unicode-DFS-2016"; - fullName = "Unicode License Agreement - Data Files and Software (2016)"; - }; - - unlicense = { - spdxId = "Unlicense"; - fullName = "The Unlicense"; - }; - - upl = { - spdxId = "UPL-1.0"; - fullName = "Universal Permissive License"; - }; - - vim = { - spdxId = "Vim"; - fullName = "Vim License"; - }; - - virtualbox-puel = { - fullName = "Oracle VM VirtualBox Extension Pack Personal Use and Evaluation License (PUEL)"; - url = "https://www.virtualbox.org/wiki/VirtualBox_PUEL"; - free = false; - }; - - vol-sl = { - fullName = "Volatility Software License, Version 1.0"; - url = "https://www.volatilityfoundation.org/license/vsl-v1.0"; - }; - - vsl10 = { - spdxId = "VSL-1.0"; - fullName = "Vovida Software License v1.0"; - }; - - watcom = { - spdxId = "Watcom-1.0"; - fullName = "Sybase Open Watcom Public License 1.0"; - # Despite being OSI‐approved, this licence is not considered FOSS - # by Debian, Fedora, or the FSF, due to an onerous restriction that - # requires publication of even privately‐deployed modifications. - # This violates the FSF’s freedom 3 and Debian’s “desert island - # test” and “dissident test”. - # - # See: - free = false; - redistributable = true; - }; - - w3c = { - spdxId = "W3C"; - fullName = "W3C Software Notice and License"; - }; - - wadalab = { - fullName = "Wadalab Font License"; - url = "https://fedoraproject.org/wiki/Licensing:Wadalab?rd=Licensing/Wadalab"; - }; - - wtfpl = { - spdxId = "WTFPL"; - fullName = "Do What The F*ck You Want To Public License"; - }; - - wxWindows = { - spdxId = "wxWindows"; - fullName = "wxWindows Library Licence, Version 3.1"; - }; - - x11 = { - spdxId = "X11"; - fullName = "X11 License"; - }; - - xfig = { - spdxId = "Xfig"; - fullName = "xfig"; - }; - - xinetd = { - spdxId = "xinetd"; - fullName = "xinetd License"; - }; - - xskat = { - spdxId = "XSkat"; - fullName = "XSkat License"; - }; - - zlib = { - spdxId = "Zlib"; - fullName = "zlib License"; - }; - - zpl20 = { - spdxId = "ZPL-2.0"; - fullName = "Zope Public License 2.0"; - }; - - zpl21 = { - spdxId = "ZPL-2.1"; - fullName = "Zope Public License 2.1"; - }; - -} // { - # TODO: remove legacy aliases - apsl10 = { - # deprecated for consistency with `apple-psl20`; use `apple-psl10` - spdxId = "APSL-1.0"; - fullName = "Apple Public Source License 1.0"; - deprecated = true; - }; - apsl20 = { - # deprecated due to confusion with Apache-2.0; use `apple-psl20` - spdxId = "APSL-2.0"; - fullName = "Apple Public Source License 2.0"; - deprecated = true; - }; - gpl2 = { - spdxId = "GPL-2.0"; - fullName = "GNU General Public License v2.0"; - deprecated = true; - }; - gpl3 = { - spdxId = "GPL-3.0"; - fullName = "GNU General Public License v3.0"; - deprecated = true; - }; - lgpl2 = { - spdxId = "LGPL-2.0"; - fullName = "GNU Library General Public License v2"; - deprecated = true; - }; - lgpl21 = { - spdxId = "LGPL-2.1"; - fullName = "GNU Lesser General Public License v2.1"; - deprecated = true; - }; - lgpl3 = { - spdxId = "LGPL-3.0"; - fullName = "GNU Lesser General Public License v3.0"; - deprecated = true; - }; -}) + + abstyles = { + spdxId = "Abstyles"; + fullName = "Abstyles License"; + }; + + acsl14 = { + fullName = "Anti-Capitalist Software License v1.4"; + url = "https://anticapitalist.software/"; + /** + restrictions on corporations apply for both use and redistribution + */ + free = false; + redistributable = false; + }; + + activision = { + # https://doomwiki.org/wiki/Raven_source_code_licensing + fullName = "Activision EULA"; + url = "https://www.doomworld.com/eternity/activision_eula.txt"; + free = false; + }; + + afl20 = { + spdxId = "AFL-2.0"; + fullName = "Academic Free License v2.0"; + }; + + afl21 = { + spdxId = "AFL-2.1"; + fullName = "Academic Free License v2.1"; + }; + + afl3 = { + spdxId = "AFL-3.0"; + fullName = "Academic Free License v3.0"; + }; + + agpl3Only = { + spdxId = "AGPL-3.0-only"; + fullName = "GNU Affero General Public License v3.0 only"; + }; + + agpl3Plus = { + spdxId = "AGPL-3.0-or-later"; + fullName = "GNU Affero General Public License v3.0 or later"; + }; + + aladdin = { + spdxId = "Aladdin"; + fullName = "Aladdin Free Public License"; + free = false; + }; + + amazonsl = { + fullName = "Amazon Software License"; + url = "https://aws.amazon.com/asl/"; + free = false; + }; + + amd = { + fullName = "AMD License Agreement"; + url = "https://developer.amd.com/amd-license-agreement/"; + free = false; + }; + + aml = { + spdxId = "AML"; + fullName = "Apple MIT License"; + }; + + ampas = { + spdxId = "AMPAS"; + fullName = "Academy of Motion Picture Arts and Sciences BSD"; + }; + + aom = { + fullName = "Alliance for Open Media Patent License 1.0"; + url = "https://aomedia.org/license/patent-license/"; + }; + + apple-psl10 = { + spdxId = "APSL-1.0"; + fullName = "Apple Public Source License 1.0"; + }; + + apple-psl20 = { + spdxId = "APSL-2.0"; + fullName = "Apple Public Source License 2.0"; + }; + + arphicpl = { + spdxId = "Arphic-1999"; + fullName = "Arphic Public License"; + }; + + artistic1 = { + spdxId = "Artistic-1.0"; + 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"; + }; + + asl20 = { + spdxId = "Apache-2.0"; + fullName = "Apache License 2.0"; + }; + + bitstreamVera = { + spdxId = "Bitstream-Vera"; + fullName = "Bitstream Vera Font License"; + }; + + bitTorrent10 = { + spdxId = "BitTorrent-1.0"; + fullName = " BitTorrent Open Source License v1.0"; + }; + + bitTorrent11 = { + spdxId = "BitTorrent-1.1"; + fullName = " BitTorrent Open Source License v1.1"; + }; + + bola11 = { + url = "https://blitiri.com.ar/p/bola/"; + fullName = "Buena Onda License Agreement 1.1"; + }; + + boost = { + spdxId = "BSL-1.0"; + fullName = "Boost Software License 1.0"; + }; + + beerware = { + spdxId = "Beerware"; + fullName = "Beerware License"; + }; + + blueOak100 = { + spdxId = "BlueOak-1.0.0"; + fullName = "Blue Oak Model License 1.0.0"; + }; + + bsd0 = { + spdxId = "0BSD"; + fullName = "BSD Zero Clause License"; + }; + + bsd1 = { + spdxId = "BSD-1-Clause"; + fullName = "BSD 1-Clause License"; + }; + + bsd2 = { + spdxId = "BSD-2-Clause"; + fullName = ''BSD 2-clause "Simplified" License''; + }; + + bsd2Patent = { + spdxId = "BSD-2-Clause-Patent"; + fullName = "BSD-2-Clause Plus Patent License"; + }; + + bsd2WithViews = { + spdxId = "BSD-2-Clause-Views"; + fullName = "BSD 2-Clause with views sentence"; + }; + + bsd3 = { + spdxId = "BSD-3-Clause"; + fullName = ''BSD 3-clause "New" or "Revised" License''; + }; + + bsd3Clear = { + spdxId = "BSD-3-Clause-Clear"; + fullName = "BSD 3-Clause Clear License"; + }; + + bsd3Lbnl = { + spdxId = "BSD-3-Clause-LBNL"; + fullName = "Lawrence Berkeley National Labs BSD variant license"; + }; + + bsdAxisNoDisclaimerUnmodified = { + fullName = "BSD-Axis without Warranty Disclaimer with Unmodified requirement"; + url = "https://scancode-licensedb.aboutcode.org/bsd-no-disclaimer-unmodified.html"; + }; + + bsdOriginal = { + spdxId = "BSD-4-Clause"; + fullName = ''BSD 4-clause "Original" or "Old" License''; + }; + + bsdOriginalShortened = { + spdxId = "BSD-4-Clause-Shortened"; + fullName = "BSD 4 Clause Shortened"; + }; + + bsdOriginalUC = { + spdxId = "BSD-4-Clause-UC"; + fullName = "BSD 4-Clause University of California-Specific"; + }; + + bsdProtection = { + spdxId = "BSD-Protection"; + fullName = "BSD Protection License"; + }; + + bsl11 = { + spdxId = "BUSL-1.1"; + fullName = "Business Source License 1.1"; + free = false; + redistributable = true; + }; + + caossl = { + fullName = "Computer Associates Open Source Licence Version 1.0"; + url = "http://jxplorer.org/licence.html"; + }; + + cal10 = { + spdxId = "CAL-1.0"; + fullName = "Cryptographic Autonomy License version 1.0 (CAL-1.0)"; + }; + + caldera = { + spdxId = "Caldera"; + fullName = "Caldera License"; + }; + + capec = { + fullName = "Common Attack Pattern Enumeration and Classification"; + url = "https://capec.mitre.org/about/termsofuse.html"; + }; + + clArtistic = { + spdxId = "ClArtistic"; + fullName = "Clarified Artistic License"; + }; + + cc0 = { + spdxId = "CC0-1.0"; + fullName = "Creative Commons Zero v1.0 Universal"; + }; + + cc-by-nc-nd-30 = { + spdxId = "CC-BY-NC-ND-3.0"; + fullName = "Creative Commons Attribution Non Commercial No Derivative Works 3.0 Unported"; + free = false; + }; + + cc-by-nc-nd-40 = { + spdxId = "CC-BY-NC-ND-4.0"; + fullName = "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International"; + free = false; + }; + + cc-by-nc-sa-20 = { + spdxId = "CC-BY-NC-SA-2.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 2.0"; + free = false; + }; + + cc-by-nc-sa-25 = { + spdxId = "CC-BY-NC-SA-2.5"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 2.5"; + free = false; + }; + + cc-by-nc-sa-30 = { + spdxId = "CC-BY-NC-SA-3.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 3.0"; + free = false; + }; + + cc-by-nc-sa-40 = { + spdxId = "CC-BY-NC-SA-4.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 4.0"; + free = false; + }; + + cc-by-nc-30 = { + spdxId = "CC-BY-NC-3.0"; + fullName = "Creative Commons Attribution Non Commercial 3.0 Unported"; + free = false; + }; + + cc-by-nc-40 = { + spdxId = "CC-BY-NC-4.0"; + fullName = "Creative Commons Attribution Non Commercial 4.0 International"; + free = false; + }; + + cc-by-nd-30 = { + spdxId = "CC-BY-ND-3.0"; + fullName = "Creative Commons Attribution-No Derivative Works v3.00"; + free = false; + }; + + cc-by-nd-40 = { + spdxId = "CC-BY-ND-4.0"; + fullName = "Creative Commons Attribution-No Derivative Works v4.0"; + 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-20 = { + spdxId = "CC-BY-2.0"; + fullName = "Creative Commons Attribution 2.0"; + }; + + cc-by-30 = { + spdxId = "CC-BY-3.0"; + fullName = "Creative Commons Attribution 3.0"; + }; + + cc-by-sa-30 = { + spdxId = "CC-BY-SA-3.0"; + fullName = "Creative Commons Attribution Share Alike 3.0"; + }; + + cc-by-40 = { + spdxId = "CC-BY-4.0"; + fullName = "Creative Commons Attribution 4.0"; + }; + + cc-by-sa-40 = { + spdxId = "CC-BY-SA-4.0"; + fullName = "Creative Commons Attribution Share Alike 4.0"; + }; + + cc-sa-10 = { + shortName = "CC-SA-1.0"; + fullName = "Creative Commons Share Alike 1.0"; + url = "https://creativecommons.org/licenses/sa/1.0"; + }; + + cddl = { + spdxId = "CDDL-1.0"; + fullName = "Common Development and Distribution License 1.0"; + }; + + cecill20 = { + spdxId = "CECILL-2.0"; + fullName = "CeCILL Free Software License Agreement v2.0"; + }; + + cecill21 = { + spdxId = "CECILL-2.1"; + fullName = "CeCILL Free Software License Agreement v2.1"; + }; + + cecill-b = { + spdxId = "CECILL-B"; + fullName = "CeCILL-B Free Software License Agreement"; + }; + + cecill-c = { + spdxId = "CECILL-C"; + fullName = "CeCILL-C Free Software License Agreement"; + }; + + cockroachdb-community-license = { + fullName = "CockroachDB Community License Agreement"; + url = "https://www.cockroachlabs.com/cockroachdb-community-license/"; + free = false; + }; + + cpal10 = { + spdxId = "CPAL-1.0"; + fullName = "Common Public Attribution License 1.0"; + }; + + commons-clause = { + fullName = "Commons Clause License"; + url = "https://commonsclause.com/"; + free = false; + }; + + cpl10 = { + spdxId = "CPL-1.0"; + fullName = "Common Public License 1.0"; + }; + + curl = { + spdxId = "curl"; + fullName = "curl License"; + }; + + doc = { + spdxId = "DOC"; + fullName = "DOC License"; + }; + + drl10 = { + spdxId = "DRL-1.0"; + fullName = "Detection Rule License 1.0"; + }; + + dtoa = { + spdxId = "dtoa"; + fullName = "dtoa License"; + }; + + eapl = { + fullName = "EPSON AVASYS PUBLIC LICENSE"; + url = "https://avasys.jp/hp/menu000000700/hpg000000603.htm"; + free = false; + }; + + ecl20 = { + fullName = "Educational Community License, Version 2.0"; + shortName = "ECL 2.0"; + spdxId = "ECL-2.0"; + }; + + efl10 = { + spdxId = "EFL-1.0"; + fullName = "Eiffel Forum License v1.0"; + }; + + efl20 = { + spdxId = "EFL-2.0"; + fullName = "Eiffel Forum License v2.0"; + }; + + elastic20 = { + spdxId = "Elastic-2.0"; + fullName = "Elastic License 2.0"; + free = false; + }; + + epl10 = { + spdxId = "EPL-1.0"; + fullName = "Eclipse Public License 1.0"; + }; + + epl20 = { + spdxId = "EPL-2.0"; + fullName = "Eclipse Public License 2.0"; + }; + + epson = { + fullName = "Seiko Epson Corporation Software License Agreement for Linux"; + url = "https://download.ebz.epson.net/dsc/du/02/eula/global/LINUX_EN.html"; + free = false; + }; + + eupl11 = { + spdxId = "EUPL-1.1"; + fullName = "European Union Public License 1.1"; + }; + + eupl12 = { + spdxId = "EUPL-1.2"; + fullName = "European Union Public License 1.2"; + }; + + fdl11Only = { + spdxId = "GFDL-1.1-only"; + fullName = "GNU Free Documentation License v1.1 only"; + }; + + fdl11Plus = { + spdxId = "GFDL-1.1-or-later"; + fullName = "GNU Free Documentation License v1.1 or later"; + }; + + fdl12Only = { + spdxId = "GFDL-1.2-only"; + fullName = "GNU Free Documentation License v1.2 only"; + }; + + fdl12Plus = { + spdxId = "GFDL-1.2-or-later"; + fullName = "GNU Free Documentation License v1.2 or later"; + }; + + fdl13Only = { + spdxId = "GFDL-1.3-only"; + fullName = "GNU Free Documentation License v1.3 only"; + }; + + fdl13Plus = { + spdxId = "GFDL-1.3-or-later"; + fullName = "GNU Free Documentation License v1.3 or later"; + }; + + ffsl = { + fullName = "Floodgap Free Software License"; + url = "https://www.floodgap.com/software/ffsl/license.html"; + free = false; + }; + + fraunhofer-fdk = { + fullName = "Fraunhofer FDK AAC Codec Library"; + spdxId = "FDK-AAC"; + }; + + free = { + fullName = "Unspecified free software license"; + }; + + fsl11Mit = { + fullName = "Functional Source License, Version 1.1, MIT Future License"; + url = "https://fsl.software/FSL-1.1-MIT.template.md"; + free = false; + redistributable = true; + }; + + fsl11Asl20 = { + fullName = "Functional Source License, Version 1.1, Apache 2.0 Future License"; + url = "https://fsl.software/FSL-1.1-Apache-2.0.template.md"; + free = false; + redistributable = true; + }; + + ftl = { + spdxId = "FTL"; + fullName = "Freetype Project License"; + }; + + g4sl = { + fullName = "Geant4 Software License"; + url = "https://geant4.web.cern.ch/geant4/license/LICENSE.html"; + }; + + geogebra = { + fullName = "GeoGebra Non-Commercial License Agreement"; + url = "https://www.geogebra.org/license"; + free = false; + }; + + generaluser = { + fullName = "GeneralUser GS License v2.0"; + url = "https://www.schristiancollins.com/generaluser.php"; # license included in sources + }; + + gfl = { + fullName = "GUST Font License"; + url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-LICENSE.txt"; + }; + + gfsl = { + fullName = "GUST Font Source License"; + url = "https://www.gust.org.pl/projects/e-foundry/licenses/GUST-FONT-SOURCE-LICENSE.txt"; + }; + + gpl1Only = { + spdxId = "GPL-1.0-only"; + fullName = "GNU General Public License v1.0 only"; + }; + + gpl1Plus = { + spdxId = "GPL-1.0-or-later"; + fullName = "GNU General Public License v1.0 or later"; + }; + + gpl2Only = { + spdxId = "GPL-2.0-only"; + fullName = "GNU General Public License v2.0 only"; + }; + + gpl2Classpath = { + spdxId = "GPL-2.0-with-classpath-exception"; + fullName = "GNU General Public License v2.0 only (with Classpath exception)"; + }; + + gpl2ClasspathPlus = { + fullName = "GNU General Public License v2.0 or later (with Classpath exception)"; + url = "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception"; + }; + + gpl2Oss = { + fullName = "GNU General Public License version 2 only (with OSI approved licenses linking exception)"; + url = "https://www.mysql.com/about/legal/licensing/foss-exception"; + }; + + gpl2Plus = { + spdxId = "GPL-2.0-or-later"; + fullName = "GNU General Public License v2.0 or later"; + }; + + gpl3Only = { + spdxId = "GPL-3.0-only"; + fullName = "GNU General Public License v3.0 only"; + }; + + gpl3Plus = { + spdxId = "GPL-3.0-or-later"; + fullName = "GNU General Public License v3.0 or later"; + }; + + gpl3ClasspathPlus = { + fullName = "GNU General Public License v3.0 or later (with Classpath exception)"; + url = "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception"; + }; + + giftware = { + spdxId = "Giftware"; + fullName = "Giftware License"; + }; + + hpnd = { + spdxId = "HPND"; + fullName = "Historic Permission Notice and Disclaimer"; + }; + + hpndSellVariant = { + fullName = "Historical Permission Notice and Disclaimer - sell variant"; + spdxId = "HPND-sell-variant"; + }; + + hpndUc = { + spdxId = "HPND-UC"; + fullName = "Historical Permission Notice and Disclaimer - University of California variant"; + }; + + # Intel's license, seems free + iasl = { + spdxId = "Intel-ACPI"; + fullName = "Intel ACPI Software License Agreement"; + }; + + icu = { + spdxId = "ICU"; + fullName = "ICU"; + }; + + ijg = { + spdxId = "IJG"; + fullName = "Independent JPEG Group License"; + }; + + imagemagick = { + fullName = "ImageMagick License"; + spdxId = "ImageMagick"; + }; + + imlib2 = { + spdxId = "Imlib2"; + fullName = "Imlib2 License"; + }; + + info-zip = { + spdxId = "Info-ZIP"; + fullName = "Info-ZIP License"; + }; + + inria-compcert = { + fullName = "INRIA Non-Commercial License Agreement for the CompCert verified compiler"; + url = "https://compcert.org/doc/LICENSE.txt"; + free = false; + }; + + inria-icesl = { + fullName = "End User License Agreement for IceSL Software"; + url = "https://icesl.loria.fr/assets/pdf/EULA_IceSL_binary.pdf"; + free = false; + }; + + inria-zelus = { + fullName = "INRIA Non-Commercial License Agreement for the Zélus compiler"; + url = "https://github.com/INRIA/zelus/raw/829f2b97cba93b0543a9ca0272269e6b8fdad356/LICENSE"; + free = false; + }; + + ipa = { + spdxId = "IPA"; + fullName = "IPA Font License"; + }; + + ipl10 = { + spdxId = "IPL-1.0"; + fullName = "IBM Public License v1.0"; + }; + + isc = { + spdxId = "ISC"; + fullName = "ISC License"; + }; + + databricks = { + fullName = "Databricks License"; + url = "https://www.databricks.com/legal/db-license"; + free = false; + }; + + databricks-dbx = { + fullName = "DataBricks eXtensions aka dbx License"; + url = "https://github.com/databrickslabs/dbx/blob/743b579a4ac44531f764c6e522dbe5a81a7dc0e4/LICENSE"; + free = false; + redistributable = false; + }; + + databricks-license = { + fullName = "Databricks License"; + url = "https://www.databricks.com/legal/db-license"; + free = false; + }; + + fair = { + fullName = "Fair License"; + spdxId = "Fair"; + 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"; + }; + + lal13 = { + spdxId = "LAL-1.3"; + fullName = "Licence Art Libre 1.3"; + }; + + lens = { + fullName = "Lens Terms of Service Agreement"; + url = "https://k8slens.dev/legal/tos"; + free = false; + }; + + lgpl2Only = { + spdxId = "LGPL-2.0-only"; + fullName = "GNU Library General Public License v2 only"; + }; + + lgpl2Plus = { + spdxId = "LGPL-2.0-or-later"; + fullName = "GNU Library General Public License v2 or later"; + }; + + lgpl21Only = { + spdxId = "LGPL-2.1-only"; + fullName = "GNU Lesser General Public License v2.1 only"; + }; + + lgpl21Plus = { + spdxId = "LGPL-2.1-or-later"; + fullName = "GNU Lesser General Public License v2.1 or later"; + }; + + lgpl3Only = { + spdxId = "LGPL-3.0-only"; + fullName = "GNU Lesser General Public License v3.0 only"; + }; + + lgpl3Plus = { + spdxId = "LGPL-3.0-or-later"; + fullName = "GNU Lesser General Public License v3.0 or later"; + }; + + lgpllr = { + spdxId = "LGPLLR"; + fullName = "Lesser General Public License For Linguistic Resources"; + }; + + libpng = { + spdxId = "Libpng"; + fullName = "libpng License"; + }; + + libpng2 = { + spdxId = "libpng-2.0"; # Used since libpng 1.6.36. + fullName = "PNG Reference Library version 2"; + }; + + libtiff = { + spdxId = "libtiff"; + fullName = "libtiff License"; + }; + + llgpl21 = { + fullName = "Lisp LGPL; GNU Lesser General Public License version 2.1 with Franz Inc. preamble for clarification of LGPL terms in context of Lisp"; + url = "https://opensource.franz.com/preamble.html"; + }; + + llvm-exception = { + spdxId = "LLVM-exception"; + fullName = "LLVM Exception"; # LLVM exceptions to the Apache 2.0 License + }; + + 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"; + }; + + lpl-102 = { + spdxId = "LPL-1.02"; + fullName = "Lucent Public License v1.02"; + }; + + miros = { + spdxId = "MirOS"; + fullName = "MirOS License"; + }; + + mit = { + spdxId = "MIT"; + fullName = "MIT License"; + }; + + mit-cmu = { + spdxId = "MIT-CMU"; + fullName = "CMU License"; + }; + + mit-feh = { + spdxId = "MIT-feh"; + fullName = "feh License"; + }; + + mit-modern = { + # Also known as Zsh license + spdxId = "MIT-Modern-Variant"; + fullName = "MIT License Modern Variant"; + }; + + mitAdvertising = { + spdxId = "MIT-advertising"; + fullName = "Enlightenment License (e16)"; + }; + + mit0 = { + spdxId = "MIT-0"; + fullName = "MIT No Attribution"; + }; + + mpl10 = { + spdxId = "MPL-1.0"; + fullName = "Mozilla Public License 1.0"; + }; + + mpl11 = { + spdxId = "MPL-1.1"; + fullName = "Mozilla Public License 1.1"; + }; + + mpl20 = { + spdxId = "MPL-2.0"; + fullName = "Mozilla Public License 2.0"; + }; + + mplus = { + spdxId = "mplus"; + fullName = "M+ Font License"; + }; + + mspl = { + spdxId = "MS-PL"; + fullName = "Microsoft Public License"; + }; + + mulan-psl2 = { + spdxId = "MulanPSL-2.0"; + fullName = "Mulan Permissive Software License, Version 2"; + }; + + naist-2003 = { + spdxId = "NAIST-2003"; + fullName = "Nara Institute of Science and Technology License (2003)"; + }; + + nasa13 = { + spdxId = "NASA-1.3"; + fullName = "NASA Open Source Agreement 1.3"; + free = false; + }; + + ncbiPd = { + spdxId = "NCBI-PD"; + fullName = "NCBI Public Domain Notice"; + # Due to United States copyright law, anything with this "license" does not have a copyright in the + # jurisdiction of the United States. However, other jurisdictions may assign the United States + # government copyright to the work, and the license explicitly states that in such a case, no license + # is granted. This is nonfree and nonredistributable in most jurisdictions other than the United States. + free = false; + redistributable = false; + }; + + ncsa = { + spdxId = "NCSA"; + fullName = "University of Illinois/NCSA Open Source License"; + }; + + ncul1 = { + spdxId = "NCUL1"; + fullName = "Netdata Cloud UI License v1.0"; + free = false; + redistributable = true; # Only if used in Netdata products. + }; + + nistSoftware = { + spdxId = "NIST-Software"; + fullName = "NIST Software License"; + }; + + nlpl = { + spdxId = "NLPL"; + fullName = "No Limit Public License"; + }; + + nposl3 = { + spdxId = "NPOSL-3.0"; + fullName = "Non-Profit Open Software License 3.0"; + }; + + nvidiaCuda = { + shortName = "CUDA EULA"; + fullName = "CUDA Toolkit End User License Agreement (EULA)"; + url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; + free = false; + }; + + nvidiaCudaRedist = { + shortName = "CUDA EULA"; + fullName = "CUDA Toolkit End User License Agreement (EULA)"; + url = "https://docs.nvidia.com/cuda/eula/index.html#cuda-toolkit-supplement-license-agreement"; + free = false; + redistributable = true; + }; + + obsidian = { + fullName = "Obsidian End User Agreement"; + url = "https://obsidian.md/eula"; + free = false; + }; + + ocamlLgplLinkingException = { + spdxId = "OCaml-LGPL-linking-exception"; + fullName = "OCaml LGPL Linking Exception"; + }; + + ocamlpro_nc = { + fullName = "OCamlPro Non Commercial license version 1"; + url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf"; + free = false; + }; + + odbl = { + spdxId = "ODbL-1.0"; + fullName = "Open Data Commons Open Database License v1.0"; + }; + + ofl = { + spdxId = "OFL-1.1"; + fullName = "SIL Open Font License 1.1"; + }; + + oml = { + spdxId = "OML"; + fullName = "Open Market License"; + }; + + openldap = { + spdxId = "OLDAP-2.8"; + fullName = "Open LDAP Public License v2.8"; + }; + + openssl = { + spdxId = "OpenSSL"; + 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"; + }; + + osl21 = { + spdxId = "OSL-2.1"; + fullName = "Open Software License 2.1"; + }; + + osl3 = { + spdxId = "OSL-3.0"; + fullName = "Open Software License 3.0"; + }; + + parity70 = { + spdxId = "Parity-7.0.0"; + fullName = "Parity Public License 7.0.0"; + }; + + php301 = { + spdxId = "PHP-3.01"; + fullName = "PHP License v3.01"; + }; + + postgresql = { + spdxId = "PostgreSQL"; + fullName = "PostgreSQL License"; + }; + + postman = { + fullName = "Postman EULA"; + url = "https://www.getpostman.com/licenses/postman_base_app"; + free = false; + }; + + psfl = { + spdxId = "Python-2.0"; + fullName = "Python Software Foundation License version 2"; + }; + + publicDomain = { + fullName = "Public Domain"; + }; + + purdueBsd = { + fullName = "Purdue BSD-Style License"; # also known as lsof license + url = "https://enterprise.dejacode.com/licenses/public/purdue-bsd"; + }; + + prosperity30 = { + fullName = "Prosperity-3.0.0"; + free = false; + url = "https://prosperitylicense.com/versions/3.0.0.html"; + }; + + qhull = { + spdxId = "Qhull"; + fullName = "Qhull License"; + }; + + qpl = { + spdxId = "QPL-1.0"; + fullName = "Q Public License 1.0"; + }; + + qwt = { + fullName = "Qwt License, Version 1.0"; + url = "https://qwt.sourceforge.io/qwtlicense.html"; + }; + + radiance = { + fullName = "The Radiance Software License, Version 2.0"; + url = "https://github.com/LBNL-ETA/Radiance/blob/master/License.txt"; + }; + + ruby = { + spdxId = "Ruby"; + fullName = "Ruby License"; + }; + + sendmail = { + spdxId = "Sendmail"; + fullName = "Sendmail License"; + }; + + sgi-b-20 = { + spdxId = "SGI-B-2.0"; + fullName = "SGI Free Software License B v2.0"; + }; + + # Gentoo seems to treat it as a license: + # https://gitweb.gentoo.org/repo/gentoo.git/tree/licenses/SGMLUG?id=7d999af4a47bf55e53e54713d98d145f935935c1 + sgmlug = { + fullName = "SGML UG SGML Parser Materials license"; + }; + + sleepycat = { + spdxId = "Sleepycat"; + fullName = "Sleepycat License"; + }; + + smail = { + shortName = "smail"; + fullName = "SMAIL General Public License"; + url = "https://sources.debian.org/copyright/license/debianutils/4.9.1/"; + }; + + smlnj = { + spdxId = "SMLNJ"; + fullName = "Standard ML of New Jersey License"; + }; + + sspl = { + shortName = "SSPL"; + fullName = "Server Side Public License"; + url = "https://www.mongodb.com/licensing/server-side-public-license"; + free = false; + # NOTE Debatable. + # The license a slightly modified AGPL but still considered unfree by the + # OSI for what seem like political reasons + redistributable = true; # Definitely redistributable though, it's an AGPL derivative + }; + + stk = { + shortName = "stk"; + fullName = "Synthesis Tool Kit 4.3"; + url = "https://github.com/thestk/stk/blob/master/LICENSE"; + }; + + sudo = { + shortName = "sudo"; + fullName = "Sudo License (ISC-style)"; + url = "https://www.sudo.ws/about/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" + }; + + teamspeak = { + fullName = "Teamspeak client license"; + url = "https://www.teamspeak.com/en/privacy-and-terms/"; + free = false; + redistributable = true; # we got a permit to redistribute it: + # License issues: + # Date: Mon, 10 Dec 2007 19:55:16 -0500 + # From: TeamSpeak Sales + # To: 'Marc Weber' + # Subject: RE: teamspeak on nix? + # + # Yes, that would be fine. As long as you are not renting servers or selling + # TeamSpeak then you are more than welcome to distribute it. + # + # Thank you, + # + # TeamSpeak Sales Team + # ________________________________ + # e-Mail: sales@tritoncia.com + # TeamSpeak: http://www.TeamSpeak.com + # Account Login: https://sales.TritonCIA.com/users + # + # + # + # -----Original Message----- + # From: Marc Weber [mailto:marco-oweber@gmx.de] + # Sent: Monday, December 10, 2007 5:03 PM + # To: sales@tritoncia.com + # Subject: teamspeak on nix? + # + # Hello, + # + # nix is very young software distribution system (http://nix.cs.uu.nl/) + # I'd like to ask wether you permit us to add teamspeak (server/ client?) + # + # Sincerly + # Marc Weber (small nix contributor) + }; + + tsl = { + shortName = "TSL"; + fullName = "Timescale License Agreegment"; + url = "https://github.com/timescale/timescaledb/blob/main/tsl/LICENSE-TIMESCALE"; + free = false; + }; + + tcltk = { + spdxId = "TCL"; + fullName = "TCL/TK License"; + }; + + tost = { + fullName = "Tomorrow Open Source Technology License 1.0"; + url = "https://github.com/PixarAnimationStudios/OpenUSD/blob/release/LICENSE.txt"; + }; + + ucd = { + fullName = "Unicode Character Database License"; + url = "https://fedoraproject.org/wiki/Licensing:UCD"; + }; + + ufl = { + spdxId = "Ubuntu-font-1.0"; + fullName = "Ubuntu Font License 1.0"; + }; + + unfree = { + fullName = "Unfree"; + free = false; + }; + + unfreeRedistributable = { + fullName = "Unfree redistributable"; + free = false; + redistributable = true; + }; + + unfreeRedistributableFirmware = { + fullName = "Unfree redistributable firmware"; + redistributable = true; + # Note: we currently consider these "free" for inclusion in the + # channel and NixOS images. + }; + + unicode-30 = { + spdxId = "Unicode-3.0"; + fullName = "Unicode License v3"; + }; + + unicode-dfs-2015 = { + spdxId = "Unicode-DFS-2015"; + fullName = "Unicode License Agreement - Data Files and Software (2015)"; + }; + + unicode-dfs-2016 = { + spdxId = "Unicode-DFS-2016"; + fullName = "Unicode License Agreement - Data Files and Software (2016)"; + }; + + unlicense = { + spdxId = "Unlicense"; + fullName = "The Unlicense"; + }; + + upl = { + spdxId = "UPL-1.0"; + fullName = "Universal Permissive License"; + }; + + vim = { + spdxId = "Vim"; + fullName = "Vim License"; + }; + + virtualbox-puel = { + fullName = "Oracle VM VirtualBox Extension Pack Personal Use and Evaluation License (PUEL)"; + url = "https://www.virtualbox.org/wiki/VirtualBox_PUEL"; + free = false; + }; + + vol-sl = { + fullName = "Volatility Software License, Version 1.0"; + url = "https://www.volatilityfoundation.org/license/vsl-v1.0"; + }; + + vsl10 = { + spdxId = "VSL-1.0"; + fullName = "Vovida Software License v1.0"; + }; + + watcom = { + spdxId = "Watcom-1.0"; + fullName = "Sybase Open Watcom Public License 1.0"; + # Despite being OSI‐approved, this licence is not considered FOSS + # by Debian, Fedora, or the FSF, due to an onerous restriction that + # requires publication of even privately‐deployed modifications. + # This violates the FSF’s freedom 3 and Debian’s “desert island + # test” and “dissident test”. + # + # See: + free = false; + redistributable = true; + }; + + w3c = { + spdxId = "W3C"; + fullName = "W3C Software Notice and License"; + }; + + wadalab = { + fullName = "Wadalab Font License"; + url = "https://fedoraproject.org/wiki/Licensing:Wadalab?rd=Licensing/Wadalab"; + }; + + wtfpl = { + spdxId = "WTFPL"; + fullName = "Do What The F*ck You Want To Public License"; + }; + + wxWindows = { + spdxId = "wxWindows"; + fullName = "wxWindows Library Licence, Version 3.1"; + }; + + x11 = { + spdxId = "X11"; + fullName = "X11 License"; + }; + + xfig = { + spdxId = "Xfig"; + fullName = "xfig"; + }; + + xinetd = { + spdxId = "xinetd"; + fullName = "xinetd License"; + }; + + xskat = { + spdxId = "XSkat"; + fullName = "XSkat License"; + }; + + zlib = { + spdxId = "Zlib"; + fullName = "zlib License"; + }; + + zpl20 = { + spdxId = "ZPL-2.0"; + fullName = "Zope Public License 2.0"; + }; + + zpl21 = { + spdxId = "ZPL-2.1"; + fullName = "Zope Public License 2.1"; + }; + + } + // { + # TODO: remove legacy aliases + apsl10 = { + # deprecated for consistency with `apple-psl20`; use `apple-psl10` + spdxId = "APSL-1.0"; + fullName = "Apple Public Source License 1.0"; + deprecated = true; + }; + apsl20 = { + # deprecated due to confusion with Apache-2.0; use `apple-psl20` + spdxId = "APSL-2.0"; + fullName = "Apple Public Source License 2.0"; + deprecated = true; + }; + gpl2 = { + spdxId = "GPL-2.0"; + fullName = "GNU General Public License v2.0"; + deprecated = true; + }; + gpl3 = { + spdxId = "GPL-3.0"; + fullName = "GNU General Public License v3.0"; + deprecated = true; + }; + lgpl2 = { + spdxId = "LGPL-2.0"; + fullName = "GNU Library General Public License v2"; + deprecated = true; + }; + lgpl21 = { + spdxId = "LGPL-2.1"; + fullName = "GNU Lesser General Public License v2.1"; + deprecated = true; + }; + lgpl3 = { + spdxId = "LGPL-3.0"; + fullName = "GNU Lesser General Public License v3.0"; + deprecated = true; + }; + } +) diff --git a/lib/lists.nix b/lib/lists.nix index 91e5094022b4..e119606dd5e7 100644 --- a/lib/lists.nix +++ b/lib/lists.nix @@ -4,13 +4,30 @@ { lib }: let inherit (lib.strings) toInt; - inherit (lib.trivial) compare min id warn pipe; + inherit (lib.trivial) + compare + min + id + warn + pipe + ; inherit (lib.attrsets) mapAttrs; inherit (lib) max; in rec { - inherit (builtins) head tail length isList elemAt concatLists filter elem genList map; + inherit (builtins) + head + tail + length + isList + elemAt + concatLists + filter + elem + genList + map + ; /** Create a list consisting of a single element. `singleton x` is @@ -40,7 +57,7 @@ rec { ::: */ - singleton = x: [x]; + singleton = x: [ x ]; /** Apply the function to each element in the list. @@ -82,7 +99,6 @@ rec { `list` with `nul` as the starting value, i.e., `foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`. - # Inputs `op` @@ -119,14 +135,13 @@ rec { ::: */ - foldr = op: nul: list: + foldr = + op: nul: list: let len = length list; - fold' = n: - if n == len - then nul - else op (elemAt list n) (fold' (n + 1)); - in fold' 0; + fold' = n: if n == len then nul else op (elemAt list n) (fold' (n + 1)); + in + fold' 0; /** `fold` is an alias of `foldr` for historic reasons @@ -134,7 +149,6 @@ rec { # FIXME(Profpatsch): deprecate? fold = foldr; - /** “left fold”, like `foldr`, but from the left: @@ -176,13 +190,12 @@ rec { ::: */ - foldl = op: nul: list: + foldl = + op: nul: list: let - foldl' = n: - if n == -1 - then nul - else op (foldl' (n - 1)) (elemAt list n); - in foldl' (length list - 1); + foldl' = n: if n == -1 then nul else op (foldl' (n - 1)) (elemAt list n); + in + foldl' (length list - 1); /** Reduce a list by applying a binary operator from left to right, @@ -261,13 +274,11 @@ rec { ::: */ foldl' = - op: - acc: + op: acc: # The builtin `foldl'` is a bit lazier than one might expect. # See https://github.com/NixOS/nix/pull/7158. # In particular, the initial accumulator value is not forced before the first iteration starts. - builtins.seq acc - (builtins.foldl' op acc); + builtins.seq acc (builtins.foldl' op acc); /** Map with index starting from 0 @@ -304,7 +315,6 @@ rec { /** Map with index starting from 1 - # Inputs `f` @@ -374,12 +384,9 @@ rec { ::: */ ifilter0 = - ipred: - input: + ipred: input: map (idx: elemAt input idx) ( - filter (idx: ipred idx (elemAt input idx)) ( - genList (x: x) (length input) - ) + filter (idx: ipred idx (elemAt input idx)) (genList (x: x) (length input)) ); /** @@ -408,14 +415,12 @@ rec { Flatten the argument into a single list; that is, nested lists are spliced into the top-level lists. - # Inputs `x` : 1\. Function argument - # Examples :::{.example} ## `lib.lists.flatten` usage example @@ -429,15 +434,11 @@ rec { ::: */ - flatten = x: - if isList x - then concatMap (y: flatten y) x - else [x]; + flatten = x: if isList x then concatMap (y: flatten y) x else [ x ]; /** Remove elements equal to 'e' from a list. Useful for buildInputs. - # Inputs `e` @@ -465,8 +466,7 @@ rec { ::: */ - remove = - e: filter (x: x != e); + remove = e: filter (x: x != e); /** Find the sole element in the list matching the specified @@ -475,7 +475,6 @@ rec { Returns `default` if no such element exists, or `multiple` if there are multiple matching elements. - # Inputs `pred` @@ -516,14 +515,17 @@ rec { ::: */ findSingle = - pred: - default: - multiple: - list: - let found = filter pred list; len = length found; - in if len == 0 then default - else if len != 1 then multiple - else head found; + pred: default: multiple: list: + let + found = filter pred list; + len = length found; + in + if len == 0 then + default + else if len != 1 then + multiple + else + head found; /** Find the first index in the list matching the specified @@ -563,9 +565,7 @@ rec { ::: */ findFirstIndex = - pred: - default: - list: + pred: default: list: let # A naive recursive implementation would be much simpler, but # would also overflow the evaluator stack. We use `foldl'` as a workaround @@ -580,12 +580,13 @@ rec { # - if index >= 0 then pred (elemAt list index) and all elements before (elemAt list index) didn't satisfy pred # # We start with index -1 and the 0'th element of the list, which satisfies the invariant - resultIndex = foldl' (index: el: + resultIndex = foldl' ( + index: el: if index < 0 then # No match yet before the current index, we need to check the element if pred el then # We have a match! Turn it into the actual index to prevent future iterations from modifying it - - index - 1 + -index - 1 else # Still no match, update the index to the next element (we're counting down, so minus one) index - 1 @@ -594,10 +595,7 @@ rec { index ) (-1) list; in - if resultIndex < 0 then - default - else - resultIndex; + if resultIndex < 0 then default else resultIndex; /** Find the first element in the list matching the specified @@ -637,16 +635,11 @@ rec { ::: */ findFirst = - pred: - default: - list: + pred: default: list: let index = findFirstIndex pred null list; in - if index == null then - default - else - elemAt list index; + if index == null then default else elemAt list index; /** Return true if function `pred` returns true for at least one @@ -745,8 +738,7 @@ rec { ::: */ - count = - pred: foldl' (c: x: if pred x then c + 1 else c) 0; + count = pred: foldl' (c: x: if pred x then c + 1 else c) 0; /** Return a singleton list or an empty list, depending on a boolean @@ -782,7 +774,7 @@ rec { ::: */ - optional = cond: elem: if cond then [elem] else []; + optional = cond: elem: if cond then [ elem ] else [ ]; /** Return a list or an empty list, depending on a boolean value. @@ -816,10 +808,7 @@ rec { ::: */ - optionals = - cond: - elems: if cond then elems else []; - + optionals = cond: elems: if cond then elems else [ ]; /** If argument is a list, return it; else, wrap it in a singleton @@ -845,7 +834,7 @@ rec { ::: */ - toList = x: if isList x then x else [x]; + toList = x: if isList x then x else [ x ]; /** Return a list of integers from `first` up to and including `last`. @@ -879,13 +868,7 @@ rec { ::: */ - range = - first: - last: - if first > last then - [] - else - genList (n: first + n) (last - first + 1); + range = first: last: if first > last then [ ] else genList (n: first + n) (last - first + 1); /** Return a list with `n` copies of an element. @@ -977,7 +960,6 @@ rec { : 4\. Function argument - # Examples :::{.example} ## `lib.lists.groupBy'` usage example @@ -1002,15 +984,21 @@ rec { ::: */ - groupBy' = op: nul: pred: lst: mapAttrs (name: foldl op nul) (groupBy pred lst); - - groupBy = builtins.groupBy or ( - pred: foldl' (r: e: - let - key = pred e; - in - r // { ${key} = (r.${key} or []) ++ [e]; } - ) {}); + groupBy' = + op: nul: pred: lst: + mapAttrs (name: foldl op nul) (groupBy pred lst); + + groupBy = + builtins.groupBy or ( + pred: + foldl' ( + r: e: + let + key = pred e; + in + r // { ${key} = (r.${key} or [ ]) ++ [ e ]; } + ) { } + ); /** Merges two lists of the same size together. If the sizes aren't the same @@ -1049,11 +1037,8 @@ rec { ::: */ zipListsWith = - f: - fst: - snd: - genList - (n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd)); + f: fst: snd: + genList (n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd)); /** Merges two lists of the same size together. If the sizes aren't the same @@ -1114,8 +1099,12 @@ rec { ::: */ - reverseList = xs: - let l = length xs; in genList (n: elemAt xs (l - n - 1)) l; + reverseList = + xs: + let + l = length xs; + in + genList (n: elemAt xs (l - n - 1)) l; /** Depth-First Search (DFS) for lists `list != []`. @@ -1123,7 +1112,6 @@ rec { `before a b == true` means that `b` depends on `a` (there's an edge from `b` to `a`). - # Inputs `stopOnCycles` @@ -1138,7 +1126,6 @@ rec { : 3\. Function argument - # Examples :::{.example} ## `lib.lists.listDfs` usage example @@ -1159,22 +1146,32 @@ rec { ::: */ - listDfs = stopOnCycles: before: list: + listDfs = + stopOnCycles: before: list: let - dfs' = us: visited: rest: + dfs' = + us: visited: rest: let c = filter (x: before x us) visited; b = partition (x: before x us) rest; - in if stopOnCycles && (length c > 0) - then { cycle = us; loops = c; inherit visited rest; } - else if length b.right == 0 - then # nothing is before us - { minimal = us; inherit visited rest; } - else # grab the first one before us and continue - dfs' (head b.right) - ([ us ] ++ visited) - (tail b.right ++ b.wrong); - in dfs' (head list) [] (tail list); + in + if stopOnCycles && (length c > 0) then + { + cycle = us; + loops = c; + inherit visited rest; + } + else if length b.right == 0 then + # nothing is before us + { + minimal = us; + inherit visited rest; + } + else + # grab the first one before us and continue + dfs' (head b.right) ([ us ] ++ visited) (tail b.right ++ b.wrong); + in + dfs' (head list) [ ] (tail list); /** Sort a list based on a partial ordering using DFS. This @@ -1184,7 +1181,6 @@ rec { `before a b == true` means that `b` should be after `a` in the result. - # Inputs `before` @@ -1195,7 +1191,6 @@ rec { : 2\. Function argument - # Examples :::{.example} ## `lib.lists.toposort` usage example @@ -1216,24 +1211,28 @@ rec { ::: */ - toposort = before: list: + toposort = + before: list: let dfsthis = listDfs true before list; toporest = toposort before (dfsthis.visited ++ dfsthis.rest); in - if length list < 2 - then # finish - { result = list; } - else if dfsthis ? cycle - then # there's a cycle, starting from the current vertex, return it - { cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited); - inherit (dfsthis) loops; } - else if toporest ? cycle - then # there's a cycle somewhere else in the graph, return it - toporest - # Slow, but short. Can be made a bit faster with an explicit stack. - else # there are no cycles - { result = [ dfsthis.minimal ] ++ toporest.result; }; + if length list < 2 then + # finish + { result = list; } + else if dfsthis ? cycle then + # there's a cycle, starting from the current vertex, return it + { + cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited); + inherit (dfsthis) loops; + } + else if toporest ? cycle then + # there's a cycle somewhere else in the graph, return it + toporest + # Slow, but short. Can be made a bit faster with an explicit stack. + else + # there are no cycles + { result = [ dfsthis.minimal ] ++ toporest.result; }; /** Sort a list based on a comparator function which compares two @@ -1289,7 +1288,6 @@ rec { sortOn f == sort (p: q: f p < f q) ``` - # Inputs `f` @@ -1317,18 +1315,22 @@ rec { ::: */ - sortOn = f: list: + sortOn = + f: list: let # Heterogenous list as pair may be ugly, but requires minimal allocations. - pairs = map (x: [(f x) x]) list; + pairs = map (x: [ + (f x) + x + ]) list; in - map - (x: builtins.elemAt x 1) - (sort - # Compare the first element of the pairs - # Do not factor out the `<`, to avoid calls in hot code; duplicate instead. - (a: b: head a < head b) - pairs); + map (x: builtins.elemAt x 1) ( + sort + # Compare the first element of the pairs + # Do not factor out the `<`, to avoid calls in hot code; duplicate instead. + (a: b: head a < head b) + pairs + ); /** Compare two lists element-by-element with a comparison function `cmp`. @@ -1360,7 +1362,6 @@ rec { : The second list - # Examples :::{.example} ## `lib.lists.compareLists` usage examples @@ -1378,30 +1379,28 @@ rec { ::: */ - compareLists = cmp: a: b: - if a == [] - then if b == [] - then 0 - else -1 - else if b == [] - then 1 - else let rel = cmp (head a) (head b); in - if rel == 0 - then compareLists cmp (tail a) (tail b) - else rel; + compareLists = + cmp: a: b: + if a == [ ] then + if b == [ ] then 0 else -1 + else if b == [ ] then + 1 + else + let + rel = cmp (head a) (head b); + in + if rel == 0 then compareLists cmp (tail a) (tail b) else rel; /** Sort list using "Natural sorting". Numeric portions of strings are sorted in numeric order. - # Inputs `lst` : 1\. Function argument - # Examples :::{.example} ## `lib.lists.naturalSort` usage example @@ -1417,18 +1416,21 @@ rec { ::: */ - naturalSort = lst: + naturalSort = + lst: let vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s); - prepared = map (x: [ (vectorise x) x ]) lst; # remember vectorised version for O(n) regex splits + prepared = map (x: [ + (vectorise x) + x + ]) lst; # remember vectorised version for O(n) regex splits less = a: b: (compareLists compare (head a) (head b)) < 0; in - map (x: elemAt x 1) (sort less prepared); + map (x: elemAt x 1) (sort less prepared); /** Return the first (at most) N elements of a list. - # Inputs `count` @@ -1458,13 +1460,11 @@ rec { ::: */ - take = - count: sublist 0 count; + take = count: sublist 0 count; /** Remove the first (at most) N elements of a list. - # Inputs `count` @@ -1494,14 +1494,11 @@ rec { ::: */ - drop = - count: - list: sublist count (length list) list; + drop = count: list: sublist count (length list) list; /** Remove the last (at most) N elements of a list. - # Inputs `count` @@ -1530,18 +1527,12 @@ rec { => [ ] ``` ::: - - */ - dropEnd = - n: xs: - take - (max 0 (length xs - n)) - xs; + */ + dropEnd = n: xs: take (max 0 (length xs - n)) xs; /** Whether the first list is a prefix of the second list. - # Inputs `list1` @@ -1571,10 +1562,7 @@ rec { ::: */ - hasPrefix = - list1: - list2: - take (length list1) list2 == list1; + hasPrefix = list1: list2: take (length list1) list2 == list1; /** Remove the first list as a prefix from the second list. @@ -1610,8 +1598,7 @@ rec { ::: */ removePrefix = - list1: - list2: + list1: list2: if hasPrefix list1 list2 then drop (length list1) list2 else @@ -1655,20 +1642,22 @@ rec { ::: */ sublist = - start: - count: - list: - let len = length list; in - genList - (n: elemAt list (n + start)) - (if start >= len then 0 - else if start + count > len then len - start - else count); + start: count: list: + let + len = length list; + in + genList (n: elemAt list (n + start)) ( + if start >= len then + 0 + else if start + count > len then + len - start + else + count + ); /** The common prefix of two lists. - # Inputs `list1` @@ -1701,8 +1690,7 @@ rec { ::: */ commonPrefix = - list1: - list2: + list1: list2: let # Zip the lists together into a list of booleans whether each element matches matchings = zipListsWith (fst: snd: fst != snd) list1 list2; @@ -1719,7 +1707,6 @@ rec { This function throws an error if the list is empty. - # Inputs `list` @@ -1743,8 +1730,9 @@ rec { ::: */ - last = list: - assert lib.assertMsg (list != []) "lists.last: list must not be empty!"; + last = + list: + assert lib.assertMsg (list != [ ]) "lists.last: list must not be empty!"; elemAt list (length list - 1); /** @@ -1752,7 +1740,6 @@ rec { This function throws an error if the list is empty. - # Inputs `list` @@ -1776,15 +1763,14 @@ rec { ::: */ - init = list: - assert lib.assertMsg (list != []) "lists.init: list must not be empty!"; + init = + list: + assert lib.assertMsg (list != [ ]) "lists.init: list must not be empty!"; take (length list - 1) list; - /** Return the image of the cross product of some lists by a function. - # Examples :::{.example} ## `lib.lists.crossLists` usage example @@ -1814,13 +1800,11 @@ rec { nix-repl> lib.mapCartesianProduct ({x,y}: x+y) { x = [1 2]; y = [3 4]; } [ 4 5 5 6 ] - '' - (f: foldl (fs: args: concatMap (f: map f args) fs) [f]); + '' (f: foldl (fs: args: concatMap (f: map f args) fs) [ f ]); /** Remove duplicate elements from the `list`. O(n^2) complexity. - # Inputs `list` @@ -1844,12 +1828,11 @@ rec { ::: */ - unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) []; + unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) [ ]; /** Check if list contains only unique elements. O(n^2) complexity. - # Inputs `list` @@ -1877,7 +1860,6 @@ rec { */ allUnique = list: (length (unique list) == length list); - /** Intersects list 'list1' and another list (`list2`). @@ -1893,7 +1875,6 @@ rec { : Second list - # Examples :::{.example} ## `lib.lists.intersectLists` usage example @@ -1922,7 +1903,6 @@ rec { : Second list - # Examples :::{.example} ## `lib.lists.subtractLists` usage example diff --git a/lib/meta.nix b/lib/meta.nix index dc279d9cc55f..ee234d94489b 100644 --- a/lib/meta.nix +++ b/lib/meta.nix @@ -6,14 +6,20 @@ { lib }: let - inherit (lib) matchAttrs any all isDerivation getBin assertMsg; + inherit (lib) + matchAttrs + any + all + isDerivation + getBin + assertMsg + ; inherit (lib.attrsets) mapAttrs' filterAttrs; inherit (builtins) isString match typeOf; in rec { - /** Add to or override the meta attributes of the given derivation. @@ -28,7 +34,6 @@ rec { : 2\. Function argument - # Examples :::{.example} ## `lib.meta.addMetaAttrs` usage example @@ -39,9 +44,7 @@ rec { ::: */ - addMetaAttrs = newAttrs: drv: - drv // { meta = (drv.meta or {}) // newAttrs; }; - + addMetaAttrs = newAttrs: drv: drv // { meta = (drv.meta or { }) // newAttrs; }; /** Disable Hydra builds of given derivation. @@ -52,8 +55,7 @@ rec { : 1\. Function argument */ - dontDistribute = drv: addMetaAttrs { hydraPlatforms = []; } drv; - + dontDistribute = drv: addMetaAttrs { hydraPlatforms = [ ]; } drv; /** Change the [symbolic name of a derivation](https://nixos.org/manual/nix/stable/language/derivations.html#attr-name). @@ -72,8 +74,7 @@ rec { : 2\. Function argument */ - setName = name: drv: drv // {inherit name;}; - + setName = name: drv: drv // { inherit name; }; /** Like `setName`, but takes the previous name as an argument. @@ -88,7 +89,6 @@ rec { : 2\. Function argument - # Examples :::{.example} ## `lib.meta.updateName` usage example @@ -99,8 +99,7 @@ rec { ::: */ - updateName = updater: drv: drv // {name = updater (drv.name);}; - + updateName = updater: drv: drv // { name = updater (drv.name); }; /** Append a suffix to the name of a package (before the version @@ -112,14 +111,19 @@ rec { : 1\. Function argument */ - appendToName = suffix: updateName (name: - let x = builtins.parseDrvName name; in "${x.name}-${suffix}-${x.version}"); - + appendToName = + suffix: + updateName ( + name: + let + x = builtins.parseDrvName name; + in + "${x.name}-${suffix}-${x.version}" + ); /** Apply a function to each derivation and only to derivations in an attrset. - # Inputs `f` @@ -130,11 +134,12 @@ rec { : 2\. Function argument */ - mapDerivationAttrset = f: set: lib.mapAttrs (name: pkg: if lib.isDerivation pkg then (f pkg) else pkg) set; + mapDerivationAttrset = + f: set: lib.mapAttrs (name: pkg: if lib.isDerivation pkg then (f pkg) else pkg) set; /** The default priority of packages in Nix. See `defaultPriority` in [`src/nix/profile.cc`](https://github.com/NixOS/nix/blob/master/src/nix/profile.cc#L47). - */ + */ defaultPriority = 5; /** @@ -159,7 +164,6 @@ rec { `drv` : 1\. Function argument - */ lowPrio = setPrio 10; @@ -174,7 +178,6 @@ rec { */ lowPrioSet = set: mapDerivationAttrset lowPrio set; - /** Increase the nix-env priority of the package, i.e., this version/variant of the package will be preferred. @@ -198,7 +201,6 @@ rec { */ hiPrioSet = set: mapDerivationAttrset hiPrio set; - /** Check to see if a platform is matched by the given `meta.platforms` element. @@ -214,7 +216,6 @@ rec { We can inject these into a pattern for the whole of a structured platform, and then match that. - # Inputs `platform` @@ -225,7 +226,6 @@ rec { : 2\. Function argument - # Examples :::{.example} ## `lib.meta.platformMatch` usage example @@ -237,21 +237,24 @@ rec { ::: */ - platformMatch = platform: elem: ( - # Check with simple string comparison if elem was a string. - # - # The majority of comparisons done with this function will be against meta.platforms - # which contains a simple platform string. - # - # Avoiding an attrset allocation results in significant performance gains (~2-30) across the board in OfBorg - # because this is a hot path for nixpkgs. - if isString elem then platform ? system && elem == platform.system - else matchAttrs ( - # Normalize platform attrset. - if elem ? parsed then elem - else { parsed = elem; } - ) platform - ); + platformMatch = + platform: elem: + ( + # Check with simple string comparison if elem was a string. + # + # The majority of comparisons done with this function will be against meta.platforms + # which contains a simple platform string. + # + # Avoiding an attrset allocation results in significant performance gains (~2-30) across the board in OfBorg + # because this is a hot path for nixpkgs. + if isString elem then + platform ? system && elem == platform.system + else + matchAttrs ( + # Normalize platform attrset. + if elem ? parsed then elem else { parsed = elem; } + ) platform + ); /** Check if a package is available on a given platform. @@ -263,7 +266,6 @@ rec { 2. None of `meta.badPlatforms` pattern matches the given platform. - # Inputs `platform` @@ -274,7 +276,6 @@ rec { : 2\. Function argument - # Examples :::{.example} ## `lib.meta.availableOn` usage example @@ -286,9 +287,10 @@ rec { ::: */ - availableOn = platform: pkg: - ((!pkg?meta.platforms) || any (platformMatch platform) pkg.meta.platforms) && - all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []); + availableOn = + platform: pkg: + ((!pkg ? meta.platforms) || any (platformMatch platform) pkg.meta.platforms) + && all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or [ ]); /** Mapping of SPDX ID to the attributes in lib.licenses. @@ -309,13 +311,10 @@ rec { ::: */ - licensesSpdx = - mapAttrs' - (_key: license: { - name = license.spdxId; - value = license; - }) - (filterAttrs (_key: license: license ? spdxId) lib.licenses); + licensesSpdx = mapAttrs' (_key: license: { + name = license.spdxId; + value = license; + }) (filterAttrs (_key: license: license ? spdxId) lib.licenses); /** Get the corresponding attribute in lib.licenses from the SPDX ID @@ -348,10 +347,11 @@ rec { */ getLicenseFromSpdxId = licstr: - getLicenseFromSpdxIdOr licstr ( - lib.warn "getLicenseFromSpdxId: No license matches the given SPDX ID: ${licstr}" - { shortName = licstr; } - ); + getLicenseFromSpdxIdOr licstr ( + lib.warn "getLicenseFromSpdxId: No license matches the given SPDX ID: ${licstr}" { + shortName = licstr; + } + ); /** Get the corresponding attribute in lib.licenses from the SPDX ID @@ -398,13 +398,12 @@ rec { name = lib.toLower name; inherit value; }) licensesSpdx; - in licstr: default: - lowercaseLicenses.${ lib.toLower licstr } or default; + in + licstr: default: lowercaseLicenses.${lib.toLower licstr} or default; /** Get the path to the main program of a package based on meta.mainProgram - # Inputs `x` @@ -430,17 +429,23 @@ rec { ::: */ - getExe = x: getExe' x (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, use getExe' to specify the name to the program, such as lib.getExe' foo \"bar\"." - lib.getName - x - )); + getExe = + x: + getExe' x ( + 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, use getExe' to specify the name to the program, such as lib.getExe' foo \"bar\"." + lib.getName + x + ) + ); /** Get the path of a program of a derivation. - # Inputs `x` @@ -470,7 +475,8 @@ rec { ::: */ - getExe' = x: y: + getExe' = + x: y: assert assertMsg (isDerivation x) "lib.meta.getExe': The first argument is of type ${typeOf x}, but it should be a derivation instead."; assert assertMsg (isString y) diff --git a/lib/modules.nix b/lib/modules.nix index 8e6c6249f61c..a9ddaf7bda02 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -35,7 +35,8 @@ let optionalAttrs optionalString recursiveUpdate - reverseList sort + reverseList + sort seq setAttrByPath substring @@ -61,18 +62,14 @@ let isConvertibleWithToString ; - showDeclPrefix = loc: decl: prefix: - " - option(s) with prefix `${showOption (loc ++ [prefix])}' in module `${decl._file}'"; - showRawDecls = loc: decls: - concatStringsSep "\n" - (sort (a: b: a < b) - (concatMap - (decl: map - (showDeclPrefix loc decl) - (attrNames decl.options) - ) - decls - )); + showDeclPrefix = + loc: decl: prefix: + " - option(s) with prefix `${showOption (loc ++ [ prefix ])}' in module `${decl._file}'"; + showRawDecls = + loc: decls: + concatStringsSep "\n" ( + sort (a: b: a < b) (concatMap (decl: map (showDeclPrefix loc decl) (attrNames decl.options)) decls) + ); /** See https://nixos.org/manual/nixpkgs/unstable/#module-system-lib-evalModules @@ -84,36 +81,41 @@ let config (as the proper arguments need to be replicated at each call to evalModules) and the less declarative the module set is. */ - evalModules = evalModulesArgs@ - { modules - , prefix ? [] - , # This should only be used for special arguments that need to be evaluated - # when resolving module structure (like in imports). For everything else, - # there's _module.args. If specialArgs.modulesPath is defined it will be - # used as the base path for disabledModules. - specialArgs ? {} - , # `class`: - # A nominal type for modules. When set and non-null, this adds a check to - # make sure that only compatible modules are imported. - class ? null - , # This would be remove in the future, Prefer _module.args option instead. - args ? {} - , # This would be remove in the future, Prefer _module.check option instead. - check ? true - }: + evalModules = + evalModulesArgs@{ + modules, + prefix ? [ ], + # This should only be used for special arguments that need to be evaluated + # when resolving module structure (like in imports). For everything else, + # there's _module.args. If specialArgs.modulesPath is defined it will be + # used as the base path for disabledModules. + specialArgs ? { }, + # `class`: + # A nominal type for modules. When set and non-null, this adds a check to + # make sure that only compatible modules are imported. + class ? null, + # This would be remove in the future, Prefer _module.args option instead. + args ? { }, + # This would be remove in the future, Prefer _module.check option instead. + check ? true, + }: let - withWarnings = x: - warnIf (evalModulesArgs?args) "The args argument to evalModules is deprecated. Please set config._module.args instead." - warnIf (evalModulesArgs?check) "The check argument to evalModules is deprecated. Please set config._module.check instead." - x; + withWarnings = + x: + warnIf (evalModulesArgs ? args) + "The args argument to evalModules is deprecated. Please set config._module.args instead." + warnIf + (evalModulesArgs ? check) + "The check argument to evalModules is deprecated. Please set config._module.check instead." + x; legacyModules = - optional (evalModulesArgs?args) { + optional (evalModulesArgs ? args) { config = { _module.args = args; }; } - ++ optional (evalModulesArgs?check) { + ++ optional (evalModulesArgs ? check) { config = { _module.check = mkDefault check; }; @@ -144,9 +146,13 @@ let # Only render documentation once at the root of the option tree, # not for all individual submodules. # Allow merging option decls to make this internal regardless. - ${if prefix == [] - then null # unset => visible - else "internal"} = true; + ${ + if prefix == [ ] then + null # unset => visible + else + "internal" + } = + true; # TODO: Change the type of this option to a submodule with a # freeformType, so that individual arguments can be documented # separately @@ -240,12 +246,22 @@ let }; merged = - let collected = collectModules - class - (specialArgs.modulesPath or "") - (regularModules ++ [ internalModule ]) - ({ inherit lib options config specialArgs; } // specialArgs); - in mergeModules prefix (reverseList collected); + let + collected = + collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) + ( + { + inherit + lib + options + config + specialArgs + ; + } + // specialArgs + ); + in + mergeModules prefix (reverseList collected); options = merged.matchedOptions; @@ -253,7 +269,7 @@ let let # For definitions that have an associated option - declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options; + declaredConfig = mapAttrsRecursiveCond (v: !isOption v) (_: v: v.value) options; # If freeformType is set, this is for definitions that don't have an associated option freeformConfig = @@ -262,17 +278,20 @@ let file = def.file; value = setAttrByPath def.prefix def.value; }) merged.unmatchedDefns; - in if defs == [] then {} - else declaredConfig._module.freeformType.merge prefix defs; + in + if defs == [ ] then { } else declaredConfig._module.freeformType.merge prefix defs; - in if declaredConfig._module.freeformType == null then declaredConfig - # Because all definitions that had an associated option ended in - # declaredConfig, freeformConfig can only contain the non-option - # paths, meaning recursiveUpdate will never override any value - else recursiveUpdate freeformConfig declaredConfig; + in + if declaredConfig._module.freeformType == null then + declaredConfig + # Because all definitions that had an associated option ended in + # declaredConfig, freeformConfig can only contain the non-option + # paths, meaning recursiveUpdate will never override any value + else + recursiveUpdate freeformConfig declaredConfig; checkUnmatched = - if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then + if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then let firstDef = head merged.unmatchedDefns; baseMsg = @@ -281,49 +300,53 @@ let defText = addErrorContext "while evaluating the error message for definitions for `${optText}', which is an option that does not exist" - (addErrorContext - "while evaluating a definition from `${firstDef.file}'" - ( showDefs [ firstDef ]) - ); + (addErrorContext "while evaluating a definition from `${firstDef.file}'" (showDefs [ firstDef ])); in - "The option `${optText}' does not exist. Definition values:${defText}"; + "The option `${optText}' does not exist. Definition values:${defText}"; in - if attrNames options == [ "_module" ] - # No options were declared at all (`_module` is built in) - # but we do have unmatched definitions, and no freeformType (earlier conditions) - then - let - optionName = showOption prefix; - in - if optionName == "" - then throw '' - ${baseMsg} - - It seems as if you're trying to declare an option by placing it into `config' rather than `options'! - '' - else - throw '' - ${baseMsg} + if + attrNames options == [ "_module" ] + # No options were declared at all (`_module` is built in) + # but we do have unmatched definitions, and no freeformType (earlier conditions) + then + let + optionName = showOption prefix; + in + if optionName == "" then + throw '' + ${baseMsg} - However there are no options defined in `${showOption prefix}'. Are you sure you've - declared your options properly? This can happen if you e.g. declared your options in `types.submodule' - under `config' rather than `options'. - '' - else throw baseMsg - else null; + It seems as if you're trying to declare an option by placing it into `config' rather than `options'! + '' + else + throw '' + ${baseMsg} + + However there are no options defined in `${showOption prefix}'. Are you sure you've + declared your options properly? This can happen if you e.g. declared your options in `types.submodule' + under `config' rather than `options'. + '' + else + throw baseMsg + else + null; checked = seq checkUnmatched; - extendModules = extendArgs@{ - modules ? [], - specialArgs ? {}, - prefix ? [], + extendModules = + extendArgs@{ + modules ? [ ], + specialArgs ? { }, + prefix ? [ ], }: - evalModules (evalModulesArgs // { + evalModules ( + evalModulesArgs + // { modules = regularModules ++ modules; - specialArgs = evalModulesArgs.specialArgs or {} // specialArgs; - prefix = extendArgs.prefix or evalModulesArgs.prefix or []; - }); + specialArgs = evalModulesArgs.specialArgs or { } // specialArgs; + prefix = extendArgs.prefix or evalModulesArgs.prefix or [ ]; + } + ); type = types.submoduleWith { inherit modules specialArgs class; @@ -336,16 +359,20 @@ let _module = checked (config._module); inherit extendModules type class; }; - in result; + in + result; # collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ] # # Collects all modules recursively through `import` statements, filtering out # all modules in disabledModules. - collectModules = class: let + collectModules = + class: + let # Like unifyModuleSyntax, but also imports paths and calls functions if necessary - loadModule = args: fallbackFile: fallbackKey: m: + loadModule = + args: fallbackFile: fallbackKey: m: if isFunction m then unifyModuleSyntax fallbackFile fallbackKey (applyModuleArgs fallbackKey m args) else if isAttrs m then @@ -354,27 +381,43 @@ let else if m._type == "if" || m._type == "override" then loadModule args fallbackFile fallbackKey { config = m; } else - throw (messages.not_a_module { inherit fallbackFile; value = m; _type = m._type; expectedClass = class; }) + throw ( + messages.not_a_module { + inherit fallbackFile; + value = m; + _type = m._type; + expectedClass = class; + } + ) else if isList m then - let defs = [{ file = fallbackFile; value = m; }]; in + let + defs = [ + { + file = fallbackFile; + value = m; + } + ]; + in throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}" - else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args); + else + unifyModuleSyntax (toString m) (toString m) ( + applyModuleArgsIfFunction (toString m) (import m) args + ); checkModule = - if class != null - then + if class != null then m: - if m._class == null || m._class == class - then m - else - throw '' - The module `${m._file or m.key}` (class: ${lib.strings.escapeNixString m._class}) cannot be imported into a module evaluation that expects class ${lib.strings.escapeNixString class}. - - Help: - - Ensure that you are importing the correct module. - - Verify that the module's `_class`, ${lib.strings.escapeNixString m._class} matches the expected `class` ${lib.strings.escapeNixString class}. - - If you are using a custom class, make sure it is correctly defined and used consistently across your modules. - '' + if m._class == null || m._class == class then + m + else + throw '' + The module `${m._file or m.key}` (class: ${lib.strings.escapeNixString m._class}) cannot be imported into a module evaluation that expects class ${lib.strings.escapeNixString class}. + + Help: + - Ensure that you are importing the correct module. + - Verify that the module's `_class`, ${lib.strings.escapeNixString m._class} matches the expected `class` ${lib.strings.escapeNixString class}. + - If you are using a custom class, make sure it is correctly defined and used consistently across your modules. + '' else m: m; @@ -409,60 +452,78 @@ let disabled = concatLists (catAttrs "disabled" modules); inherit modules; }; - in parentFile: parentKey: initialModules: args: collectResults (imap1 (n: x: - let - module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x); - collectedImports = collectStructuredModules module._file module.key module.imports args; - in { - key = module.key; - module = module; - modules = collectedImports.modules; - disabled = (if module.disabledModules != [] then [{ file = module._file; disabled = module.disabledModules; }] else []) ++ collectedImports.disabled; - }) initialModules); + in + parentFile: parentKey: initialModules: args: + collectResults ( + imap1 ( + n: x: + let + module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x); + collectedImports = collectStructuredModules module._file module.key module.imports args; + in + { + key = module.key; + module = module; + modules = collectedImports.modules; + disabled = + ( + if module.disabledModules != [ ] then + [ + { + file = module._file; + disabled = module.disabledModules; + } + ] + else + [ ] + ) + ++ collectedImports.disabled; + } + ) initialModules + ); # filterModules :: String -> { disabled, modules } -> [ Module ] # # Filters a structure as emitted by collectStructuredModules by removing all disabled # modules recursively. It returns the final list of unique-by-key modules - filterModules = modulesPath: { disabled, modules }: + filterModules = + modulesPath: + { disabled, modules }: let - moduleKey = file: m: - if isString m - then - if substring 0 1 m == "/" - then m - else toString modulesPath + "/" + m - - else if isConvertibleWithToString m - then - if m?key && m.key != toString m - then + moduleKey = + file: m: + if isString m then + if substring 0 1 m == "/" then m else toString modulesPath + "/" + m + + else if isConvertibleWithToString m then + if m ? key && m.key != toString m then throw "Module `${file}` contains a disabledModules item that is an attribute set that can be converted to a string (${toString m}) but also has a `.key` attribute (${m.key}) with a different value. This makes it ambiguous which module should be disabled." else toString m - else if m?key - then + else if m ? key then m.key - else if isAttrs m - then throw "Module `${file}` contains a disabledModules item that is an attribute set, presumably a module, that does not have a `key` attribute. This means that the module system doesn't have any means to identify the module that should be disabled. Make sure that you've put the correct value in disabledModules: a string path relative to modulesPath, a path value, or an attribute set with a `key` attribute." - else throw "Each disabledModules item must be a path, string, or a attribute set with a key attribute, or a value supported by toString. However, one of the disabledModules items in `${toString file}` is none of that, but is of type ${typeOf m}."; + else if isAttrs m then + throw "Module `${file}` contains a disabledModules item that is an attribute set, presumably a module, that does not have a `key` attribute. This means that the module system doesn't have any means to identify the module that should be disabled. Make sure that you've put the correct value in disabledModules: a string path relative to modulesPath, a path value, or an attribute set with a `key` attribute." + else + throw "Each disabledModules item must be a path, string, or a attribute set with a key attribute, or a value supported by toString. However, one of the disabledModules items in `${toString file}` is none of that, but is of type ${typeOf m}."; disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled; - keyFilter = filter (attrs: ! elem attrs.key disabledKeys); - in map (attrs: attrs.module) (genericClosure { + keyFilter = filter (attrs: !elem attrs.key disabledKeys); + in + map (attrs: attrs.module) (genericClosure { startSet = keyFilter modules; operator = attrs: keyFilter attrs.modules; }); - in modulesPath: initialModules: args: - filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args); + in + modulesPath: initialModules: args: + filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args); /** Wrap a module with a default location for reporting errors. - # Inputs `file` @@ -473,14 +534,15 @@ let : 2\. Function argument */ - setDefaultModuleLocation = file: m: - { _file = file; imports = [ m ]; }; + setDefaultModuleLocation = file: m: { + _file = file; + imports = [ m ]; + }; /** Massage a module into canonical form, that is, a set consisting of ‘options’, ‘config’ and ‘imports’ attributes. - # Inputs `file` @@ -495,44 +557,82 @@ let : 3\. Function argument */ - unifyModuleSyntax = file: key: m: + unifyModuleSyntax = + file: key: m: let - addMeta = config: if m ? meta - then mkMerge [ config { meta = m.meta; } ] - else config; - addFreeformType = config: if m ? freeformType - then mkMerge [ config { _module.freeformType = m.freeformType; } ] - else config; + addMeta = + config: + if m ? meta then + mkMerge [ + config + { meta = m.meta; } + ] + else + config; + addFreeformType = + config: + if m ? freeformType then + mkMerge [ + config + { _module.freeformType = m.freeformType; } + ] + else + config; in if m ? config || m ? options then - let badAttrs = removeAttrs m ["_class" "_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in - if badAttrs != {} then + let + badAttrs = removeAttrs m [ + "_class" + "_file" + "key" + "disabledModules" + "imports" + "options" + "config" + "meta" + "freeformType" + ]; + in + if badAttrs != { } then throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute." else - { _file = toString m._file or file; + { + _file = toString m._file or file; _class = m._class or null; key = toString m.key or key; - disabledModules = m.disabledModules or []; - imports = m.imports or []; - options = m.options or {}; - config = addFreeformType (addMeta (m.config or {})); + disabledModules = m.disabledModules or [ ]; + imports = m.imports or [ ]; + options = m.options or { }; + config = addFreeformType (addMeta (m.config or { })); } else # shorthand syntax - throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module." - { _file = toString m._file or file; + throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module." { + _file = toString m._file or file; _class = m._class or null; key = toString m.key or key; - disabledModules = m.disabledModules or []; - imports = m.require or [] ++ m.imports or []; - options = {}; - config = addFreeformType (removeAttrs m ["_class" "_file" "key" "disabledModules" "require" "imports" "freeformType"]); + disabledModules = m.disabledModules or [ ]; + imports = m.require or [ ] ++ m.imports or [ ]; + options = { }; + config = addFreeformType ( + removeAttrs m [ + "_class" + "_file" + "key" + "disabledModules" + "require" + "imports" + "freeformType" + ] + ); }; - applyModuleArgsIfFunction = key: f: args@{ config, ... }: - if isFunction f then applyModuleArgs key f args else f; + applyModuleArgsIfFunction = + key: f: args@{ config, ... }: if isFunction f then applyModuleArgs key f args else f; - applyModuleArgs = key: f: args@{ config, ... }: + applyModuleArgs = + key: f: + args@{ config, ... }: let # Module arguments are resolved in a strict manner when attribute set # deconstruction is used. As the arguments are now defined with the @@ -547,16 +647,16 @@ let # not their values. The values are forwarding the result of the # evaluation of the option. context = name: ''while evaluating the module argument `${name}' in "${key}":''; - extraArgs = mapAttrs (name: _: - addErrorContext (context name) - (args.${name} or config._module.args.${name}) + extraArgs = mapAttrs ( + name: _: addErrorContext (context name) (args.${name} or config._module.args.${name}) ) (functionArgs f); # Note: we append in the opposite order such that we can add an error # context on the explicit arguments of "args" too. This update # operator is used to make the "args@{ ... }: with args.lib;" notation # works. - in f (args // extraArgs); + in + f (args // extraArgs); /** Merge a list of modules. This will recurse over the option @@ -580,7 +680,6 @@ let ]; } - # Inputs `prefix` @@ -591,86 +690,95 @@ let : 2\. Function argument */ - mergeModules = prefix: modules: - mergeModules' prefix modules - (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules); - - mergeModules' = prefix: modules: configs: + mergeModules = + prefix: modules: + mergeModules' prefix modules ( + concatMap ( + m: + map (config: { + file = m._file; + inherit config; + }) (pushDownProperties m.config) + ) modules + ); + + mergeModules' = + prefix: modules: configs: let # an attrset 'name' => list of submodules that declare ‘name’. - declsByName = - zipAttrsWith - (n: v: v) - (map - (module: let subtree = module.options; in - if !(isAttrs subtree) then - throw '' - An option declaration for `${concatStringsSep "." prefix}' has type - `${typeOf subtree}' rather than an attribute set. - Did you mean to define this outside of `options'? - '' - else - mapAttrs - (n: option: - { inherit (module) _file; pos = unsafeGetAttrPos n subtree; options = option; } - ) - subtree - ) - modules); + declsByName = zipAttrsWith (n: v: v) ( + map ( + module: + let + subtree = module.options; + in + if !(isAttrs subtree) then + throw '' + An option declaration for `${concatStringsSep "." prefix}' has type + `${typeOf subtree}' rather than an attribute set. + Did you mean to define this outside of `options'? + '' + else + mapAttrs (n: option: { + inherit (module) _file; + pos = unsafeGetAttrPos n subtree; + options = option; + }) subtree + ) modules + ); # The root of any module definition must be an attrset. checkedConfigs = - assert - 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 `${typeOf c.config}' - rather than an attribute set for the option - `${concatStringsSep "." prefix}'! - - This usually happens if `${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; + assert 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 `${typeOf c.config}' + rather than an attribute set for the option + `${concatStringsSep "." prefix}'! + + This usually happens if `${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’. - pushedDownDefinitionsByName = - zipAttrsWith - (n: concatLists) - (map - (module: - mapAttrs - (n: value: - map (config: { inherit (module) file; inherit config; }) (pushDownProperties value) - ) - module.config - ) - checkedConfigs); + 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 - rawDefinitionsByName = - zipAttrsWith - (n: v: v) - (map - (module: - mapAttrs - (n: value: - { inherit (module) file; inherit value; } - ) - module.config - ) - checkedConfigs); + rawDefinitionsByName = zipAttrsWith (n: v: v) ( + 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: - if isOption decl.options - then decl - else decl // { + optionTreeToOption = + decl: + if isOption decl.options then + decl + else + decl + // { options = mkOption { type = types.submoduleWith { modules = [ { options = decl.options; } ]; @@ -682,52 +790,57 @@ let }; }; - resultsByName = mapAttrs (name: decls: + resultsByName = mapAttrs ( + name: decls: # We're descending into attribute ‘name’. let - loc = prefix ++ [name]; - defns = pushedDownDefinitionsByName.${name} or []; - defns' = rawDefinitionsByName.${name} or []; - optionDecls = filter - (m: m.options?_type - && (m.options._type == "option" - || throwDeclarationTypeError loc m.options._type m._file - ) - ) - decls; + loc = prefix ++ [ name ]; + defns = pushedDownDefinitionsByName.${name} or [ ]; + defns' = rawDefinitionsByName.${name} or [ ]; + optionDecls = filter ( + m: + m.options ? _type + && (m.options._type == "option" || throwDeclarationTypeError loc m.options._type m._file) + ) decls; in - if length optionDecls == length decls then - let opt = fixupOptionType loc (mergeOptionDecls loc decls); - in { + if length optionDecls == length decls then + let + opt = fixupOptionType loc (mergeOptionDecls loc decls); + in + { + matchedOptions = evalOptionValue loc opt defns'; + unmatchedDefns = [ ]; + } + else if optionDecls != [ ] then + 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 + # attrset.foo.bar, this could mean: + # a. option `bar` is only available in `attrset.foo` + # b. option `foo.bar` is available in all `attrset.*` + # c. reject and require "" as a reminder that it behaves like (b). + # d. magically combine (a) and (c). + # All of the above are merely syntax sugar though. + then + let + opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls)); + in + { matchedOptions = evalOptionValue loc opt defns'; - unmatchedDefns = []; + unmatchedDefns = [ ]; } - else if optionDecls != [] then - 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 - # attrset.foo.bar, this could mean: - # a. option `bar` is only available in `attrset.foo` - # b. option `foo.bar` is available in all `attrset.*` - # c. reject and require "" as a reminder that it behaves like (b). - # d. magically combine (a) and (c). - # All of the above are merely syntax sugar though. - then - let opt = fixupOptionType loc (mergeOptionDecls loc (map optionTreeToOption decls)); - in { - matchedOptions = evalOptionValue loc opt defns'; - unmatchedDefns = []; - } - else - let - nonOptions = filter (m: !isOption m.options) decls; - in - throw "The option `${showOption loc}' in module `${(head optionDecls)._file}' would be a parent of the following options, but its type `${(head optionDecls).options.type.description or ""}' does not support nested options.\n${ - showRawDecls loc nonOptions - }" else - mergeModules' loc decls defns) declsByName; + let + nonOptions = filter (m: !isOption m.options) decls; + in + throw "The option `${showOption loc}' in module `${(head optionDecls)._file}' would be a parent of the following options, but its type `${ + (head optionDecls).options.type.description or "" + }' does not support nested options.\n${showRawDecls loc nonOptions}" + else + mergeModules' loc decls defns + ) declsByName; matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName; @@ -737,34 +850,44 @@ let mapAttrs (n: v: v.unmatchedDefns) resultsByName # Plus the definitions for the current prefix that don't have a matching option // removeAttrs rawDefinitionsByName (attrNames matchedOptions); - in { + in + { inherit matchedOptions; # Transforms unmatchedDefnsByName into a list of definitions unmatchedDefns = - if configs == [] - then + if configs == [ ] then # When no config values exist, there can be no unmatched config, so # we short circuit and avoid evaluating more _options_ than necessary. - [] + [ ] else - concatLists (mapAttrsToList (name: defs: - map (def: def // { - # Set this so we know when the definition first left unmatched territory - prefix = [name] ++ (def.prefix or []); - }) defs - ) unmatchedDefnsByName); + concatLists ( + mapAttrsToList ( + name: defs: + map ( + def: + def + // { + # Set this so we know when the definition first left unmatched territory + prefix = [ name ] ++ (def.prefix or [ ]); + } + ) defs + ) unmatchedDefnsByName + ); }; - throwDeclarationTypeError = loc: actualTag: file: + throwDeclarationTypeError = + loc: actualTag: file: let name = lib.strings.escapeNixIdentifier (lib.lists.last loc); path = showOption loc; depth = length loc; - paragraphs = [ - "In module ${file}: expected an option declaration at option path `${path}` but got an attribute set with type ${actualTag}" - ] ++ optional (actualTag == "option-type") '' + paragraphs = + [ + "In module ${file}: expected an option declaration at option path `${path}` but got an attribute set with type ${actualTag}" + ] + ++ optional (actualTag == "option-type") '' When declaring an option, you must wrap the type in a `mkOption` call. It should look somewhat like: ${comment} ${name} = lib.mkOption { @@ -795,7 +918,6 @@ let 'opts' is a list of modules. Each module has an options attribute which correspond to the definition of 'loc' in 'opt.file'. - # Inputs `loc` @@ -807,17 +929,22 @@ let : 2\. Function argument */ mergeOptionDecls = - loc: opts: - foldl' (res: opt: - let t = res.type; + loc: opts: + foldl' + ( + res: opt: + let + t = res.type; t' = opt.options.type; mergedType = t.typeMerge t'.functor; typesMergeable = mergedType != null; # TODO: Remove this when all downstream reliances of internals: 'functor.wrapped' are sufficiently migrated. # A function that adds the deprecated wrapped message to a type. - addDeprecatedWrapped = t: - t // { + addDeprecatedWrapped = + t: + t + // { functor = t.functor // { wrapped = t.functor.wrappedDeprecationMessage { inherit loc; @@ -838,47 +965,67 @@ let } else # Keep in sync with the same error below! - throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}." + throw + "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}." else if opt.options.type ? functor.wrappedDeprecationMessage then { type = addDeprecatedWrapped opt.options.type; } else - {} + { } else - {}; + { }; bothHave = k: opt.options ? ${k} && res ? ${k}; - in - if bothHave "default" || - bothHave "example" || - bothHave "description" || - bothHave "apply" - then - # Keep in sync with the same error above! - throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}." - else - let - getSubModules = opt.options.type.getSubModules or null; - submodules = - if getSubModules != null then map (setDefaultModuleLocation opt._file) getSubModules ++ res.options - else res.options; - in opt.options // res // - { declarations = res.declarations ++ [opt._file]; + in + if bothHave "default" || bothHave "example" || bothHave "description" || bothHave "apply" then + # Keep in sync with the same error above! + throw + "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}." + else + let + getSubModules = opt.options.type.getSubModules or null; + submodules = + if getSubModules != null then + map (setDefaultModuleLocation opt._file) getSubModules ++ res.options + else + res.options; + in + opt.options + // res + // { + declarations = res.declarations ++ [ opt._file ]; # In the case of modules that are generated dynamically, we won't # have exact declaration lines; fall back to just the file being # evaluated. - declarationPositions = res.declarationPositions - ++ (if opt.pos != null - then [opt.pos] - else [{ file = opt._file; line = null; column = null; }]); + declarationPositions = + res.declarationPositions + ++ ( + if opt.pos != null then + [ opt.pos ] + else + [ + { + file = opt._file; + line = null; + column = null; + } + ] + ); options = submodules; - } // typeSet - ) { inherit loc; declarations = []; declarationPositions = []; options = []; } opts; + } + // typeSet + ) + { + inherit loc; + declarations = [ ]; + declarationPositions = [ ]; + options = [ ]; + } + opts; /** Merge all the definitions of an option to produce the final config value. - # Inputs `loc` @@ -893,12 +1040,16 @@ let : 3\. Function argument */ - evalOptionValue = loc: opt: defs: + evalOptionValue = + loc: opt: defs: let # Add in the default value for this option, if any. defs' = - (optional (opt ? default) - { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; + (optional (opt ? default) { + file = head opt.declarations; + value = mkOptionDefault opt.default; + }) + ++ defs; # Handle properties, check types, and merge everything together. res = @@ -906,10 +1057,15 @@ let let # For a better error message, evaluate all readOnly definitions as # if they were the only definition. - separateDefs = map (def: def // { - value = (mergeDefinitions loc opt.type [ def ]).mergedValue; - }) defs'; - in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}" + separateDefs = map ( + def: + def + // { + value = (mergeDefinitions loc opt.type [ def ]).mergedValue; + } + ) defs'; + in + throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}" else mergeDefinitions loc opt.type defs'; @@ -921,24 +1077,30 @@ let warnIf (opt.type.deprecationMessage != null) "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}"; - in warnDeprecation opt // - { value = addErrorContext "while evaluating the option `${showOption loc}':" value; - inherit (res.defsFinal') highestPrio; - definitions = map (def: def.value) res.defsFinal; - files = map (def: def.file) res.defsFinal; - definitionsWithLocations = res.defsFinal; - inherit (res) isDefined; - # This allows options to be correctly displayed using `${options.path.to.it}` - __toString = _: showOption loc; - }; + in + warnDeprecation opt + // { + value = addErrorContext "while evaluating the option `${showOption loc}':" value; + inherit (res.defsFinal') highestPrio; + definitions = map (def: def.value) res.defsFinal; + files = map (def: def.file) res.defsFinal; + definitionsWithLocations = res.defsFinal; + inherit (res) isDefined; + # This allows options to be correctly displayed using `${options.path.to.it}` + __toString = _: showOption loc; + }; # Merge definitions of a value of a given type. mergeDefinitions = loc: type: defs: rec { defsFinal' = let # Process mkMerge and mkIf properties. - defs' = concatMap (m: - map (value: { inherit (m) file; inherit value; }) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value)) + defs' = concatMap ( + m: + map (value: { + inherit (m) file; + inherit value; + }) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value)) ) defs; # Process mkOverride properties. @@ -947,10 +1109,12 @@ let # Sort mkOrder properties. defs''' = # Avoid sorting if we don't have to. - if any (def: def.value._type or "" == "order") defs''.values - then sortProperties defs''.values - else defs''.values; - in { + if any (def: def.value._type or "" == "order") defs''.values then + sortProperties defs''.values + else + defs''.values; + in + { values = defs'''; inherit (defs'') highestPrio; }; @@ -959,19 +1123,22 @@ let # Type-check the remaining definitions, and merge them. Or throw if no definitions. mergedValue = if isDefined then - if all (def: type.check def.value) defsFinal then type.merge loc defsFinal - else let allInvalid = filter (def: ! type.check def.value) defsFinal; - in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}" + if all (def: type.check def.value) defsFinal then + type.merge loc defsFinal + else + let + allInvalid = filter (def: !type.check def.value) defsFinal; + in + throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}" else # (nixos-option detects this specific error message and gives it special # handling. If changed here, please change it there too.) - throw "The option `${showOption loc}' was accessed but has no value defined. Try setting the option."; + throw + "The option `${showOption loc}' was accessed but has no value defined. Try setting the option."; - isDefined = defsFinal != []; + isDefined = defsFinal != [ ]; - optionalValue = - if isDefined then { value = mergedValue; } - else {}; + optionalValue = if isDefined then { value = mergedValue; } else { }; }; /** @@ -990,21 +1157,22 @@ let to refer to the full configuration without creating an infinite recursion. - # Inputs `cfg` : 1\. Function argument */ - pushDownProperties = cfg: + pushDownProperties = + cfg: if cfg._type or "" == "merge" then concatMap pushDownProperties cfg.contents else if cfg._type or "" == "if" then map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) else if cfg._type or "" == "override" then map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) - else # FIXME: handle mkOrder? + # FIXME: handle mkOrder? + else [ cfg ]; /** @@ -1018,22 +1186,19 @@ let yields ‘[ 1 2 ]’. - # Inputs `def` : 1\. Function argument */ - dischargeProperties = def: + dischargeProperties = + def: if def._type or "" == "merge" then concatMap dischargeProperties def.contents else if def._type or "" == "if" then if isBool def.condition then - if def.condition then - dischargeProperties def.content - else - [ ] + if def.condition then dischargeProperties def.content else [ ] else throw "‘mkIf’ called with a non-Boolean condition" else @@ -1059,7 +1224,6 @@ let Note that "z" has the default priority 100. - # Inputs `defs` @@ -1068,13 +1232,17 @@ let */ filterOverrides = defs: (filterOverrides' defs).values; - filterOverrides' = defs: + filterOverrides' = + defs: let - getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultOverridePriority; + getPrio = + def: if def.value._type or "" == "override" then def.value.priority else defaultOverridePriority; highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; - strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; - in { - values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; + strip = + def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; + in + { + values = concatMap (def: if getPrio def == highestPrio then [ (strip def) ] else [ ]) defs; inherit highestPrio; }; @@ -1083,31 +1251,43 @@ let defaultOrderPriority by default, but can be overridden by wrapping the property using mkOrder. - # Inputs `defs` : 1\. Function argument */ - sortProperties = defs: + sortProperties = + defs: let - strip = def: - if def.value._type or "" == "order" - then def // { value = def.value.content; inherit (def.value) priority; } - else def; + strip = + def: + if def.value._type or "" == "order" then + def + // { + value = def.value.content; + inherit (def.value) priority; + } + else + def; defs' = map strip defs; compare = a: b: (a.priority or defaultOrderPriority) < (b.priority or defaultOrderPriority); - in sort compare defs'; + in + sort compare defs'; # This calls substSubModules, whose entire purpose is only to ensure that # option declarations in submodules have accurate position information. # TODO: Merge this into mergeOptionDecls - fixupOptionType = loc: opt: - if opt.type.getSubModules or null == null - then opt // { type = opt.type or types.unspecified; } - else opt // { type = opt.type.substSubModules opt.options; options = []; }; - + fixupOptionType = + loc: opt: + if opt.type.getSubModules or null == null then + opt // { type = opt.type or types.unspecified; } + else + opt + // { + type = opt.type.substSubModules opt.options; + options = [ ]; + }; /** Merge an option's definitions in a way that preserves the priority of the @@ -1115,7 +1295,6 @@ let This does not account for all option semantics, such as readOnly. - # Inputs `opt` @@ -1128,35 +1307,39 @@ let option -> attrsOf { highestPrio, value } ``` */ - mergeAttrDefinitionsWithPrio = opt: - let - defsByAttr = - zipAttrs ( - concatLists ( - concatMap - ({ value, ... }@def: - map - (mapAttrsToList (k: value: { ${k} = def // { inherit value; }; })) - (pushDownProperties value) - ) - opt.definitionsWithLocations - ) - ); - in - assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf"; - mapAttrs - (k: v: - let merging = mergeDefinitions (opt.loc ++ [k]) opt.type.nestedTypes.elemType v; - in { - value = merging.mergedValue; - inherit (merging.defsFinal') highestPrio; - }) - defsByAttr; + mergeAttrDefinitionsWithPrio = + opt: + let + defsByAttr = zipAttrs ( + concatLists ( + concatMap ( + { value, ... }@def: + map (mapAttrsToList ( + k: value: { + ${k} = def // { + inherit value; + }; + } + )) (pushDownProperties value) + ) opt.definitionsWithLocations + ) + ); + in + assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf"; + mapAttrs ( + k: v: + let + merging = mergeDefinitions (opt.loc ++ [ k ]) opt.type.nestedTypes.elemType v; + in + { + value = merging.mergedValue; + inherit (merging.defsFinal') highestPrio; + } + ) defsByAttr; /** Properties. - # Inputs `condition` @@ -1168,25 +1351,24 @@ let : 2\. Function argument */ - mkIf = condition: content: - { _type = "if"; - inherit condition content; - }; + mkIf = condition: content: { + _type = "if"; + inherit condition content; + }; - mkAssert = assertion: message: content: - mkIf - (if assertion then true else throw "\nFailed assertion: ${message}") - content; + mkAssert = + assertion: message: content: + mkIf (if assertion then true else throw "\nFailed assertion: ${message}") content; - mkMerge = contents: - { _type = "merge"; - inherit contents; - }; + mkMerge = contents: { + _type = "merge"; + inherit contents; + }; - mkOverride = priority: content: - { _type = "override"; - inherit priority content; - }; + mkOverride = priority: content: { + _type = "override"; + inherit priority content; + }; mkOptionDefault = mkOverride 1500; # priority of option defaults mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default @@ -1195,14 +1377,17 @@ let mkForce = mkOverride 50; mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ - defaultPriority = warnIf (oldestSupportedReleaseIsAtLeast 2305) "lib.modules.defaultPriority is deprecated, please use lib.modules.defaultOverridePriority instead." defaultOverridePriority; + defaultPriority = + warnIf (oldestSupportedReleaseIsAtLeast 2305) + "lib.modules.defaultPriority is deprecated, please use lib.modules.defaultOverridePriority instead." + defaultOverridePriority; mkFixStrictness = warn "lib.mkFixStrictness has no effect and will be removed. It returns its argument unmodified, so you can just remove any calls." id; - mkOrder = priority: content: - { _type = "order"; - inherit priority content; - }; + mkOrder = priority: content: { + _type = "order"; + inherit priority content; + }; mkBefore = mkOrder 500; defaultOrderPriority = 1000; @@ -1228,26 +1413,25 @@ let # mkDefault properties of the previous option. # mkAliasDefinitions = mkAliasAndWrapDefinitions id; - mkAliasAndWrapDefinitions = wrap: option: - mkAliasIfDef option (wrap (mkMerge option.definitions)); + mkAliasAndWrapDefinitions = wrap: option: mkAliasIfDef option (wrap (mkMerge option.definitions)); # Similar to mkAliasAndWrapDefinitions but copies over the priority from the # option as well. # # If a priority is not set, it assumes a priority of defaultOverridePriority. - mkAliasAndWrapDefsWithPriority = wrap: option: + mkAliasAndWrapDefsWithPriority = + wrap: option: let prio = option.highestPrio or defaultOverridePriority; defsWithPrio = map (mkOverride prio) option.definitions; - in mkAliasIfDef option (wrap (mkMerge defsWithPrio)); + in + mkAliasIfDef option (wrap (mkMerge defsWithPrio)); - mkAliasIfDef = option: - mkIf (isOption option && option.isDefined); + mkAliasIfDef = option: mkIf (isOption option && option.isDefined); /** Compatibility. - # Inputs `modules` @@ -1258,8 +1442,12 @@ let : 2\. Function argument */ - fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; - + fixMergeModules = + modules: args: + evalModules { + inherit modules args; + check = false; + }; /** Return a module that causes a warning to be shown if the @@ -1274,7 +1462,6 @@ let or alternatively a reasoning why the functionality is not needed. replacementInstructions SHOULD be provided! - # Inputs `optionName` @@ -1285,20 +1472,29 @@ let : 2\. Function argument */ - mkRemovedOptionModule = optionName: replacementInstructions: + mkRemovedOptionModule = + optionName: replacementInstructions: { options, ... }: - { options = setAttrByPath optionName (mkOption { + { + options = setAttrByPath optionName (mkOption { visible = false; - apply = x: throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}"; + apply = + x: + throw "The option `${showOption optionName}' can no longer be used since it's been removed. ${replacementInstructions}"; }); config.assertions = - let opt = getAttrFromPath optionName options; in [{ - assertion = !opt.isDefined; - message = '' - The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. - ${replacementInstructions} - ''; - }]; + let + opt = getAttrFromPath optionName options; + in + [ + { + assertion = !opt.isDefined; + message = '' + The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. + ${replacementInstructions} + ''; + } + ]; }; /** @@ -1315,7 +1511,6 @@ let This also copies over the priority from the aliased option to the non-aliased option. - # Inputs `from` @@ -1326,36 +1521,39 @@ let : 2\. Function argument */ - mkRenamedOptionModule = from: to: doRename { - inherit from to; - visible = false; - warn = true; - use = trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; - }; + mkRenamedOptionModule = + from: to: + doRename { + inherit from to; + visible = false; + warn = true; + use = trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; + }; - mkRenamedOptionModuleWith = { - /** - Old option path as list of strings. - */ - from, - /** - New option path as list of strings. - */ - to, - - /** - Release number of the first release that contains the rename, ignoring backports. - Set it to the upcoming release, matching the nixpkgs/.version file. - */ - sinceRelease, - - }: doRename { - inherit from to; - visible = false; - warn = oldestSupportedReleaseIsAtLeast sinceRelease; - use = warnIf (oldestSupportedReleaseIsAtLeast sinceRelease) - "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; - }; + mkRenamedOptionModuleWith = + { + /** + Old option path as list of strings. + */ + from, + /** + New option path as list of strings. + */ + to, + + /** + Release number of the first release that contains the rename, ignoring backports. + Set it to the upcoming release, matching the nixpkgs/.version file. + */ + sinceRelease, + + }: + doRename { + inherit from to; + visible = false; + warn = oldestSupportedReleaseIsAtLeast sinceRelease; + use = warnIf (oldestSupportedReleaseIsAtLeast sinceRelease) "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; + }; /** Return a module that causes a warning to be shown if any of the "from" @@ -1386,7 +1584,6 @@ let This show a warning if any a.b.c or d.e.f is set, and set the value of x.y.z to the result of the merge function - # Inputs `from` @@ -1401,28 +1598,40 @@ let : 3\. Function argument */ - mkMergedOptionModule = from: to: mergeFn: + mkMergedOptionModule = + from: to: mergeFn: { config, options, ... }: { - options = foldl' recursiveUpdate {} (map (path: setAttrByPath path (mkOption { - visible = false; - # To use the value in mergeFn without triggering errors - default = "_mkMergedOptionModule"; - })) from); - - config = { - warnings = filter (x: x != "") (map (f: - let val = getAttrFromPath f config; - opt = getAttrFromPath f options; - in - optionalString - (val != "_mkMergedOptionModule") - "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly." - ) from); - } // setAttrByPath to (mkMerge - (optional - (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) - (mergeFn config))); + options = foldl' recursiveUpdate { } ( + map ( + path: + setAttrByPath path (mkOption { + visible = false; + # To use the value in mergeFn without triggering errors + default = "_mkMergedOptionModule"; + }) + ) from + ); + + config = + { + warnings = filter (x: x != "") ( + map ( + f: + let + val = getAttrFromPath f config; + opt = getAttrFromPath f options; + in + optionalString (val != "_mkMergedOptionModule") + "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly." + ) from + ); + } + // setAttrByPath to ( + mkMerge ( + optional (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) (mergeFn config) + ) + ); }; /** @@ -1449,7 +1658,6 @@ let This show a warning if a.b.c is set, and set the value of x.y.z to the result of the change function - # Inputs `from` @@ -1464,13 +1672,13 @@ let : 3\. Function argument */ - mkChangedOptionModule = from: to: changeFn: + mkChangedOptionModule = + from: to: changeFn: mkMergedOptionModule [ from ] to changeFn; /** Like ‘mkRenamedOptionModule’, but doesn't show a warning. - # Inputs `from` @@ -1481,12 +1689,14 @@ let : 2\. Function argument */ - mkAliasOptionModule = from: to: doRename { - inherit from to; - visible = true; - warn = false; - use = id; - }; + mkAliasOptionModule = + from: to: + doRename { + inherit from to; + visible = true; + warn = false; + use = id; + }; /** Transitional version of mkAliasOptionModule that uses MD docs. @@ -1505,7 +1715,6 @@ let It takes care of setting the right priority using `mkOverride`. - # Inputs `opt` @@ -1522,106 +1731,114 @@ let # to all messages that report option locations "this value was derived from # which was defined in ". It can provide a trace of options that contributed # to definitions. - mkDerivedConfig = opt: f: - mkOverride - (opt.highestPrio or defaultOverridePriority) - (f opt.value); + mkDerivedConfig = opt: f: mkOverride (opt.highestPrio or defaultOverridePriority) (f opt.value); /** Return a module that help declares an option that has been renamed. When a value is defined for the old option, it is forwarded to the `to` option. */ - doRename = { - # List of strings representing the attribute path of the old option. - from, - # List of strings representing the attribute path of the new option. - to, - # Boolean, whether the old option is to be included in documentation. - visible, - # Whether to warn when a value is defined for the old option. - # NOTE: This requires the NixOS assertions module to be imported, so - # - this generally does not work in submodules - # - this may or may not work outside NixOS - warn, - # A function that is applied to the option value, to form the value - # of the old `from` option. - # - # For example, the identity function can be passed, to return the option value unchanged. - # ```nix - # use = x: x; - # ``` - # - # To add a warning, you can pass the partially applied `warn` function. - # ```nix - # use = lib.warn "Obsolete option `${opt.old}' is used. Use `${opt.to}' instead."; - # ``` - use, - # Legacy option, enabled by default: whether to preserve the priority of definitions in `old`. - withPriority ? true, - # A boolean that defines the `mkIf` condition for `to`. - # If the condition evaluates to `true`, and the `to` path points into an - # `attrsOf (submodule ...)`, then `doRename` would cause an empty module to - # be created, even if the `from` option is undefined. - # By setting this to an expression that may return `false`, you can inhibit - # this undesired behavior. - # - # Example: - # - # ```nix - # { config, lib, ... }: - # let - # inherit (lib) mkOption mkEnableOption types doRename; - # in - # { - # options = { - # - # # Old service - # services.foo.enable = mkEnableOption "foo"; - # - # # New multi-instance service - # services.foos = mkOption { - # type = types.attrsOf (types.submodule …); - # }; - # }; - # imports = [ - # (doRename { - # from = [ "services" "foo" "bar" ]; - # to = [ "services" "foos" "" "bar" ]; - # visible = true; - # warn = false; - # use = x: x; - # withPriority = true; - # # Only define services.foos."" if needed. (It's not just about `bar`) - # condition = config.services.foo.enable; - # }) - # ]; - # } - # ``` - condition ? true - }: + doRename = + { + # List of strings representing the attribute path of the old option. + from, + # List of strings representing the attribute path of the new option. + to, + # Boolean, whether the old option is to be included in documentation. + visible, + # Whether to warn when a value is defined for the old option. + # NOTE: This requires the NixOS assertions module to be imported, so + # - this generally does not work in submodules + # - this may or may not work outside NixOS + warn, + # A function that is applied to the option value, to form the value + # of the old `from` option. + # + # For example, the identity function can be passed, to return the option value unchanged. + # ```nix + # use = x: x; + # ``` + # + # To add a warning, you can pass the partially applied `warn` function. + # ```nix + # use = lib.warn "Obsolete option `${opt.old}' is used. Use `${opt.to}' instead."; + # ``` + use, + # Legacy option, enabled by default: whether to preserve the priority of definitions in `old`. + withPriority ? true, + # A boolean that defines the `mkIf` condition for `to`. + # If the condition evaluates to `true`, and the `to` path points into an + # `attrsOf (submodule ...)`, then `doRename` would cause an empty module to + # be created, even if the `from` option is undefined. + # By setting this to an expression that may return `false`, you can inhibit + # this undesired behavior. + # + # Example: + # + # ```nix + # { config, lib, ... }: + # let + # inherit (lib) mkOption mkEnableOption types doRename; + # in + # { + # options = { + # + # # Old service + # services.foo.enable = mkEnableOption "foo"; + # + # # New multi-instance service + # services.foos = mkOption { + # type = types.attrsOf (types.submodule …); + # }; + # }; + # imports = [ + # (doRename { + # from = [ "services" "foo" "bar" ]; + # to = [ "services" "foos" "" "bar" ]; + # visible = true; + # warn = false; + # use = x: x; + # withPriority = true; + # # Only define services.foos."" if needed. (It's not just about `bar`) + # condition = config.services.foo.enable; + # }) + # ]; + # } + # ``` + condition ? true, + }: { config, options, ... }: let fromOpt = getAttrFromPath from options; - toOf = attrByPath to - (abort "Renaming error: option `${showOption to}' does not exist."); - toType = let opt = attrByPath to {} options; in opt.type or (types.submodule {}); + toOf = attrByPath to (abort "Renaming error: option `${showOption to}' does not exist."); + toType = + let + opt = attrByPath to { } options; + in + opt.type or (types.submodule { }); in { - options = setAttrByPath from (mkOption { - inherit visible; - description = "Alias of {option}`${showOption to}`."; - apply = x: use (toOf config); - } // optionalAttrs (toType != null) { - type = toType; - }); + options = setAttrByPath from ( + mkOption { + inherit visible; + description = "Alias of {option}`${showOption to}`."; + apply = x: use (toOf config); + } + // optionalAttrs (toType != null) { + type = toType; + } + ); config = mkIf condition (mkMerge [ (optionalAttrs (options ? warnings) { - warnings = optional (warn && fromOpt.isDefined) - "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; + warnings = + optional (warn && fromOpt.isDefined) + "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; }) - (if withPriority - then mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt - else mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt) + ( + if withPriority then + mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt + else + mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt + ) ]); }; @@ -1671,18 +1888,15 @@ let options = ...; config = ... bar ...; } - */ importApply = - modulePath: staticArg: - lib.setDefaultModuleLocation modulePath (import modulePath staticArg); + modulePath: staticArg: lib.setDefaultModuleLocation modulePath (import modulePath staticArg); /** Use this function to import a JSON file as NixOS configuration. modules.importJSON :: path -> attrs - # Inputs `file` @@ -1699,7 +1913,6 @@ let modules.importTOML :: path -> attrs - # Inputs `file` @@ -1711,19 +1924,23 @@ let config = lib.importTOML file; }; - private = mapAttrs - (k: warn "External use of `lib.modules.${k}` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/.") - { - inherit - applyModuleArgsIfFunction - dischargeProperties - mergeModules - mergeModules' - pushDownProperties - unifyModuleSyntax - ; - collectModules = collectModules null; - }; + private = + mapAttrs + ( + k: + warn "External use of `lib.modules.${k}` is deprecated. If your use case isn't covered by non-deprecated functions, we'd like to know more and perhaps support your use case well, instead of providing access to these low level functions. In this case please open an issue in https://github.com/nixos/nixpkgs/issues/." + ) + { + inherit + applyModuleArgsIfFunction + dischargeProperties + mergeModules + mergeModules' + pushDownProperties + unifyModuleSyntax + ; + collectModules = collectModules null; + }; /** Error messages produced by the module system. @@ -1734,61 +1951,76 @@ let lower_snake_case. This goes against the convention in order to make the error message implementation more readable, and to visually distinguish them from other functions in the module system. - */ - messages = let - inherit (lib.strings) concatMapStringsSep escapeNixString trim; - /** "" or ", in file FOO" */ - into_fallback_file_maybe = file: - optionalString - (file != null && file != unknownModule) - ", while trying to load a module into ${toString file}"; - - /** Format text with one line break between each list item. */ - lines = concatMapStringsSep "\n" trim; - - /** Format text with two line break between each list item. */ - paragraphs = concatMapStringsSep "\n\n" trim; - - /** - ``` - optionalMatch - { foo = "Foo result"; - bar = "Bar result"; - } "foo" - == [ "Foo result" ] - - optionalMatch { foo = "Foo"; } "baz" == [ ] - - optionalMatch { foo = "Foo"; } true == [ ] - ``` - */ - optionalMatch = cases: value: - if isString value && cases?${value} - then [ cases.${value} ] - else []; - - # esc = builtins.fromJSON "\"\\u001b\""; - esc = builtins.fromJSON "\"\\u001b\""; - # Bold purple for warnings - warn = s: "${esc}[1;35m${s}${esc}[0m"; - # Bold green for suggestions - good = s: "${esc}[1;32m${s}${esc}[0m"; - # Bold, default color for code - code = s: "${esc}[1m${s}${esc}[0m"; - - in { - - /** When load a value with a (wrong) _type as a module */ - not_a_module = { fallbackFile, value, _type, expectedClass ? null }: - paragraphs ( - [ '' - Expected a module, but found a value of type ${warn (escapeNixString _type)}${into_fallback_file_maybe fallbackFile}. - A module is typically loaded by adding it the ${code "imports = [ ... ];"} attribute of an existing module, or in the ${code "modules = [ ... ];"} argument of various functions. - Please make sure that each of the list items is a module, and not a different kind of value. - '' - ] - ++ (optionalMatch - { + */ + messages = + let + inherit (lib.strings) concatMapStringsSep escapeNixString trim; + /** + "" or ", in file FOO" + */ + into_fallback_file_maybe = + file: + optionalString ( + file != null && file != unknownModule + ) ", while trying to load a module into ${toString file}"; + + /** + Format text with one line break between each list item. + */ + lines = concatMapStringsSep "\n" trim; + + /** + Format text with two line break between each list item. + */ + paragraphs = concatMapStringsSep "\n\n" trim; + + /** + ``` + optionalMatch + { foo = "Foo result"; + bar = "Bar result"; + } "foo" + == [ "Foo result" ] + + optionalMatch { foo = "Foo"; } "baz" == [ ] + + optionalMatch { foo = "Foo"; } true == [ ] + ``` + */ + optionalMatch = + cases: value: if isString value && cases ? ${value} then [ cases.${value} ] else [ ]; + + # esc = builtins.fromJSON "\"\\u001b\""; + esc = builtins.fromJSON "\"\\u001b\""; + # Bold purple for warnings + warn = s: "${esc}[1;35m${s}${esc}[0m"; + # Bold green for suggestions + good = s: "${esc}[1;32m${s}${esc}[0m"; + # Bold, default color for code + code = s: "${esc}[1m${s}${esc}[0m"; + + in + { + + /** + When load a value with a (wrong) _type as a module + */ + not_a_module = + { + fallbackFile, + value, + _type, + expectedClass ? null, + }: + paragraphs ( + [ + '' + Expected a module, but found a value of type ${warn (escapeNixString _type)}${into_fallback_file_maybe fallbackFile}. + A module is typically loaded by adding it the ${code "imports = [ ... ];"} attribute of an existing module, or in the ${code "modules = [ ... ];"} argument of various functions. + Please make sure that each of the list items is a module, and not a different kind of value. + '' + ] + ++ (optionalMatch { "configuration" = trim '' If you really mean to import this configuration, instead please only import the modules that make up the configuration. You may have to create a `let` binding, file or attribute to give yourself access to the relevant modules. @@ -1796,21 +2028,24 @@ let ''; # ^^ Extended explanation: That's because a finalized configuration is more than just a set of modules. For instance, it has its own `specialArgs` that, by the nature of `specialArgs` can't be loaded through `imports` or the the `modules` argument. So instead, we have to ask you to extract the relevant modules and use those instead. This way, we keep the module system comparatively simple, and hopefully avoid a bad surprise down the line. - "flake" = lines - ([(trim '' - Perhaps you forgot to select an attribute name? - Instead of, for example, - ${warn "inputs.someflake"} - you need to write something like - ${warn "inputs.someflake"}${ - if expectedClass == null - then good ".modules.someApp.default" - else good ".modules.${expectedClass}.default" - - } - '')] - ++ optionalMatch - { # We'll no more than 5 custom suggestions here. + "flake" = lines ( + [ + (trim '' + Perhaps you forgot to select an attribute name? + Instead of, for example, + ${warn "inputs.someflake"} + you need to write something like + ${warn "inputs.someflake"}${ + if expectedClass == null then + good ".modules.someApp.default" + else + good ".modules.${expectedClass}.default" + + } + '') + ] + ++ optionalMatch { + # We'll no more than 5 custom suggestions here. # Please switch to `.modules.${class}` in your Module System application. "nixos" = trim '' or @@ -1820,18 +2055,15 @@ let or ${warn "inputs.someflake"}${good ".darwinModules.default"} ''; - } - expectedClass + } expectedClass ); - } - _type - ) - ); - }; + } _type) + ); + }; in -private // -{ +private +// { # NOTE: not all of these functions are necessarily public interfaces; some # are just needed by types.nix, but are not meant to be consumed # externally. @@ -1841,17 +2073,17 @@ private // defaultPriority doRename evalModules - evalOptionValue # for use by lib.types + evalOptionValue # for use by lib.types filterOverrides filterOverrides' fixMergeModules - fixupOptionType # should be private? + fixupOptionType # should be private? importApply importJSON importTOML mergeDefinitions mergeAttrDefinitionsWithPrio - mergeOptionDecls # should be private? + mergeOptionDecls # should be private? mkAfter mkAliasAndWrapDefinitions mkAliasAndWrapDefsWithPriority @@ -1878,5 +2110,6 @@ private // mkRenamedOptionModuleWith mkVMOverride setDefaultModuleLocation - sortProperties; + sortProperties + ; } diff --git a/lib/options.nix b/lib/options.nix index 08ac385ff46f..85ce6ca77e92 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -42,7 +42,7 @@ let last ; prioritySuggestion = '' - Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions. + Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions. ''; in rec { @@ -71,7 +71,6 @@ rec { ``` isOption :: a -> Bool ``` - */ isOption = lib.isType "option"; @@ -91,7 +90,6 @@ rec { : Can be any nix value that evaluates. : Usage with `lib.literalMD` or `lib.literalExpression` is supported - `example` : Optional example value used in the manual. : Can be any nix value that evaluates. @@ -144,7 +142,7 @@ rec { internal ? null, visible ? null, readOnly ? null, - } @ attrs: + }@attrs: attrs // { _type = "option"; }; /** @@ -179,12 +177,14 @@ rec { ::: */ - mkEnableOption = name: mkOption { - default = false; - example = true; - description = "Whether to enable ${name}."; - type = lib.types.bool; - }; + mkEnableOption = + name: + mkOption { + default = false; + example = true; + description = "Whether to enable ${name}."; + type = lib.types.bool; + }; /** Creates an Option attribute set for an option that specifies the @@ -220,7 +220,6 @@ rec { If you want users to be able to set no package, pass `nullable = true`. In this mode a `default = null` will not be interpreted as no default and is interpreted literally. - # Inputs `pkgs` @@ -264,39 +263,33 @@ rec { mkPackageOption pkgs "hello" { } => { ...; default = pkgs.hello; defaultText = literalExpression "pkgs.hello"; description = "The hello package to use."; type = package; } - mkPackageOption pkgs "GHC" { default = [ "ghc" ]; example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; } => { ...; default = pkgs.ghc; defaultText = literalExpression "pkgs.ghc"; description = "The GHC package to use."; example = literalExpression "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; type = package; } - mkPackageOption pkgs [ "python3Packages" "pytorch" ] { extraDescription = "This is an example and doesn't actually do anything."; } => { ...; default = pkgs.python3Packages.pytorch; defaultText = literalExpression "pkgs.python3Packages.pytorch"; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = package; } - mkPackageOption pkgs "nushell" { nullable = true; } => { ...; default = pkgs.nushell; defaultText = literalExpression "pkgs.nushell"; description = "The nushell package to use."; type = nullOr package; } - mkPackageOption pkgs "coreutils" { default = null; } => { ...; description = "The coreutils package to use."; type = package; } - mkPackageOption pkgs "dbus" { nullable = true; default = null; } => { ...; default = null; description = "The dbus package to use."; type = nullOr package; } - mkPackageOption pkgs.javaPackages "OpenJFX" { default = "openjfx20"; pkgsText = "pkgs.javaPackages"; @@ -306,35 +299,44 @@ rec { ::: */ - mkPackageOption = pkgs: - name: - { - nullable ? false, - default ? name, - example ? null, - extraDescription ? "", - pkgsText ? "pkgs" - }: - let - name' = if isList name then last name else name; - default' = if isList default then default else [ default ]; - defaultText = concatStringsSep "." default'; - defaultValue = attrByPath default' - (throw "${defaultText} cannot be found in ${pkgsText}") pkgs; - defaults = if default != null then { - default = defaultValue; - defaultText = literalExpression ("${pkgsText}." + defaultText); - } else optionalAttrs nullable { - default = null; - }; - in mkOption (defaults // { - description = "The ${name'} package to use." - + (if extraDescription == "" then "" else " ") + extraDescription; + mkPackageOption = + pkgs: name: + { + nullable ? false, + default ? name, + example ? null, + extraDescription ? "", + pkgsText ? "pkgs", + }: + let + name' = if isList name then last name else name; + default' = if isList default then default else [ default ]; + defaultText = concatStringsSep "." default'; + defaultValue = attrByPath default' (throw "${defaultText} cannot be found in ${pkgsText}") pkgs; + defaults = + if default != null then + { + default = defaultValue; + defaultText = literalExpression ("${pkgsText}." + defaultText); + } + else + optionalAttrs nullable { + default = null; + }; + in + mkOption ( + defaults + // { + description = + "The ${name'} package to use." + (if extraDescription == "" then "" else " ") + extraDescription; type = with lib.types; (if nullable then nullOr else lib.id) package; - } // optionalAttrs (example != null) { - example = literalExpression - (if isList example then "${pkgsText}." + concatStringsSep "." example else example); - }); + } + // optionalAttrs (example != null) { + example = literalExpression ( + if isList example then "${pkgsText}." + concatStringsSep "." example else example + ); + } + ); /** Deprecated alias of mkPackageOption, to be removed in 25.05. @@ -350,25 +352,29 @@ rec { without having to implement similar features as long as the values of the options are not accessed. - # Inputs `attrs` : Attribute set whose attributes override the argument to `mkOption`. */ - mkSinkUndeclaredOptions = attrs: mkOption ({ - internal = true; - visible = false; - default = false; - description = "Sink for option definitions."; - type = mkOptionType { - name = "sink"; - check = x: true; - merge = loc: defs: false; - }; - apply = x: throw "Option value is not readable because the option is not declared."; - } // attrs); + mkSinkUndeclaredOptions = + attrs: + mkOption ( + { + internal = true; + visible = false; + default = false; + description = "Sink for option definitions."; + type = mkOptionType { + name = "sink"; + check = x: true; + merge = loc: defs: false; + }; + apply = x: throw "Option value is not readable because the option is not declared."; + } + // attrs + ); /** A merge function that merges multiple definitions of an option into a single value @@ -413,18 +419,28 @@ rec { - If all definitions are attribute sets, they are merged. (`lib.mergeAttrs`) - If all definitions are functions, the first function is applied to the result of the second function. (`f -> x: f x`) - Otherwise, an error is thrown. - */ - mergeDefaultOption = loc: defs: - let list = getValues defs; in - if length list == 1 then head list - else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) - else if all isList list then concatLists list - else if all isAttrs list then foldl' lib.mergeAttrs {} list - else if all isBool list then foldl' lib.or false list - else if all isString list then lib.concatStrings list - else if all isInt list && all (x: x == head list) list then head list - else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; + mergeDefaultOption = + loc: defs: + let + list = getValues defs; + in + if length list == 1 then + head list + else if all isFunction list then + x: mergeDefaultOption loc (map (f: f x) list) + else if all isList list then + concatLists list + else if all isAttrs list then + foldl' lib.mergeAttrs { } list + else if all isBool list then + foldl' lib.or false list + else if all isString list then + lib.concatStrings list + else if all isInt list && all (x: x == head list) list then + head list + else + throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; /** Require a single definition. @@ -438,7 +454,6 @@ rec { NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc). - # Inputs `loc` @@ -449,24 +464,25 @@ rec { : 3\. Function argument */ - mergeUniqueOption = args@{ + mergeUniqueOption = + args@{ message, # WARNING: the default merge function assumes that the definition is a valid (option) value. You MUST pass a merge function if the return value needs to be # - type checked beyond what .check does (which should be very litte; only on the value head; not attribute values, etc) # - if you want attribute values to be checked, or list items # - if you want coercedTo-like behavior to work - merge ? loc: defs: (head defs).value }: + merge ? loc: defs: (head defs).value, + }: loc: defs: - if length defs == 1 - then merge loc defs - else - assert length defs > 1; - throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; + if length defs == 1 then + merge loc defs + else + assert length defs > 1; + throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}"; /** "Merge" option definitions by checking that they all have the same value. - # Inputs `loc` @@ -477,17 +493,28 @@ rec { : 2\. Function argument */ - mergeEqualOption = loc: defs: - if defs == [] then abort "This case should never happen." + mergeEqualOption = + loc: defs: + if defs == [ ] then + abort "This case should never happen." # Return early if we only have one element # This also makes it work for functions, because the foldl' below would try # to compare the first element with itself, which is false for functions - else if length defs == 1 then (head defs).value - else (foldl' (first: def: - if def.value != first.value then - throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}\n${prioritySuggestion}" - else - first) (head defs) (tail defs)).value; + else if length defs == 1 then + (head defs).value + else + (foldl' ( + first: def: + if def.value != first.value then + throw "The option `${showOption loc}' has conflicting definition values:${ + showDefs [ + first + def + ] + }\n${prioritySuggestion}" + else + first + ) (head defs) (tail defs)).value; /** Extracts values of all "value" keys of the given list. @@ -535,48 +562,50 @@ rec { # Generate documentation template from the list of option declaration like # the set generated with filterOptionSets. - optionAttrSetToDocList = optionAttrSetToDocList' []; + optionAttrSetToDocList = optionAttrSetToDocList' [ ]; - optionAttrSetToDocList' = _: options: - concatMap (opt: + optionAttrSetToDocList' = + _: options: + concatMap ( + opt: let name = showOption opt.loc; - docOption = { - loc = opt.loc; - inherit name; - description = opt.description or null; - declarations = filter (x: x != unknownModule) opt.declarations; - internal = opt.internal or false; - visible = - if (opt?visible && opt.visible == "shallow") - then true - else opt.visible or true; - readOnly = opt.readOnly or false; - type = opt.type.description or "unspecified"; - } - // optionalAttrs (opt ? example) { - example = - builtins.addErrorContext "while evaluating the example of option `${name}`" ( + docOption = + { + loc = opt.loc; + inherit name; + description = opt.description or null; + declarations = filter (x: x != unknownModule) opt.declarations; + internal = opt.internal or false; + visible = if (opt ? visible && opt.visible == "shallow") then true else opt.visible or true; + readOnly = opt.readOnly or false; + type = opt.type.description or "unspecified"; + } + // optionalAttrs (opt ? example) { + example = builtins.addErrorContext "while evaluating the example of option `${name}`" ( renderOptionValue opt.example ); - } - // optionalAttrs (opt ? defaultText || opt ? default) { - default = - builtins.addErrorContext "while evaluating the ${if opt?defaultText then "defaultText" else "default value"} of option `${name}`" ( - renderOptionValue (opt.defaultText or opt.default) - ); - } - // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; + } + // optionalAttrs (opt ? defaultText || opt ? default) { + default = builtins.addErrorContext "while evaluating the ${ + if opt ? defaultText then "defaultText" else "default value" + } of option `${name}`" (renderOptionValue (opt.defaultText or opt.default)); + } + // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { + inherit (opt) relatedPackages; + }; subOptions = - let ss = opt.type.getSubOptions opt.loc; - in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; + let + ss = opt.type.getSubOptions opt.loc; + in + if ss != { } then optionAttrSetToDocList' opt.loc ss else [ ]; subOptionsVisible = docOption.visible && opt.visible or null != "shallow"; in - # To find infinite recursion in NixOS option docs: - # builtins.trace opt.loc - [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options); - + # To find infinite recursion in NixOS option docs: + # builtins.trace opt.loc + [ docOption ] ++ optionals subOptionsVisible subOptions + ) (collect isOption options); /** This function recursively removes all derivation attributes from @@ -590,39 +619,49 @@ rec { This function was made obsolete by renderOptionValue and is kept for compatibility with out-of-tree code. - # Inputs `x` : 1\. Function argument */ - scrubOptionValue = x: + scrubOptionValue = + x: if isDerivation x then - { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } - else if isList x then map scrubOptionValue x - else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) - else x; - + { + type = "derivation"; + drvPath = x.name; + outPath = x.name; + name = x.name; + } + else if isList x then + map scrubOptionValue x + else if isAttrs x then + mapAttrs (n: v: scrubOptionValue v) (removeAttrs x [ "_args" ]) + else + x; /** Ensures that the given option value (default or example) is a `_type`d string by rendering Nix values to `literalExpression`s. - # Inputs `v` : 1\. Function argument */ - renderOptionValue = v: - if v ? _type && v ? text then v - else literalExpression (lib.generators.toPretty { - multiline = true; - allowPrettyValues = true; - } v); - + renderOptionValue = + v: + if v ? _type && v ? text then + v + else + literalExpression ( + lib.generators.toPretty { + multiline = true; + allowPrettyValues = true; + } v + ); /** For use in the `defaultText` and `example` option attributes. Causes the @@ -630,16 +669,21 @@ rec { is necessary for complex values, e.g. functions, or values that depend on other values or packages. - # Inputs `text` : 1\. Function argument */ - literalExpression = text: - if ! isString text then throw "literalExpression expects a string." - else { _type = "literalExpression"; inherit text; }; + literalExpression = + text: + if !isString text then + throw "literalExpression expects a string." + else + { + _type = "literalExpression"; + inherit text; + }; literalExample = lib.warn "lib.literalExample is deprecated, use lib.literalExpression instead, or use lib.literalMD for a non-Nix description." literalExpression; @@ -648,16 +692,21 @@ rec { given MD text to be inserted verbatim in the documentation, for when a `literalExpression` would be too hard to read. - # Inputs `text` : 1\. Function argument */ - literalMD = text: - if ! isString text then throw "literalMD expects a string." - else { _type = "literalMD"; inherit text; }; + literalMD = + text: + if !isString text then + throw "literalMD expects a string." + else + { + _type = "literalMD"; + inherit text; + }; # Helper functions. @@ -665,14 +714,12 @@ rec { Convert an option, described as a list of the option parts to a human-readable version. - # Inputs `parts` : 1\. Function argument - # Examples :::{.example} ## `showOption` usage example @@ -690,36 +737,53 @@ rec { ::: */ - showOption = parts: let - # If the part is a named placeholder of the form "<...>" don't escape it. - # It may cause misleading escaping if somebody uses literally "<...>" in their option names. - # This is the trade-off to allow for placeholders in option names. - isNamedPlaceholder = builtins.match "<(.*)>"; - escapeOptionPart = part: - if part == "*" || isNamedPlaceholder part != null - then part - else lib.strings.escapeNixIdentifier part; - in (concatStringsSep ".") (map escapeOptionPart parts); + showOption = + parts: + let + # If the part is a named placeholder of the form "<...>" don't escape it. + # It may cause misleading escaping if somebody uses literally "<...>" in their option names. + # This is the trade-off to allow for placeholders in option names. + isNamedPlaceholder = builtins.match "<(.*)>"; + escapeOptionPart = + part: + if part == "*" || isNamedPlaceholder part != null then + part + else + lib.strings.escapeNixIdentifier part; + in + (concatStringsSep ".") (map escapeOptionPart parts); showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); - showDefs = defs: concatMapStrings (def: - let - # Pretty print the value for display, if successful - prettyEval = builtins.tryEval - (lib.generators.toPretty { } - (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value)); - # Split it into its lines - lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value); - # Only display the first 5 lines, and indent them for better visibility - value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); - result = - # Don't print any value if evaluating the value strictly fails - if ! prettyEval.success then "" - # Put it on a new line if it consists of multiple - else if length lines > 1 then ":\n " + value - else ": " + value; - in "\n- In `${def.file}'${result}" - ) defs; + showDefs = + defs: + concatMapStrings ( + def: + let + # Pretty print the value for display, if successful + prettyEval = builtins.tryEval ( + lib.generators.toPretty { } ( + lib.generators.withRecursion { + depthLimit = 10; + throwOnDepthLimit = false; + } def.value + ) + ); + # Split it into its lines + lines = filter (v: !isList v) (builtins.split "\n" prettyEval.value); + # Only display the first 5 lines, and indent them for better visibility + value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); + result = + # Don't print any value if evaluating the value strictly fails + if !prettyEval.success then + "" + # Put it on a new line if it consists of multiple + else if length lines > 1 then + ":\n " + value + else + ": " + value; + in + "\n- In `${def.file}'${result}" + ) defs; /** Pretty prints all option definition locations @@ -733,7 +797,6 @@ rec { :::{.example} ## `lib.options.showOptionWithDefLocs` usage example - ```nix showOptionWithDefLocs { loc = ["x" "y" ]; files = [ "foo.nix" "bar.nix" ]; } "x.y, with values defined in:\n - foo.nix\n - bar.nix\n" @@ -763,9 +826,9 @@ rec { ``` */ showOptionWithDefLocs = opt: '' - ${showOption opt.loc}, with values defined in: - ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} - ''; + ${showOption opt.loc}, with values defined in: + ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} + ''; unknownModule = ""; diff --git a/lib/strings-with-deps.nix b/lib/strings-with-deps.nix index e22e19def581..6322cad11995 100644 --- a/lib/strings-with-deps.nix +++ b/lib/strings-with-deps.nix @@ -125,30 +125,58 @@ rec { - Ordering the dependent phases of `system.userActivationScripts` For further examples see: [NixOS activation script](https://nixos.org/manual/nixos/stable/#sec-activation-script) - */ - textClosureList = predefined: arg: + textClosureList = + predefined: arg: let - f = done: todo: - if todo == [] then {result = []; inherit done;} + f = + done: todo: + if todo == [ ] then + { + result = [ ]; + inherit done; + } else - let entry = head todo; in + let + entry = head todo; + in if isAttrs entry then - let x = f done entry.deps; - y = f x.done (tail todo); - in { result = x.result ++ [entry.text] ++ y.result; - done = y.done; - } - else if done ? ${entry} then f done (tail todo) - else f (done // listToAttrs [{name = entry; value = 1;}]) ([predefined.${entry}] ++ tail todo); - in (f {} arg).result; - - textClosureMap = f: predefined: names: + let + x = f done entry.deps; + y = f x.done (tail todo); + in + { + result = x.result ++ [ entry.text ] ++ y.result; + done = y.done; + } + else if done ? ${entry} then + f done (tail todo) + else + f ( + done + // listToAttrs [ + { + name = entry; + value = 1; + } + ] + ) ([ predefined.${entry} ] ++ tail todo); + in + (f { } arg).result; + + textClosureMap = + f: predefined: names: concatStringsSep "\n" (map f (textClosureList predefined names)); - noDepEntry = text: {inherit text; deps = [];}; - fullDepEntry = text: deps: {inherit text deps;}; - packEntry = deps: {inherit deps; text="";}; + noDepEntry = text: { + inherit text; + deps = [ ]; + }; + fullDepEntry = text: deps: { inherit text deps; }; + packEntry = deps: { + inherit deps; + text = ""; + }; stringAfter = deps: text: { inherit text deps; }; diff --git a/lib/strings.nix b/lib/strings.nix index 7ee459f5b443..d281120cad7f 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -8,7 +8,7 @@ let inherit (lib.trivial) warnIf; -asciiTable = import ./ascii-table.nix; + asciiTable = import ./ascii-table.nix; in @@ -66,7 +66,6 @@ rec { /** Map a function over a list and concatenate the resulting strings. - # Inputs `f` @@ -98,7 +97,6 @@ rec { Like `concatMapStrings` except that the f functions also gets the position as a parameter. - # Inputs `f` @@ -129,7 +127,6 @@ rec { /** Place an element between each element of a list - # Inputs `separator` @@ -156,11 +153,16 @@ rec { ::: */ intersperse = - separator: - list: - if list == [] || length list == 1 - then list - else tail (lib.concatMap (x: [separator x]) list); + separator: list: + if list == [ ] || length list == 1 then + list + else + tail ( + lib.concatMap (x: [ + separator + x + ]) list + ); /** Concatenate a list of strings with a separator between each element @@ -197,7 +199,6 @@ rec { result with the specified separator interspersed between elements. - # Inputs `sep` @@ -227,15 +228,13 @@ rec { ::: */ concatMapStringsSep = - sep: - f: - list: concatStringsSep sep (map f list); + sep: f: list: + concatStringsSep sep (map f list); /** Same as `concatMapStringsSep`, but the mapping function additionally receives the position of its argument. - # Inputs `sep` @@ -265,9 +264,8 @@ rec { ::: */ concatImapStringsSep = - sep: - f: - list: concatStringsSep sep (lib.imap1 f list); + sep: f: list: + concatStringsSep sep (lib.imap1 f list); /** Like [`concatMapStringsSep`](#function-library-lib.strings.concatMapStringsSep) @@ -338,7 +336,6 @@ rec { Repeat a string `n` times, and concatenate the parts into a new string. - # Inputs `n` @@ -502,9 +499,7 @@ rec { ::: */ makeSearchPath = - subDir: - paths: - concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); + subDir: paths: concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); /** Construct a Unix-style search path by appending the given @@ -513,7 +508,6 @@ rec { If no output by the given name is found, fallback to `.out` and then to the default. - # Inputs `output` @@ -543,9 +537,8 @@ rec { ::: */ makeSearchPathOutput = - output: - subDir: - pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs); + output: subDir: pkgs: + makeSearchPath subDir (map (lib.getOutput output) pkgs); /** Construct a library search path (such as RPATH) containing the @@ -640,7 +633,6 @@ rec { /** Normalize path, removing extraneous /s - # Inputs `s` @@ -663,26 +655,22 @@ rec { ::: */ - normalizePath = s: - warnIf - (isPath s) + normalizePath = + s: + warnIf (isPath s) '' lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported. Path values are always normalised in Nix, so there's no need to call this function on them. This function also copies the path to the Nix store and returns the store path, the same as "''${path}" will, which may not be what you want. This behavior is deprecated and will throw an error in the future.'' ( - builtins.foldl' - (x: y: if y == "/" && hasSuffix "/" x then x else x+y) - "" - (stringToCharacters s) + builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x + y) "" (stringToCharacters s) ); /** Depending on the boolean `cond', return either the given string or the empty string. Useful to concatenate against a bigger string. - # Inputs `cond` @@ -710,14 +698,11 @@ rec { ::: */ - optionalString = - cond: - string: if cond then string else ""; + optionalString = cond: string: if cond then string else ""; /** Determine whether a string has given prefix. - # Inputs `pref` @@ -746,12 +731,10 @@ rec { ::: */ hasPrefix = - pref: - str: + pref: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath pref) + warnIf (isPath pref) '' 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. @@ -763,7 +746,6 @@ rec { /** Determine whether a string has given suffix. - # Inputs `suffix` @@ -792,30 +774,24 @@ rec { ::: */ hasSuffix = - suffix: - content: + suffix: content: let lenContent = stringLength content; lenSuffix = stringLength suffix; in # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath suffix) + warnIf (isPath suffix) '' lib.strings.hasSuffix: The first argument (${toString suffix}) 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.'' - ( - lenContent >= lenSuffix - && substring (lenContent - lenSuffix) lenContent content == suffix - ); + (lenContent >= lenSuffix && substring (lenContent - lenSuffix) lenContent content == suffix); /** Determine whether a string contains the given infix - # Inputs `infix` @@ -847,11 +823,11 @@ rec { ::: */ - hasInfix = infix: content: + hasInfix = + infix: content: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath infix) + warnIf (isPath infix) '' lib.strings.hasInfix: The first argument (${toString infix}) 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. @@ -868,7 +844,6 @@ rec { Also note that Nix treats strings as a list of bytes and thus doesn't handle unicode. - # Inputs `s` @@ -895,14 +870,12 @@ rec { ::: */ - stringToCharacters = s: - genList (p: substring p 1 s) (stringLength s); + stringToCharacters = s: genList (p: substring p 1 s) (stringLength s); /** Manipulate a string character by character and replace them by strings before concatenating the results. - # Inputs `f` @@ -932,14 +905,12 @@ rec { # Function to map over each individual character f: # Input string - s: concatStrings ( - map f (stringToCharacters s) - ); + s: + concatStrings (map f (stringToCharacters s)); /** Convert char to ascii value, must be in printable range - # Inputs `c` @@ -970,7 +941,6 @@ rec { Escape occurrence of the elements of `list` in `string` by prefixing it with a backslash. - # Inputs `list` @@ -1003,7 +973,6 @@ rec { converting to its ASCII value and prefixing it with \\x. Only works for printable ascii characters. - # Inputs `list` @@ -1029,7 +998,7 @@ rec { ::: */ - escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list); + escapeC = list: replaceStrings list (map (c: "\\x${toLower (lib.toHexString (charToInt c))}") list); /** Escape the `string` so it can be safely placed inside a URL @@ -1057,17 +1026,86 @@ rec { ::: */ - escapeURL = let - unreserved = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ]; - toEscape = builtins.removeAttrs asciiTable unreserved; - in - replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape); + escapeURL = + let + unreserved = [ + "A" + "B" + "C" + "D" + "E" + "F" + "G" + "H" + "I" + "J" + "K" + "L" + "M" + "N" + "O" + "P" + "Q" + "R" + "S" + "T" + "U" + "V" + "W" + "X" + "Y" + "Z" + "a" + "b" + "c" + "d" + "e" + "f" + "g" + "h" + "i" + "j" + "k" + "l" + "m" + "n" + "o" + "p" + "q" + "r" + "s" + "t" + "u" + "v" + "w" + "x" + "y" + "z" + "0" + "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "-" + "_" + "." + "~" + ]; + toEscape = builtins.removeAttrs asciiTable unreserved; + in + replaceStrings (builtins.attrNames toEscape) ( + lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape + ); /** Quote `string` to be used safely within the Bourne shell if it has any special characters. - # Inputs `string` @@ -1090,13 +1128,15 @@ rec { ::: */ - escapeShellArg = arg: + escapeShellArg = + arg: let string = toString arg; in - if match "[[:alnum:],._+:@%/-]+" string == null - then "'${replaceStrings ["'"] ["'\\''"] string}'" - else string; + if match "[[:alnum:],._+:@%/-]+" string == null then + "'${replaceStrings [ "'" ] [ "'\\''" ] string}'" + else + string; /** Quote all arguments that have special characters to be safely passed to the @@ -1129,7 +1169,6 @@ rec { /** Test whether the given `name` is a valid POSIX shell variable name. - # Inputs `name` @@ -1167,7 +1206,6 @@ rec { Strings are translated into POSIX sh-compatible code; lists and attribute sets assume a shell that understands Bash syntax (e.g. Bash or ZSH). - # Inputs `name` @@ -1195,25 +1233,23 @@ rec { ::: */ - toShellVar = name: value: + toShellVar = + name: value: lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( - if isAttrs value && ! isStringLike value then - "declare -A ${name}=(${ - concatStringsSep " " (lib.mapAttrsToList (n: v: - "[${escapeShellArg n}]=${escapeShellArg v}" - ) value) - })" - else if isList value then - "declare -a ${name}=(${escapeShellArgs value})" - else - "${name}=${escapeShellArg value}" + if isAttrs value && !isStringLike value then + "declare -A ${name}=(${ + concatStringsSep " " (lib.mapAttrsToList (n: v: "[${escapeShellArg n}]=${escapeShellArg v}") value) + })" + else if isList value then + "declare -a ${name}=(${escapeShellArgs value})" + else + "${name}=${escapeShellArg value}" ); /** Translate an attribute set `vars` into corresponding shell variable declarations using `toShellVar`. - # Inputs `vars` @@ -1270,7 +1306,7 @@ rec { ::: */ - escapeNixString = s: escape ["$"] (toJSON s); + escapeNixString = s: escape [ "$" ] (toJSON s); /** Turn a string `s` into an exact regular expression @@ -1302,7 +1338,6 @@ rec { /** Quotes a string `s` if it can't be used as an identifier directly. - # Inputs `s` @@ -1327,10 +1362,10 @@ rec { ::: */ - escapeNixIdentifier = s: + escapeNixIdentifier = + s: # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91 - if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null - then s else escapeNixString s; + if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null then s else escapeNixString s; /** Escapes a string `s` such that it is safe to include verbatim in an XML @@ -1358,9 +1393,10 @@ rec { ::: */ - escapeXML = builtins.replaceStrings - ["\"" "'" "<" ">" "&"] - [""" "'" "<" ">" "&"]; + escapeXML = + builtins.replaceStrings + [ "\"" "'" "<" ">" "&" ] + [ """ "'" "<" ">" "&" ]; # warning added 12-12-2022 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings; @@ -1404,7 +1440,6 @@ rec { `s` : The string to convert to upper-case. - # Type ``` @@ -1449,7 +1484,8 @@ rec { ::: */ - toSentenceCase = str: + toSentenceCase = + str: lib.throwIfNot (isString str) "toSentenceCase does only accepts string values, but got ${typeOf str}" ( @@ -1473,7 +1509,6 @@ rec { attribute, the derivation will properly populate the inputDrvs and inputSrcs. - # Inputs `src` @@ -1544,12 +1579,14 @@ rec { ::: */ - splitString = sep: s: + splitString = + sep: s: let - splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s)); + splits = builtins.filter builtins.isString ( + builtins.split (escapeRegex (toString sep)) (toString s) + ); in - map (addContextFrom s) splits; - + map (addContextFrom s) splits; /** Return a string without the specified prefix, if the prefix matches. @@ -1582,30 +1619,29 @@ rec { ::: */ removePrefix = - prefix: - str: + prefix: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath prefix) + warnIf (isPath prefix) '' lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function never removes any prefix 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.'' - (let - preLen = stringLength prefix; - in - if substring 0 preLen str == prefix then - # -1 will take the string until the end - substring preLen (-1) str - else - str); + ( + let + preLen = stringLength prefix; + in + if substring 0 preLen str == prefix then + # -1 will take the string until the end + substring preLen (-1) str + else + str + ); /** Return a string without the specified suffix, if the suffix matches. - # Inputs `suffix` @@ -1634,30 +1670,29 @@ rec { ::: */ removeSuffix = - suffix: - str: + suffix: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath suffix) + warnIf (isPath suffix) '' lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function never removes any suffix 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.'' - (let - sufLen = stringLength suffix; - sLen = stringLength str; - in - if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then - substring 0 (sLen - sufLen) str - else - str); + ( + let + sufLen = stringLength suffix; + sLen = stringLength str; + in + if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then + substring 0 (sLen - sufLen) str + else + str + ); /** Return true if string `v1` denotes a version older than `v2`. - # Inputs `v1` @@ -1690,7 +1725,6 @@ rec { /** Return true if string v1 denotes a version equal to or newer than v2. - # Inputs `v1` @@ -1738,7 +1772,6 @@ rec { getName :: String | Derivation -> String ``` - # Examples :::{.example} ## `lib.strings.getName` usage example @@ -1752,19 +1785,17 @@ rec { ::: */ - getName = let - parse = drv: (parseDrvName drv).name; - in x: - if isString x - then parse x - else x.pname or (parse x.name); + getName = + let + parse = drv: (parseDrvName drv).name; + in + x: if isString x then parse x else x.pname or (parse x.name); /** This function takes an argument `x` that's either a derivation or a derivation's "name" attribute and extracts the version part from that argument. - # Inputs `x` @@ -1789,19 +1820,17 @@ rec { ::: */ - getVersion = let - parse = drv: (parseDrvName drv).version; - in x: - if isString x - then parse x - else x.version or (parse x.name); + getVersion = + let + parse = drv: (parseDrvName drv).version; + in + x: if isString x then parse x else x.version or (parse x.name); /** Extract name and version from a URL as shown in the examples. Separator `sep` is used to determine the end of the extension. - # Inputs `url` @@ -1829,12 +1858,15 @@ rec { ::: */ - nameFromURL = url: sep: + nameFromURL = + url: sep: let components = splitString "/" url; filename = lib.last components; name = head (splitString sep filename); - in assert name != filename; name; + in + assert name != filename; + name; /** Create a `"-D:="` string that can be passed to typical @@ -1871,9 +1903,17 @@ rec { ::: */ - cmakeOptionType = let - types = [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]; - in type: feature: value: + cmakeOptionType = + let + types = [ + "BOOL" + "FILEPATH" + "PATH" + "STRING" + "INTERNAL" + ]; + in + type: feature: value: assert (elem (toUpper type) types); assert (isString feature); assert (isString value); @@ -1883,7 +1923,6 @@ rec { Create a -D={TRUE,FALSE} string that can be passed to typical CMake invocations. - # Inputs `condition` @@ -1909,7 +1948,8 @@ rec { ::: */ - cmakeBool = condition: flag: + cmakeBool = + condition: flag: assert (lib.isString condition); assert (lib.isBool flag); cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag)); @@ -1919,7 +1959,6 @@ rec { CMake invocations. This is the most typical usage, so it deserves a special case. - # Inputs `feature` @@ -1928,7 +1967,6 @@ rec { `value` : The desired value - # Type ``` @@ -1946,7 +1984,8 @@ rec { ::: */ - cmakeFeature = feature: value: + cmakeFeature = + feature: value: assert (lib.isString feature); assert (lib.isString value); cmakeOptionType "string" feature value; @@ -1955,7 +1994,6 @@ rec { Create a -D= string that can be passed to typical Meson invocations. - # Inputs `feature` @@ -1981,7 +2019,8 @@ rec { ::: */ - mesonOption = feature: value: + mesonOption = + feature: value: assert (lib.isString feature); assert (lib.isString value); "-D${feature}=${value}"; @@ -1990,7 +2029,6 @@ rec { Create a -D={true,false} string that can be passed to typical Meson invocations. - # Inputs `condition` @@ -2018,7 +2056,8 @@ rec { ::: */ - mesonBool = condition: flag: + mesonBool = + condition: flag: assert (lib.isString condition); assert (lib.isBool flag); mesonOption condition (lib.boolToString flag); @@ -2027,7 +2066,6 @@ rec { Create a -D={enabled,disabled} string that can be passed to typical Meson invocations. - # Inputs `feature` @@ -2055,7 +2093,8 @@ rec { ::: */ - mesonEnable = feature: flag: + mesonEnable = + feature: flag: assert (lib.isString feature); assert (lib.isBool flag); mesonOption feature (if flag then "enabled" else "disabled"); @@ -2064,7 +2103,6 @@ rec { Create an --{enable,disable}- string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2092,7 +2130,8 @@ rec { ::: */ - enableFeature = flag: feature: + enableFeature = + flag: feature: assert lib.isBool flag; assert lib.isString feature; # e.g. passing openssl instead of "openssl" "--${if flag then "enable" else "disable"}-${feature}"; @@ -2101,7 +2140,6 @@ rec { Create an --{enable-=,disable-} string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2132,14 +2170,14 @@ rec { ::: */ - enableFeatureAs = flag: feature: value: + enableFeatureAs = + flag: feature: value: enableFeature flag feature + optionalString flag "=${value}"; /** Create an --{with,without}- string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2148,7 +2186,6 @@ rec { `feature` : 2\. Function argument - # Type ``` @@ -2168,7 +2205,8 @@ rec { ::: */ - withFeature = flag: feature: + withFeature = + flag: feature: assert isString feature; # e.g. passing openssl instead of "openssl" "--${if flag then "with" else "without"}-${feature}"; @@ -2176,7 +2214,6 @@ rec { Create an --{with-=,without-} string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2194,7 +2231,6 @@ rec { withFeatureAs :: bool -> string -> string -> string ``` - # Examples :::{.example} ## `lib.strings.withFeatureAs` usage example @@ -2208,7 +2244,8 @@ rec { ::: */ - withFeatureAs = flag: feature: value: + withFeatureAs = + flag: feature: value: withFeature flag feature + optionalString flag "=${value}"; /** @@ -2218,7 +2255,6 @@ rec { This function will fail if the input string is longer than the requested length. - # Inputs `width` @@ -2247,21 +2283,19 @@ rec { ::: */ - fixedWidthString = width: filler: str: + fixedWidthString = + width: filler: str: let strw = lib.stringLength str; reqWidth = width - (lib.stringLength filler); in - assert lib.assertMsg (strw <= width) - "fixedWidthString: requested string length (${ - toString width}) must not be shorter than actual length (${ - toString strw})"; - if strw == width then str else filler + fixedWidthString reqWidth filler str; + assert lib.assertMsg (strw <= width) + "fixedWidthString: requested string length (${toString width}) must not be shorter than actual length (${toString strw})"; + if strw == width then str else filler + fixedWidthString reqWidth filler str; /** Format a number adding leading zeroes up to fixed width. - # Inputs `width` @@ -2293,13 +2327,11 @@ rec { Convert a float to a string, but emit a warning when precision is lost during the conversion - # Inputs `float` : 1\. Function argument - # Type ``` @@ -2320,11 +2352,13 @@ rec { ::: */ - floatToString = float: let - result = toString float; - precise = float == fromJSON result; - in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" - result; + floatToString = + float: + let + result = toString float; + precise = float == fromJSON result; + in + lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" result; /** Check whether a value `val` can be coerced to a string. @@ -2345,9 +2379,10 @@ rec { isCoercibleToString :: a -> bool ``` */ - isCoercibleToString = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) - "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." - isConvertibleWithToString; + isCoercibleToString = + lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) + "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." + isConvertibleWithToString; /** Check whether a list or other value `x` can be passed to toString. @@ -2366,12 +2401,16 @@ rec { isConvertibleWithToString :: a -> bool ``` */ - isConvertibleWithToString = let - types = [ "null" "int" "float" "bool" ]; - in x: - isStringLike x || - elem (typeOf x) types || - (isList x && lib.all isConvertibleWithToString x); + isConvertibleWithToString = + let + types = [ + "null" + "int" + "float" + "bool" + ]; + in + x: isStringLike x || elem (typeOf x) types || (isList x && lib.all isConvertibleWithToString x); /** Check whether a value can be coerced to a string. @@ -2380,7 +2419,6 @@ rec { String-like values can be used without explicit conversion in string interpolations and in most functions that expect a string. - # Inputs `x` @@ -2392,16 +2430,11 @@ rec { isStringLike :: a -> bool ``` */ - isStringLike = x: - isString x || - isPath x || - x ? outPath || - x ? __toString; + isStringLike = x: isString x || isPath x || x ? outPath || x ? __toString; /** Check whether a value `x` is a store path. - # Inputs `x` @@ -2430,17 +2463,22 @@ rec { ::: */ - isStorePath = x: + isStorePath = + x: if isStringLike x then - let str = toString x; in + let + str = toString x; + in substring 0 1 str == "/" - && (dirOf str == storeDir + && ( + dirOf str == storeDir # Match content‐addressed derivations, which _currently_ do not have a # store directory prefix. # This is a workaround for https://github.com/NixOS/nix/issues/12361 # which was needed during the experimental phase of ca-derivations and # should be removed once the issue has been resolved. - || builtins.match "/[0-9a-z]{52}" str != null) + || builtins.match "/[0-9a-z]{52}" str != null + ) else false; @@ -2494,7 +2532,7 @@ rec { strippedInput = matchStripInput str; # RegEx: Match a leading '0' then one or more digits. - isLeadingZero = matchLeadingZero (head strippedInput) == []; + isLeadingZero = matchLeadingZero (head strippedInput) == [ ]; # Attempt to parse input parsedInput = fromJSON (head strippedInput); @@ -2502,18 +2540,18 @@ rec { generalError = "toInt: Could not convert ${escapeNixString str} to int."; in - # Error on presence of non digit characters. - if strippedInput == null - then throw generalError - # Error on presence of leading zero/octal ambiguity. - else if isLeadingZero - then throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer." - # Error if parse function fails. - else if !isInt parsedInput - then throw generalError - # Return result. - else parsedInput; - + # Error on presence of non digit characters. + if strippedInput == null then + throw generalError + # Error on presence of leading zero/octal ambiguity. + else if isLeadingZero then + throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer." + # Error if parse function fails. + else if !isInt parsedInput then + throw generalError + # Return result. + else + parsedInput; /** Parse a string as a base 10 int. This supports parsing of zero-padded integers. @@ -2565,7 +2603,7 @@ rec { strippedInput = matchStripInput str; # RegEx: Match at least one '0'. - isZero = matchZero (head strippedInput) == []; + isZero = matchZero (head strippedInput) == [ ]; # Attempt to parse input parsedInput = fromJSON (head strippedInput); @@ -2573,17 +2611,18 @@ rec { generalError = "toIntBase10: Could not convert ${escapeNixString str} to int."; in - # Error on presence of non digit characters. - if strippedInput == null - then throw generalError - # In the special case zero-padded zero (00000), return early. - else if isZero - then 0 - # Error if parse function fails. - else if !isInt parsedInput - then throw generalError - # Return result. - else parsedInput; + # Error on presence of non digit characters. + if strippedInput == null then + throw generalError + # In the special case zero-padded zero (00000), return early. + else if isZero then + 0 + # Error if parse function fails. + else if !isInt parsedInput then + throw generalError + # Return result. + else + parsedInput; /** Read a list of paths from `file`, relative to the `rootPath`. @@ -2628,20 +2667,20 @@ rec { ::: */ - readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead." - (rootPath: file: - let - lines = lib.splitString "\n" (readFile file); - removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); - relativePaths = removeComments lines; - absolutePaths = map (path: rootPath + "/${path}") relativePaths; - in - absolutePaths); + readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead." ( + rootPath: file: + let + lines = lib.splitString "\n" (readFile file); + removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); + relativePaths = removeComments lines; + absolutePaths = map (path: rootPath + "/${path}") relativePaths; + in + absolutePaths + ); /** Read the contents of a file removing the trailing \n - # Inputs `file` @@ -2668,7 +2707,6 @@ rec { */ fileContents = file: removeSuffix "\n" (readFile file); - /** Creates a valid derivation name from a potentially invalid one. @@ -2699,29 +2737,31 @@ rec { ::: */ sanitizeDerivationName = - let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*"; - in - string: - # First detect the common case of already valid strings, to speed those up - if stringLength string <= 207 && okRegex string != null - then unsafeDiscardStringContext string - else lib.pipe string [ - # Get rid of string context. This is safe under the assumption that the - # resulting string is only used as a derivation name - unsafeDiscardStringContext - # Strip all leading "." - (x: elemAt (match "\\.*(.*)" x) 0) - # Split out all invalid characters - # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 - # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 - (split "[^[:alnum:]+._?=-]+") - # Replace invalid character ranges with a "-" - (concatMapStrings (s: if lib.isList s then "-" else s)) - # Limit to 211 characters (minus 4 chars for ".drv") - (x: substring (lib.max (stringLength x - 207) 0) (-1) x) - # If the result is empty, replace it with "unknown" - (x: if stringLength x == 0 then "unknown" else x) - ]; + let + okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*"; + in + string: + # First detect the common case of already valid strings, to speed those up + if stringLength string <= 207 && okRegex string != null then + unsafeDiscardStringContext string + else + lib.pipe string [ + # Get rid of string context. This is safe under the assumption that the + # resulting string is only used as a derivation name + unsafeDiscardStringContext + # Strip all leading "." + (x: elemAt (match "\\.*(.*)" x) 0) + # Split out all invalid characters + # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 + # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 + (split "[^[:alnum:]+._?=-]+") + # Replace invalid character ranges with a "-" + (concatMapStrings (s: if lib.isList s then "-" else s)) + # Limit to 211 characters (minus 4 chars for ".drv") + (x: substring (lib.max (stringLength x - 207) 0) (-1) x) + # If the result is empty, replace it with "unknown" + (x: if stringLength x == 0 then "unknown" else x) + ]; /** Computes the Levenshtein distance between two strings `a` and `b`. @@ -2729,7 +2769,6 @@ rec { Complexity O(n*m) where n and m are the lengths of the strings. Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742 - # Inputs `a` @@ -2759,29 +2798,29 @@ rec { ::: */ - levenshtein = a: b: let - # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1) - arr = lib.genList (i: - lib.genList (j: - dist i j - ) (stringLength b + 1) - ) (stringLength a + 1); - d = x: y: lib.elemAt (lib.elemAt arr x) y; - dist = i: j: - let c = if substring (i - 1) 1 a == substring (j - 1) 1 b - then 0 else 1; - in - if j == 0 then i - else if i == 0 then j - else lib.min - ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) - ( d (i - 1) (j - 1) + c ); - in d (stringLength a) (stringLength b); + levenshtein = + a: b: + let + # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1) + arr = lib.genList (i: lib.genList (j: dist i j) (stringLength b + 1)) (stringLength a + 1); + d = x: y: lib.elemAt (lib.elemAt arr x) y; + dist = + i: j: + let + c = if substring (i - 1) 1 a == substring (j - 1) 1 b then 0 else 1; + in + if j == 0 then + i + else if i == 0 then + j + else + lib.min (lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) (d (i - 1) (j - 1) + c); + in + d (stringLength a) (stringLength b); /** Returns the length of the prefix that appears in both strings `a` and `b`. - # Inputs `a` @@ -2796,16 +2835,24 @@ rec { commonPrefixLength :: string -> string -> int ``` */ - commonPrefixLength = a: b: + commonPrefixLength = + a: b: let m = lib.min (stringLength a) (stringLength b); - go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i; - in go 0; + go = + i: + if i >= m then + m + else if substring i 1 a == substring i 1 b then + go (i + 1) + else + i; + in + go 0; /** Returns the length of the suffix common to both strings `a` and `b`. - # Inputs `a` @@ -2820,11 +2867,20 @@ rec { commonSuffixLength :: string -> string -> int ``` */ - commonSuffixLength = a: b: + commonSuffixLength = + a: b: let m = lib.min (stringLength a) (stringLength b); - go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i; - in go 0; + go = + i: + if i >= m then + m + else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then + go (i + 1) + else + i; + in + go 0; /** Returns whether the levenshtein distance between two strings `a` and `b` is at most some value `k`. @@ -2867,64 +2923,81 @@ rec { ::: */ - levenshteinAtMost = let - infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1; - - # This function takes two strings stripped by their common pre and suffix, - # and returns whether they differ by at most two by Levenshtein distance. - # Because of this stripping, if they do indeed differ by at most two edits, - # we know that those edits were (if at all) done at the start or the end, - # while the middle has to have stayed the same. This fact is used in the - # implementation. - infixDifferAtMost2 = x: y: - let - xlen = stringLength x; - ylen = stringLength y; - # This function is only called with |x| >= |y| and |x| - |y| <= 2, so - # diff is one of 0, 1 or 2 - diff = xlen - ylen; - - # Infix of x and y, stripped by the left and right most character - xinfix = substring 1 (xlen - 2) x; - yinfix = substring 1 (ylen - 2) y; - - # x and y but a character deleted at the left or right - xdelr = substring 0 (xlen - 1) x; - xdell = substring 1 (xlen - 1) x; - ydelr = substring 0 (ylen - 1) y; - ydell = substring 1 (ylen - 1) y; - in + levenshteinAtMost = + let + infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1; + + # This function takes two strings stripped by their common pre and suffix, + # and returns whether they differ by at most two by Levenshtein distance. + # Because of this stripping, if they do indeed differ by at most two edits, + # we know that those edits were (if at all) done at the start or the end, + # while the middle has to have stayed the same. This fact is used in the + # implementation. + infixDifferAtMost2 = + x: y: + let + xlen = stringLength x; + ylen = stringLength y; + # This function is only called with |x| >= |y| and |x| - |y| <= 2, so + # diff is one of 0, 1 or 2 + diff = xlen - ylen; + + # Infix of x and y, stripped by the left and right most character + xinfix = substring 1 (xlen - 2) x; + yinfix = substring 1 (ylen - 2) y; + + # x and y but a character deleted at the left or right + xdelr = substring 0 (xlen - 1) x; + xdell = substring 1 (xlen - 1) x; + ydelr = substring 0 (ylen - 1) y; + ydell = substring 1 (ylen - 1) y; + in # A length difference of 2 can only be gotten with 2 delete edits, # which have to have happened at the start and end of x # Example: "abcdef" -> "bcde" - if diff == 2 then xinfix == y + if diff == 2 then + xinfix == y # A length difference of 1 can only be gotten with a deletion on the # right and a replacement on the left or vice versa. # Example: "abcdef" -> "bcdez" or "zbcde" - else if diff == 1 then xinfix == ydelr || xinfix == ydell + else if diff == 1 then + xinfix == ydelr || xinfix == ydell # No length difference can either happen through replacements on both # sides, or a deletion on the left and an insertion on the right or # vice versa # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde" - else xinfix == yinfix || xdelr == ydell || xdell == ydelr; + else + xinfix == yinfix || xdelr == ydell || xdell == ydelr; - in k: if k <= 0 then a: b: a == b else - let f = a: b: - let - alen = stringLength a; - blen = stringLength b; - prelen = commonPrefixLength a b; - suflen = commonSuffixLength a b; - presuflen = prelen + suflen; - ainfix = substring prelen (alen - presuflen) a; - binfix = substring prelen (blen - presuflen) b; - in - # Make a be the bigger string - if alen < blen then f b a - # If a has over k more characters than b, even with k deletes on a, b can't be reached - else if alen - blen > k then false - else if k == 1 then infixDifferAtMost1 ainfix binfix - else if k == 2 then infixDifferAtMost2 ainfix binfix - else levenshtein ainfix binfix <= k; - in f; + in + k: + if k <= 0 then + a: b: a == b + else + let + f = + a: b: + let + alen = stringLength a; + blen = stringLength b; + prelen = commonPrefixLength a b; + suflen = commonSuffixLength a b; + presuflen = prelen + suflen; + ainfix = substring prelen (alen - presuflen) a; + binfix = substring prelen (blen - presuflen) b; + in + # Make a be the bigger string + if alen < blen then + f b a + # If a has over k more characters than b, even with k deletes on a, b can't be reached + else if alen - blen > k then + false + else if k == 1 then + infixDifferAtMost1 ainfix binfix + else if k == 2 then + infixDifferAtMost2 ainfix binfix + else + levenshtein ainfix binfix <= k; + in + f; } diff --git a/lib/systems/architectures.nix b/lib/systems/architectures.nix index a448398449d3..e4f67e3d8e9f 100644 --- a/lib/systems/architectures.nix +++ b/lib/systems/architectures.nix @@ -5,94 +5,395 @@ rec { 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" ]; + 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" ]; - westmere = [ "sse3" "ssse3" "sse4_1" "sse4_2" ]; - silvermont = [ "sse3" "ssse3" "sse4_1" "sse4_2" ]; - sandybridge = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" ]; - ivybridge = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" ]; - haswell = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" "avx2" "fma" ]; - broadwell = [ "sse3" "ssse3" "sse4_1" "sse4_2" "avx" "avx2" "fma" ]; - skylake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; - skylake-avx512 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - cannonlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - icelake-client = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - icelake-server = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - cascadelake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - cooperlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - tigerlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - alderlake = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; - sapphirerapids = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - emeraldrapids = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "avx512" "fma" ]; - sierraforest = [ "sse3" "ssse3" "sse4_1" "sse4_2" "aes" "avx" "avx2" "fma" ]; + nehalem = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + ]; + westmere = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + ]; + silvermont = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + ]; + sandybridge = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "avx" + ]; + ivybridge = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "avx" + ]; + haswell = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "avx" + "avx2" + "fma" + ]; + broadwell = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "avx" + "avx2" + "fma" + ]; + skylake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "fma" + ]; + skylake-avx512 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + cannonlake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + icelake-client = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + icelake-server = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + cascadelake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + cooperlake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + tigerlake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + alderlake = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "fma" + ]; + sapphirerapids = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + emeraldrapids = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + sierraforest = [ + "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" ]; - bdver1 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; - bdver2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; - bdver3 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "fma" "fma4" ]; - bdver4 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" "fma4" ]; - znver1 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; - znver2 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; - znver3 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "fma" ]; - znver4 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "avx512" "fma" ]; - znver5 = [ "sse3" "ssse3" "sse4_1" "sse4_2" "sse4a" "aes" "avx" "avx2" "avx512" "fma" ]; + btver1 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + ]; + btver2 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "aes" + "avx" + ]; + bdver1 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "fma" + "fma4" + ]; + bdver2 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "fma" + "fma4" + ]; + bdver3 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "fma" + "fma4" + ]; + bdver4 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "fma" + "fma4" + ]; + znver1 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "fma" + ]; + znver2 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "fma" + ]; + znver3 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "fma" + ]; + znver4 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; + znver5 = [ + "sse3" + "ssse3" + "sse4_1" + "sse4_2" + "sse4a" + "aes" + "avx" + "avx2" + "avx512" + "fma" + ]; # other - armv5te = [ ]; - armv6 = [ ]; - armv7-a = [ ]; - armv8-a = [ ]; - mips32 = [ ]; - loongson2f = [ ]; + armv5te = [ ]; + armv6 = [ ]; + armv7-a = [ ]; + armv8-a = [ ]; + mips32 = [ ]; + loongson2f = [ ]; }; # 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" ]; + 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 - nehalem = [ "x86-64-v2" ] ++ inferiors.x86-64-v2; - westmere = [ "nehalem" ] ++ inferiors.nehalem; - sandybridge = [ "westmere" ] ++ inferiors.westmere; - ivybridge = [ "sandybridge" ] ++ inferiors.sandybridge; + 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; + 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; + 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 = [ "cannonlake" ] ++ inferiors.cannonlake; - cooperlake = [ "cascadelake" ] ++ inferiors.cascadelake; - tigerlake = [ "icelake-server" ] ++ inferiors.icelake-server; - sapphirerapids = [ "tigerlake" ] ++ inferiors.tigerlake; - emeraldrapids = [ "sapphirerapids" ] ++ inferiors.sapphirerapids; + cascadelake = [ "cannonlake" ] ++ inferiors.cannonlake; + cooperlake = [ "cascadelake" ] ++ inferiors.cascadelake; + tigerlake = [ "icelake-server" ] ++ inferiors.icelake-server; + sapphirerapids = [ "tigerlake" ] ++ inferiors.tigerlake; + emeraldrapids = [ "sapphirerapids" ] ++ inferiors.sapphirerapids; # CX16 does not exist on alderlake, while it does on nearly all other intel CPUs - alderlake = [ ]; - sierraforest = [ "alderlake" ] ++ inferiors.alderlake; + alderlake = [ ]; + sierraforest = [ "alderlake" ] ++ inferiors.alderlake; # x86_64 AMD # TODO: fill this (need testing) - btver1 = [ ]; - btver2 = [ ]; - bdver1 = [ ]; - bdver2 = [ ]; - bdver3 = [ ]; - bdver4 = [ ]; + btver1 = [ ]; + btver2 = [ ]; + bdver1 = [ ]; + bdver2 = [ ]; + bdver3 = [ ]; + bdver4 = [ ]; # Regarding `skylake` as inferior of `znver1`, there are reports of # successful usage by Gentoo users and Phoronix benchmarking of different # `-march` targets. @@ -112,34 +413,43 @@ 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; # 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); - znver5 = [ "znver4" ] ++ inferiors.znver4; + 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 + ); + znver5 = [ "znver4" ] ++ inferiors.znver4; # other - armv5te = [ ]; - armv6 = [ ]; - armv7-a = [ ]; - armv8-a = [ ]; - mips32 = [ ]; - loongson2f = [ ]; + armv5te = [ ]; + armv6 = [ ]; + armv7-a = [ ]; + armv8-a = [ ]; + mips32 = [ ]; + loongson2f = [ ]; }; - predicates = let - featureSupport = feature: x: builtins.elem feature features.${x} or []; - in { - sse3Support = featureSupport "sse3"; - ssse3Support = featureSupport "ssse3"; - sse4_1Support = featureSupport "sse4_1"; - sse4_2Support = featureSupport "sse4_2"; - sse4_aSupport = featureSupport "sse4a"; - avxSupport = featureSupport "avx"; - avx2Support = featureSupport "avx2"; - avx512Support = featureSupport "avx512"; - aesSupport = featureSupport "aes"; - fmaSupport = featureSupport "fma"; - fma4Support = featureSupport "fma4"; - }; + predicates = + let + featureSupport = feature: x: builtins.elem feature features.${x} or [ ]; + in + { + sse3Support = featureSupport "sse3"; + ssse3Support = featureSupport "ssse3"; + sse4_1Support = featureSupport "sse4_1"; + sse4_2Support = featureSupport "sse4_2"; + sse4_aSupport = featureSupport "sse4a"; + avxSupport = featureSupport "avx"; + avx2Support = featureSupport "avx2"; + avx512Support = featureSupport "avx512"; + aesSupport = featureSupport "aes"; + fmaSupport = featureSupport "fma"; + fma4Support = featureSupport "fma4"; + }; } diff --git a/lib/systems/default.nix b/lib/systems/default.nix index ec0106a13dac..67bd9826465c 100644 --- a/lib/systems/default.nix +++ b/lib/systems/default.nix @@ -42,8 +42,10 @@ let both arguments have been `elaborate`-d. */ equals = - let removeFunctions = a: filterAttrs (_: v: !isFunction v) a; - in a: b: removeFunctions a == removeFunctions b; + let + removeFunctions = a: filterAttrs (_: v: !isFunction v) a; + in + a: b: removeFunctions a == removeFunctions b; /** List of all Nix system doubles the nixpkgs flake will expose the package set @@ -57,8 +59,8 @@ let # Turn localSystem or crossSystem, which could be system-string or attrset, into # attrset. - systemToAttrs = systemOrArgs: - if isAttrs systemOrArgs then systemOrArgs else { system = systemOrArgs; }; + systemToAttrs = + systemOrArgs: if isAttrs systemOrArgs then systemOrArgs else { system = systemOrArgs; }; # Elaborate a `localSystem` or `crossSystem` so that it contains everything # necessary. @@ -66,370 +68,478 @@ let # `parsed` is inferred from args, both because there are two options with one # clearly preferred, and to prevent cycles. A simpler fixed point where the RHS # always just used `final.*` would fail on both counts. - elaborate = systemOrArgs: let - allArgs = systemToAttrs systemOrArgs; - - # Those two will always be derived from "config", if given, so they should NOT - # be overridden further down with "// args". - args = builtins.removeAttrs allArgs [ "parsed" "system" ]; - - # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. - rust = args.rust or args.rustc or {}; - - final = { - # Prefer to parse `config` as it is strictly more informative. - parsed = parse.mkSystemFromString (args.config or allArgs.system); - # This can be losslessly-extracted from `parsed` iff parsing succeeds. - system = parse.doubleFromSystem final.parsed; - # TODO: This currently can't be losslessly-extracted from `parsed`, for example - # because of -mingw32. - config = parse.tripleFromSystem final.parsed; - # Determine whether we can execute binaries built for the provided platform. - canExecute = platform: - final.isAndroid == platform.isAndroid && - parse.isCompatible final.parsed.cpu platform.parsed.cpu - && final.parsed.kernel == platform.parsed.kernel; - isCompatible = _: throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details"; - # Derived meta-data - useLLVM = final.isFreeBSD || final.isOpenBSD; - - libc = - /**/ if final.isDarwin then "libSystem" - else if final.isMinGW then "msvcrt" - else if final.isWasi then "wasilibc" - else if final.isWasm && !final.isWasi then null - else if final.isRedox then "relibc" - else if final.isMusl then "musl" - else if final.isUClibc then "uclibc" - else if final.isAndroid then "bionic" - else if final.isLLVMLibc then "llvm" - else if final.isLinux /* default */ then "glibc" - else if final.isFreeBSD then "fblibc" - else if final.isOpenBSD then "oblibc" - else if final.isNetBSD then "nblibc" - else if final.isAvr then "avrlibc" - else if final.isGhcjs then null - else if final.isNone then "newlib" - # TODO(@Ericson2314) think more about other operating systems - else "native/impure"; - # Choose what linker we wish to use by default. Someday we might also - # choose the C compiler, runtime library, C++ standard library, etc. in - # this way, nice and orthogonally, and deprecate `useLLVM`. But due to - # the monolithic GCC build we cannot actually make those choices - # independently, so we are just doing `linker` and keeping `useLLVM` for - # now. - linker = - /**/ if final.useLLVM or false then "lld" - else if final.isDarwin then "cctools" - # "bfd" and "gold" both come from GNU binutils. The existence of Gold - # is why we use the more obscure "bfd" and not "binutils" for this - # choice. - else "bfd"; - # The standard lib directory name that non-nixpkgs binaries distributed - # for this platform normally assume. - libDir = if final.isLinux then - if final.isx86_64 || final.isMips64 || final.isPower64 - then "lib64" - else "lib" - else null; - extensions = optionalAttrs final.hasSharedLibraries { - sharedLibrary = - 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 final.extensions.staticLibrary - else final.extensions.sharedLibrary; - executable = - /**/ if final.isWindows then ".exe" - else ""; - }; - # Misc boolean options - useAndroidPrebuilt = false; - useiOSPrebuilt = false; - - # Output from uname - uname = { - # uname -s - system = { - linux = "Linux"; - windows = "Windows"; - darwin = "Darwin"; - netbsd = "NetBSD"; - freebsd = "FreeBSD"; - openbsd = "OpenBSD"; - wasi = "Wasi"; - redox = "Redox"; - genode = "Genode"; - }.${final.parsed.kernel.name} or null; - - # uname -m - processor = - if final.isPower64 - then "ppc64${optionalString final.isLittleEndian "le"}" - else if final.isPower - then "ppc${optionalString final.isLittleEndian "le"}" - else if final.isMips64 - then "mips64" # endianness is *not* included on mips64 - else final.parsed.cpu.name; - - # 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 || isWindows # Windows - || isWasm # WASM - ) && !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.isWasi || final.isRedox; - - # Just a guess, based on `system` - inherit - ({ - linux-kernel = args.linux-kernel or {}; - gcc = args.gcc or {}; - } // platforms.select final) - linux-kernel gcc; - - # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs. - rustc = args.rustc or {}; - - linuxArch = - if final.isAarch32 then "arm" - else if final.isAarch64 then "arm64" - else if final.isx86_32 then "i386" - else if final.isx86_64 then "x86_64" - # linux kernel does not distinguish microblaze/microblazeel - else if final.isMicroBlaze then "microblaze" - else if final.isMips32 then "mips" - else if final.isMips64 then "mips" # linux kernel does not distinguish mips32/mips64 - else if final.isPower then "powerpc" - else if final.isRiscV then "riscv" - else if final.isS390 then "s390" - else if final.isLoongArch64 then "loongarch" - else final.parsed.cpu.name; - - # https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106 - ubootArch = - if final.isx86_32 then "x86" # not i386 - else if final.isMips64 then "mips64" # uboot *does* distinguish between mips32/mips64 - else final.linuxArch; # other cases appear to agree with linuxArch - - qemuArch = - if final.isAarch32 then "arm" - else if final.isS390 && !final.isS390x then null - else if final.isx86_64 then "x86_64" - else if final.isx86 then "i386" - else if final.isMips64n32 then "mipsn32${optionalString final.isLittleEndian "el"}" - else if final.isMips64 then "mips64${optionalString final.isLittleEndian "el"}" - else final.uname.processor; - - # Name used by UEFI for architectures. - efiArch = - if final.isx86_32 then "ia32" - else if final.isx86_64 then "x64" - else if final.isAarch32 then "arm" - else if final.isAarch64 then "aa64" - else final.parsed.cpu.name; - - darwinArch = { - armv7a = "armv7"; - aarch64 = "arm64"; - }.${final.parsed.cpu.name} or final.parsed.cpu.name; - - darwinPlatform = - if final.isMacOS then "macos" - else if final.isiOS then "ios" - else null; - # The canonical name for this attribute is darwinSdkVersion, but some - # platforms define the old name "sdkVer". - darwinSdkVersion = final.sdkVer or "11.3"; - darwinMinVersion = final.darwinSdkVersion; - darwinMinVersionVariable = - if final.isMacOS then "MACOSX_DEPLOYMENT_TARGET" - else if final.isiOS then "IPHONEOS_DEPLOYMENT_TARGET" - else null; - - # Remove before 25.05 - androidSdkVersion = - if (args ? sdkVer && !args ? androidSdkVersion) then - throw "For android `sdkVer` has been renamed to `androidSdkVersion`" - else if (args ? androidSdkVersion) then - args.androidSdkVersion - else - null; - androidNdkVersion = - if (args ? ndkVer && !args ? androidNdkVersion) then - throw "For android `ndkVer` has been renamed to `androidNdkVersion`" - else if (args ? androidSdkVersion) then - args.androidNdkVersion - else - null; - } // ( - let - selectEmulator = pkgs: + elaborate = + systemOrArgs: + let + allArgs = systemToAttrs systemOrArgs; + + # Those two will always be derived from "config", if given, so they should NOT + # be overridden further down with "// args". + args = builtins.removeAttrs allArgs [ + "parsed" + "system" + ]; + + # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. + rust = args.rust or args.rustc or { }; + + final = + { + # Prefer to parse `config` as it is strictly more informative. + parsed = parse.mkSystemFromString (args.config or allArgs.system); + # This can be losslessly-extracted from `parsed` iff parsing succeeds. + system = parse.doubleFromSystem final.parsed; + # TODO: This currently can't be losslessly-extracted from `parsed`, for example + # because of -mingw32. + config = parse.tripleFromSystem final.parsed; + # Determine whether we can execute binaries built for the provided platform. + canExecute = + platform: + final.isAndroid == platform.isAndroid + && parse.isCompatible final.parsed.cpu platform.parsed.cpu + && final.parsed.kernel == platform.parsed.kernel; + isCompatible = + _: + throw "2022-05-23: isCompatible has been removed in favor of canExecute, refer to the 22.11 changelog for details"; + # Derived meta-data + useLLVM = final.isFreeBSD || final.isOpenBSD; + + libc = + if final.isDarwin then + "libSystem" + else if final.isMinGW then + "msvcrt" + else if final.isWasi then + "wasilibc" + else if final.isWasm && !final.isWasi then + null + else if final.isRedox then + "relibc" + else if final.isMusl then + "musl" + else if final.isUClibc then + "uclibc" + else if final.isAndroid then + "bionic" + else if final.isLLVMLibc then + "llvm" + else if + final.isLinux # default + then + "glibc" + else if final.isFreeBSD then + "fblibc" + else if final.isOpenBSD then + "oblibc" + else if final.isNetBSD then + "nblibc" + else if final.isAvr then + "avrlibc" + else if final.isGhcjs then + null + else if final.isNone then + "newlib" + # TODO(@Ericson2314) think more about other operating systems + else + "native/impure"; + # Choose what linker we wish to use by default. Someday we might also + # choose the C compiler, runtime library, C++ standard library, etc. in + # this way, nice and orthogonally, and deprecate `useLLVM`. But due to + # the monolithic GCC build we cannot actually make those choices + # independently, so we are just doing `linker` and keeping `useLLVM` for + # now. + linker = + if final.useLLVM or false then + "lld" + else if final.isDarwin then + "cctools" + # "bfd" and "gold" both come from GNU binutils. The existence of Gold + # is why we use the more obscure "bfd" and not "binutils" for this + # choice. + else + "bfd"; + # The standard lib directory name that non-nixpkgs binaries distributed + # for this platform normally assume. + libDir = + if final.isLinux then + if final.isx86_64 || final.isMips64 || final.isPower64 then "lib64" else "lib" + else + null; + extensions = + optionalAttrs final.hasSharedLibraries { + sharedLibrary = + 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 final.extensions.staticLibrary else final.extensions.sharedLibrary; + executable = if final.isWindows then ".exe" else ""; + }; + # Misc boolean options + useAndroidPrebuilt = false; + useiOSPrebuilt = false; + + # Output from uname + uname = { + # uname -s + system = + { + linux = "Linux"; + windows = "Windows"; + darwin = "Darwin"; + netbsd = "NetBSD"; + freebsd = "FreeBSD"; + openbsd = "OpenBSD"; + wasi = "Wasi"; + redox = "Redox"; + genode = "Genode"; + } + .${final.parsed.kernel.name} or null; + + # uname -m + processor = + if final.isPower64 then + "ppc64${optionalString final.isLittleEndian "le"}" + else if final.isPower then + "ppc${optionalString final.isLittleEndian "le"}" + else if final.isMips64 then + "mips64" # endianness is *not* included on mips64 + else + final.parsed.cpu.name; + + # 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 + || isWindows # Windows + || isWasm # WASM + ) + && !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.isWasi || final.isRedox; + + # Just a guess, based on `system` + inherit + ( + { + linux-kernel = args.linux-kernel or { }; + gcc = args.gcc or { }; + } + // platforms.select final + ) + linux-kernel + gcc + ; + + # TODO: remove after 23.05 is EOL, with an error pointing to the rust.* attrs. + rustc = args.rustc or { }; + + linuxArch = + if final.isAarch32 then + "arm" + else if final.isAarch64 then + "arm64" + else if final.isx86_32 then + "i386" + else if final.isx86_64 then + "x86_64" + # linux kernel does not distinguish microblaze/microblazeel + else if final.isMicroBlaze then + "microblaze" + else if final.isMips32 then + "mips" + else if final.isMips64 then + "mips" # linux kernel does not distinguish mips32/mips64 + else if final.isPower then + "powerpc" + else if final.isRiscV then + "riscv" + else if final.isS390 then + "s390" + else if final.isLoongArch64 then + "loongarch" + else + final.parsed.cpu.name; + + # https://source.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106 + ubootArch = + if final.isx86_32 then + "x86" # not i386 + else if final.isMips64 then + "mips64" # uboot *does* distinguish between mips32/mips64 + else + final.linuxArch; # other cases appear to agree with linuxArch + + qemuArch = + if final.isAarch32 then + "arm" + else if final.isS390 && !final.isS390x then + null + else if final.isx86_64 then + "x86_64" + else if final.isx86 then + "i386" + else if final.isMips64n32 then + "mipsn32${optionalString final.isLittleEndian "el"}" + else if final.isMips64 then + "mips64${optionalString final.isLittleEndian "el"}" + else + final.uname.processor; + + # Name used by UEFI for architectures. + efiArch = + if final.isx86_32 then + "ia32" + else if final.isx86_64 then + "x64" + else if final.isAarch32 then + "arm" + else if final.isAarch64 then + "aa64" + else + final.parsed.cpu.name; + + darwinArch = + { + armv7a = "armv7"; + aarch64 = "arm64"; + } + .${final.parsed.cpu.name} or final.parsed.cpu.name; + + darwinPlatform = + if final.isMacOS then + "macos" + else if final.isiOS then + "ios" + else + null; + # The canonical name for this attribute is darwinSdkVersion, but some + # platforms define the old name "sdkVer". + darwinSdkVersion = final.sdkVer or "11.3"; + darwinMinVersion = final.darwinSdkVersion; + darwinMinVersionVariable = + if final.isMacOS then + "MACOSX_DEPLOYMENT_TARGET" + else if final.isiOS then + "IPHONEOS_DEPLOYMENT_TARGET" + else + null; + + # Remove before 25.05 + androidSdkVersion = + if (args ? sdkVer && !args ? androidSdkVersion) then + throw "For android `sdkVer` has been renamed to `androidSdkVersion`" + else if (args ? androidSdkVersion) then + args.androidSdkVersion + else + null; + androidNdkVersion = + if (args ? ndkVer && !args ? androidNdkVersion) then + throw "For android `ndkVer` has been renamed to `androidNdkVersion`" + else if (args ? androidSdkVersion) then + args.androidNdkVersion + else + null; + } + // ( let - wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal; + selectEmulator = + pkgs: + let + wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal; + in + # Note: we guarantee that the return value is either `null` or a path + # to an emulator program. That is, if an emulator requires additional + # arguments, a wrapper should be used. + if pkgs.stdenv.hostPlatform.canExecute final then + lib.getExe (pkgs.writeShellScriptBin "exec" ''exec "$@"'') + else if final.isWindows then + "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}" + else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null then + "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}" + else if final.isWasi then + "${pkgs.wasmtime}/bin/wasmtime" + else if final.isMmix then + "${pkgs.mmixware}/bin/mmix" + else + null; in - # Note: we guarantee that the return value is either `null` or a path - # to an emulator program. That is, if an emulator requires additional - # arguments, a wrapper should be used. - if pkgs.stdenv.hostPlatform.canExecute final - then lib.getExe (pkgs.writeShellScriptBin "exec" ''exec "$@"'') - else if final.isWindows - then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}" - else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null - then "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}" - else if final.isWasi - then "${pkgs.wasmtime}/bin/wasmtime" - else if final.isMmix - then "${pkgs.mmixware}/bin/mmix" - else null; - in { - emulatorAvailable = pkgs: (selectEmulator pkgs) != null; - - # whether final.emulator pkgs.pkgsStatic works - staticEmulatorAvailable = pkgs: final.emulatorAvailable pkgs - && (final.isLinux || final.isWasi || final.isMmix); - - emulator = pkgs: - if (final.emulatorAvailable pkgs) - then selectEmulator pkgs - else throw "Don't know how to run ${final.config} executables."; - - }) // mapAttrs (n: v: v final.parsed) inspect.predicates - // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates - // args // { - rust = rust // { - # Once args.rustc.platform.target-family is deprecated and - # removed, there will no longer be any need to modify any - # values from args.rust.platform, so we can drop all the - # "args ? rust" etc. checks, and merge args.rust.platform in - # /after/. - platform = rust.platform or {} // { - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch - arch = - /**/ if rust ? platform then rust.platform.arch - else if final.isAarch32 then "arm" - else if final.isMips64 then "mips64" # never add "el" suffix - else if final.isPower64 then "powerpc64" # never add "le" suffix - else final.parsed.cpu.name; - - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os - os = - /**/ if rust ? platform then rust.platform.os or "none" - else if final.isDarwin then "macos" - else if final.isWasm && !final.isWasi then "unknown" # Needed for {wasm32,wasm64}-unknown-unknown. - else final.parsed.kernel.name; - - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family - target-family = - /**/ if args ? rust.platform.target-family then args.rust.platform.target-family - else if args ? rustc.platform.target-family - then - ( - # Since https://github.com/rust-lang/rust/pull/84072 - # `target-family` is a list instead of single value. - let - f = args.rustc.platform.target-family; - in + { + emulatorAvailable = pkgs: (selectEmulator pkgs) != null; + + # whether final.emulator pkgs.pkgsStatic works + staticEmulatorAvailable = + pkgs: final.emulatorAvailable pkgs && (final.isLinux || final.isWasi || final.isMmix); + + emulator = + pkgs: + if (final.emulatorAvailable pkgs) then + selectEmulator pkgs + else + throw "Don't know how to run ${final.config} executables."; + + } + ) + // mapAttrs (n: v: v final.parsed) inspect.predicates + // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates + // args + // { + rust = rust // { + # Once args.rustc.platform.target-family is deprecated and + # removed, there will no longer be any need to modify any + # values from args.rust.platform, so we can drop all the + # "args ? rust" etc. checks, and merge args.rust.platform in + # /after/. + platform = rust.platform or { } // { + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch + arch = + if rust ? platform then + rust.platform.arch + else if final.isAarch32 then + "arm" + else if final.isMips64 then + "mips64" # never add "el" suffix + else if final.isPower64 then + "powerpc64" # never add "le" suffix + else + final.parsed.cpu.name; + + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_os + os = + if rust ? platform then + rust.platform.os or "none" + else if final.isDarwin then + "macos" + else if final.isWasm && !final.isWasi then + "unknown" # Needed for {wasm32,wasm64}-unknown-unknown. + else + final.parsed.kernel.name; + + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_family + target-family = + if args ? rust.platform.target-family then + args.rust.platform.target-family + else if args ? rustc.platform.target-family then + ( + # Since https://github.com/rust-lang/rust/pull/84072 + # `target-family` is a list instead of single value. + let + f = args.rustc.platform.target-family; + in if isList f then f else [ f ] - ) - else optional final.isUnix "unix" - ++ optional final.isWindows "windows" - ++ optional final.isWasm "wasm"; - - # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor - vendor = let - inherit (final.parsed) vendor; - in rust.platform.vendor or { - "w64" = "pc"; - }.${vendor.name} or vendor.name; + ) + else + optional final.isUnix "unix" ++ optional final.isWindows "windows" ++ optional final.isWasm "wasm"; + + # https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor + vendor = + let + inherit (final.parsed) vendor; + in + rust.platform.vendor or { + "w64" = "pc"; + } + .${vendor.name} or vendor.name; + }; + + # The name of the rust target, even if it is custom. Adjustments are + # because rust has slightly different naming conventions than we do. + rustcTarget = + let + inherit (final.parsed) cpu kernel abi; + cpu_ = + rust.platform.arch or { + "armv7a" = "armv7"; + "armv7l" = "armv7"; + "armv6l" = "arm"; + "armv5tel" = "armv5te"; + "riscv32" = "riscv32gc"; + "riscv64" = "riscv64gc"; + } + .${cpu.name} or cpu.name; + vendor_ = final.rust.platform.vendor; + # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. + in + args.rust.rustcTarget or args.rustc.config or ( + # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. + # We cannot know which subversion does the user want, and + # currently use WASI 0.1 as default for compatibility. Custom + # users can set `rust.rustcTarget` to override it. + if final.isWasi then + "${cpu_}-wasip1" + else + "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}" + ); + + # The name of the rust target if it is standard, or the json file + # containing the custom target spec. + rustcTargetSpec = + rust.rustcTargetSpec or ( + if rust ? platform then + builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform) + else + final.rust.rustcTarget + ); + + # The name of the rust target if it is standard, or the + # basename of the file containing the custom target spec, + # without the .json extension. + # + # This is the name used by Cargo for target subdirectories. + cargoShortTarget = removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}"); + + # When used as part of an environment variable name, triples are + # uppercased and have all hyphens replaced by underscores: + # + # https://github.com/rust-lang/cargo/pull/9169 + # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431 + cargoEnvVarTarget = replaceStrings [ "-" ] [ "_" ] (toUpper final.rust.cargoShortTarget); + + # True if the target is no_std + # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421 + isNoStdTarget = any (t: hasInfix t final.rust.rustcTarget) [ + "-none" + "nvptx" + "switch" + "-uefi" + ]; }; - - # The name of the rust target, even if it is custom. Adjustments are - # because rust has slightly different naming conventions than we do. - rustcTarget = let - inherit (final.parsed) cpu kernel abi; - cpu_ = rust.platform.arch or { - "armv7a" = "armv7"; - "armv7l" = "armv7"; - "armv6l" = "arm"; - "armv5tel" = "armv5te"; - "riscv32" = "riscv32gc"; - "riscv64" = "riscv64gc"; - }.${cpu.name} or cpu.name; - vendor_ = final.rust.platform.vendor; - # TODO: deprecate args.rustc in favour of args.rust after 23.05 is EOL. - in - args.rust.rustcTarget or - args.rustc.config or ( - # Rust uses `wasm32-wasip?` rather than `wasm32-unknown-wasi`. - # We cannot know which subversion does the user want, and - # currently use WASI 0.1 as default for compatibility. Custom - # users can set `rust.rustcTarget` to override it. - if final.isWasi - then "${cpu_}-wasip1" - else "${cpu_}-${vendor_}-${kernel.name}${optionalString (abi.name != "unknown") "-${abi.name}"}" - ); - - # The name of the rust target if it is standard, or the json file - # containing the custom target spec. - rustcTargetSpec = rust.rustcTargetSpec or ( - /**/ if rust ? platform - then builtins.toFile (final.rust.rustcTarget + ".json") (toJSON rust.platform) - else final.rust.rustcTarget); - - # The name of the rust target if it is standard, or the - # basename of the file containing the custom target spec, - # without the .json extension. - # - # This is the name used by Cargo for target subdirectories. - cargoShortTarget = - removeSuffix ".json" (baseNameOf "${final.rust.rustcTargetSpec}"); - - # When used as part of an environment variable name, triples are - # uppercased and have all hyphens replaced by underscores: - # - # https://github.com/rust-lang/cargo/pull/9169 - # https://github.com/rust-lang/cargo/issues/8285#issuecomment-634202431 - cargoEnvVarTarget = - replaceStrings ["-"] ["_"] - (toUpper final.rust.cargoShortTarget); - - # True if the target is no_std - # https://github.com/rust-lang/rust/blob/2e44c17c12cec45b6a682b1e53a04ac5b5fcc9d2/src/bootstrap/config.rs#L415-L421 - isNoStdTarget = - any (t: hasInfix t final.rust.rustcTarget) ["-none" "nvptx" "switch" "-uefi"]; }; - }; - in assert final.useAndroidPrebuilt -> final.isAndroid; - assert foldl - (pass: { assertion, message }: - if assertion final - then pass - else throw message) - true - (final.parsed.abi.assertions or []); + in + assert final.useAndroidPrebuilt -> final.isAndroid; + assert foldl (pass: { assertion, message }: if assertion final then pass else throw message) true ( + final.parsed.abi.assertions or [ ] + ); final; in diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix index a753c835c810..b2a66021dfc9 100644 --- a/lib/systems/doubles.nix +++ b/lib/systems/doubles.nix @@ -7,16 +7,24 @@ let all = [ # Cygwin - "i686-cygwin" "x86_64-cygwin" + "i686-cygwin" + "x86_64-cygwin" # Darwin - "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" + "x86_64-darwin" + "i686-darwin" + "aarch64-darwin" + "armv7a-darwin" # FreeBSD - "i686-freebsd" "x86_64-freebsd" "aarch64-freebsd" + "i686-freebsd" + "x86_64-freebsd" + "aarch64-freebsd" # Genode - "aarch64-genode" "i686-genode" "x86_64-genode" + "aarch64-genode" + "i686-genode" + "x86_64-genode" # illumos "x86_64-solaris" @@ -25,94 +33,163 @@ let "javascript-ghcjs" # Linux - "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" - "armv7l-linux" "i686-linux" "loongarch64-linux" "m68k-linux" "microblaze-linux" - "microblazeel-linux" "mips-linux" "mips64-linux" "mips64el-linux" - "mipsel-linux" "powerpc64-linux" "powerpc64le-linux" "riscv32-linux" - "riscv64-linux" "s390-linux" "s390x-linux" "x86_64-linux" + "aarch64-linux" + "armv5tel-linux" + "armv6l-linux" + "armv7a-linux" + "armv7l-linux" + "i686-linux" + "loongarch64-linux" + "m68k-linux" + "microblaze-linux" + "microblazeel-linux" + "mips-linux" + "mips64-linux" + "mips64el-linux" + "mipsel-linux" + "powerpc64-linux" + "powerpc64le-linux" + "riscv32-linux" + "riscv64-linux" + "s390-linux" + "s390x-linux" + "x86_64-linux" # MMIXware "mmix-mmixware" # NetBSD - "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" - "i686-netbsd" "m68k-netbsd" "mipsel-netbsd" "powerpc-netbsd" - "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" + "aarch64-netbsd" + "armv6l-netbsd" + "armv7a-netbsd" + "armv7l-netbsd" + "i686-netbsd" + "m68k-netbsd" + "mipsel-netbsd" + "powerpc-netbsd" + "riscv32-netbsd" + "riscv64-netbsd" + "x86_64-netbsd" # none - "aarch64_be-none" "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-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" + "aarch64_be-none" + "aarch64-none" + "arm-none" + "armv6l-none" + "avr-none" + "i686-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" # OpenBSD - "i686-openbsd" "x86_64-openbsd" + "i686-openbsd" + "x86_64-openbsd" # Redox "x86_64-redox" # WASI - "wasm64-wasi" "wasm32-wasi" + "wasm64-wasi" + "wasm32-wasi" # Windows - "aarch64-windows" "x86_64-windows" "i686-windows" + "aarch64-windows" + "x86_64-windows" + "i686-windows" ]; allParsed = map parse.mkSystemFromString all; filterDoubles = f: map parse.doubleFromSystem (lists.filter f allParsed); -in { +in +{ inherit all; - none = []; - - arm = filterDoubles predicates.isAarch32; - armv7 = filterDoubles predicates.isArmv7; - aarch = filterDoubles predicates.isAarch; - aarch64 = filterDoubles predicates.isAarch64; - x86 = filterDoubles predicates.isx86; - i686 = filterDoubles predicates.isi686; - x86_64 = filterDoubles predicates.isx86_64; - microblaze = filterDoubles predicates.isMicroBlaze; - mips = filterDoubles predicates.isMips; - mmix = filterDoubles predicates.isMmix; - power = filterDoubles predicates.isPower; - riscv = filterDoubles predicates.isRiscV; - riscv32 = filterDoubles predicates.isRiscV32; - riscv64 = filterDoubles predicates.isRiscV64; - rx = filterDoubles predicates.isRx; - vc4 = filterDoubles predicates.isVc4; - or1k = filterDoubles predicates.isOr1k; - m68k = filterDoubles predicates.isM68k; - s390 = filterDoubles predicates.isS390; - s390x = filterDoubles predicates.isS390x; - loongarch64 = filterDoubles predicates.isLoongArch64; - js = filterDoubles predicates.isJavaScript; - - bigEndian = filterDoubles predicates.isBigEndian; - littleEndian = filterDoubles predicates.isLittleEndian; - - cygwin = filterDoubles predicates.isCygwin; - darwin = filterDoubles predicates.isDarwin; - freebsd = filterDoubles predicates.isFreeBSD; + none = [ ]; + + arm = filterDoubles predicates.isAarch32; + armv7 = filterDoubles predicates.isArmv7; + aarch = filterDoubles predicates.isAarch; + aarch64 = filterDoubles predicates.isAarch64; + x86 = filterDoubles predicates.isx86; + i686 = filterDoubles predicates.isi686; + x86_64 = filterDoubles predicates.isx86_64; + microblaze = filterDoubles predicates.isMicroBlaze; + mips = filterDoubles predicates.isMips; + mmix = filterDoubles predicates.isMmix; + power = filterDoubles predicates.isPower; + riscv = filterDoubles predicates.isRiscV; + riscv32 = filterDoubles predicates.isRiscV32; + riscv64 = filterDoubles predicates.isRiscV64; + rx = filterDoubles predicates.isRx; + vc4 = filterDoubles predicates.isVc4; + or1k = filterDoubles predicates.isOr1k; + m68k = filterDoubles predicates.isM68k; + s390 = filterDoubles predicates.isS390; + s390x = filterDoubles predicates.isS390x; + loongarch64 = filterDoubles predicates.isLoongArch64; + js = filterDoubles predicates.isJavaScript; + + bigEndian = filterDoubles predicates.isBigEndian; + littleEndian = filterDoubles predicates.isLittleEndian; + + cygwin = filterDoubles predicates.isCygwin; + darwin = filterDoubles predicates.isDarwin; + freebsd = filterDoubles predicates.isFreeBSD; # Should be better, but MinGW is unclear. - gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabin32; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabi64; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabielfv1; }) - ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnuabielfv2; }); - illumos = filterDoubles predicates.isSunOS; - linux = filterDoubles predicates.isLinux; - netbsd = filterDoubles predicates.isNetBSD; - openbsd = filterDoubles predicates.isOpenBSD; - unix = filterDoubles predicates.isUnix; - wasi = filterDoubles predicates.isWasi; - redox = filterDoubles predicates.isRedox; - windows = filterDoubles predicates.isWindows; - genode = filterDoubles predicates.isGenode; - - embedded = filterDoubles predicates.isNone; + gnu = + filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnu; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnueabi; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnueabihf; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnuabin32; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnuabi64; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnuabielfv1; + }) + ++ filterDoubles (matchAttrs { + kernel = parse.kernels.linux; + abi = parse.abis.gnuabielfv2; + }); + illumos = filterDoubles predicates.isSunOS; + linux = filterDoubles predicates.isLinux; + netbsd = filterDoubles predicates.isNetBSD; + openbsd = filterDoubles predicates.isOpenBSD; + unix = filterDoubles predicates.isUnix; + wasi = filterDoubles predicates.isWasi; + redox = filterDoubles predicates.isRedox; + windows = filterDoubles predicates.isWindows; + genode = filterDoubles predicates.isGenode; + + embedded = filterDoubles predicates.isNone; } diff --git a/lib/systems/examples.nix b/lib/systems/examples.nix index 223de50f67dd..cb54d8fd631a 100644 --- a/lib/systems/examples.nix +++ b/lib/systems/examples.nix @@ -26,7 +26,9 @@ rec { }; ppc64-musl = { config = "powerpc64-unknown-linux-musl"; - gcc = { abi = "elfv2"; }; + gcc = { + abi = "elfv2"; + }; }; sheevaplug = { @@ -95,16 +97,28 @@ rec { } // platforms.fuloong2f_n32; # can execute on 32bit chip - mips-linux-gnu = { config = "mips-unknown-linux-gnu"; } // platforms.gcc_mips32r2_o32; - mipsel-linux-gnu = { config = "mipsel-unknown-linux-gnu"; } // platforms.gcc_mips32r2_o32; + mips-linux-gnu = { + config = "mips-unknown-linux-gnu"; + } // platforms.gcc_mips32r2_o32; + mipsel-linux-gnu = { + config = "mipsel-unknown-linux-gnu"; + } // platforms.gcc_mips32r2_o32; # require 64bit chip (for more registers, 64-bit floating point, 64-bit "long long") but use 32bit pointers - mips64-linux-gnuabin32 = { config = "mips64-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; - mips64el-linux-gnuabin32 = { config = "mips64el-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; + mips64-linux-gnuabin32 = { + config = "mips64-unknown-linux-gnuabin32"; + } // platforms.gcc_mips64r2_n32; + mips64el-linux-gnuabin32 = { + config = "mips64el-unknown-linux-gnuabin32"; + } // platforms.gcc_mips64r2_n32; # 64bit pointers - mips64-linux-gnuabi64 = { config = "mips64-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; - mips64el-linux-gnuabi64 = { config = "mips64el-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; + mips64-linux-gnuabi64 = { + config = "mips64-unknown-linux-gnuabi64"; + } // platforms.gcc_mips64r2_64; + mips64el-linux-gnuabi64 = { + config = "mips64el-unknown-linux-gnuabi64"; + } // platforms.gcc_mips64r2_64; muslpi = raspberryPi // { config = "armv6l-unknown-linux-musleabihf"; @@ -114,12 +128,20 @@ rec { config = "aarch64-unknown-linux-musl"; }; - gnu64 = { config = "x86_64-unknown-linux-gnu"; }; + gnu64 = { + config = "x86_64-unknown-linux-gnu"; + }; gnu64_simplekernel = gnu64 // platforms.pc_simplekernel; # see test/cross/default.nix - gnu32 = { config = "i686-unknown-linux-gnu"; }; + gnu32 = { + config = "i686-unknown-linux-gnu"; + }; - musl64 = { config = "x86_64-unknown-linux-musl"; }; - musl32 = { config = "i686-unknown-linux-musl"; }; + musl64 = { + config = "x86_64-unknown-linux-musl"; + }; + musl32 = { + config = "i686-unknown-linux-musl"; + }; riscv64 = riscv "64"; riscv32 = riscv "32"; @@ -294,13 +316,13 @@ rec { aarch64-darwin = { config = "aarch64-apple-darwin"; xcodePlatform = "MacOSX"; - platform = {}; + platform = { }; }; x86_64-darwin = { config = "x86_64-apple-darwin"; xcodePlatform = "MacOSX"; - platform = {}; + platform = { }; }; # diff --git a/lib/systems/inspect.nix b/lib/systems/inspect.nix index de4df13cd811..c330d5473bc5 100644 --- a/lib/systems/inspect.nix +++ b/lib/systems/inspect.nix @@ -38,124 +38,429 @@ rec { # `lib.attrsets.matchAttrs`, which requires a match on *all* attributes of # the product. - isi686 = { cpu = cpuTypes.i686; }; - isx86_32 = { cpu = { family = "x86"; bits = 32; }; }; - isx86_64 = { cpu = { family = "x86"; bits = 64; }; }; - isPower = { cpu = { family = "power"; }; }; - isPower64 = { cpu = { family = "power"; bits = 64; }; }; + isi686 = { + cpu = cpuTypes.i686; + }; + isx86_32 = { + cpu = { + family = "x86"; + bits = 32; + }; + }; + isx86_64 = { + cpu = { + family = "x86"; + bits = 64; + }; + }; + isPower = { + cpu = { + family = "power"; + }; + }; + isPower64 = { + cpu = { + family = "power"; + bits = 64; + }; + }; # This ABI is the default in NixOS PowerPC64 BE, but not on mainline GCC, # so it sometimes causes issues in certain packages that makes the wrong # assumption on the used ABI. isAbiElfv2 = [ - { abi = { abi = "elfv2"; }; } - { abi = { name = "musl"; }; cpu = { family = "power"; bits = 64; }; } + { + abi = { + abi = "elfv2"; + }; + } + { + abi = { + name = "musl"; + }; + cpu = { + family = "power"; + bits = 64; + }; + } ]; - isx86 = { cpu = { family = "x86"; }; }; - isAarch32 = { cpu = { family = "arm"; bits = 32; }; }; - isArmv7 = map ({ arch, ... }: { cpu = { inherit arch; }; }) - (filter (cpu: hasPrefix "armv7" cpu.arch or "") - (attrValues cpuTypes)); - isAarch64 = { cpu = { family = "arm"; bits = 64; }; }; - isAarch = { cpu = { family = "arm"; }; }; - isMicroBlaze = { cpu = { family = "microblaze"; }; }; - isMips = { cpu = { family = "mips"; }; }; - isMips32 = { cpu = { family = "mips"; bits = 32; }; }; - isMips64 = { cpu = { family = "mips"; bits = 64; }; }; - isMips64n32 = { cpu = { family = "mips"; bits = 64; }; abi = { abi = "n32"; }; }; - isMips64n64 = { cpu = { family = "mips"; bits = 64; }; abi = { abi = "64"; }; }; - isMmix = { cpu = { family = "mmix"; }; }; - isRiscV = { cpu = { family = "riscv"; }; }; - isRiscV32 = { cpu = { family = "riscv"; bits = 32; }; }; - isRiscV64 = { cpu = { family = "riscv"; bits = 64; }; }; - isRx = { cpu = { family = "rx"; }; }; - isSparc = { cpu = { family = "sparc"; }; }; - isSparc64 = { cpu = { family = "sparc"; bits = 64; }; }; - isWasm = { cpu = { family = "wasm"; }; }; - isMsp430 = { cpu = { family = "msp430"; }; }; - isVc4 = { cpu = { family = "vc4"; }; }; - isAvr = { cpu = { family = "avr"; }; }; - isAlpha = { cpu = { family = "alpha"; }; }; - isOr1k = { cpu = { family = "or1k"; }; }; - isM68k = { cpu = { family = "m68k"; }; }; - isS390 = { cpu = { family = "s390"; }; }; - isS390x = { cpu = { family = "s390"; bits = 64; }; }; - isLoongArch64 = { cpu = { family = "loongarch"; bits = 64; }; }; - isJavaScript = { cpu = cpuTypes.javascript; }; - - is32bit = { cpu = { bits = 32; }; }; - is64bit = { cpu = { bits = 64; }; }; - isILP32 = [ { cpu = { family = "wasm"; bits = 32; }; } ] ++ - map (a: { abi = { abi = a; }; }) [ "n32" "ilp32" "x32" ]; - isBigEndian = { cpu = { significantByte = significantBytes.bigEndian; }; }; - isLittleEndian = { cpu = { significantByte = significantBytes.littleEndian; }; }; - - isBSD = { kernel = { families = { inherit (kernelFamilies) bsd; }; }; }; - isDarwin = { kernel = { families = { inherit (kernelFamilies) darwin; }; }; }; - isUnix = [ isBSD isDarwin isLinux isSunOS isCygwin isRedox ]; - - isMacOS = { kernel = kernels.macos; }; - isiOS = { kernel = kernels.ios; }; - isLinux = { kernel = kernels.linux; }; - isSunOS = { kernel = kernels.solaris; }; - isFreeBSD = { kernel = { name = "freebsd"; }; }; - isNetBSD = { kernel = kernels.netbsd; }; - isOpenBSD = { kernel = kernels.openbsd; }; - isWindows = { kernel = kernels.windows; }; - isCygwin = { kernel = kernels.windows; abi = abis.cygnus; }; - isMinGW = { kernel = kernels.windows; abi = abis.gnu; }; - isWasi = { kernel = kernels.wasi; }; - isRedox = { kernel = kernels.redox; }; - isGhcjs = { kernel = kernels.ghcjs; }; - isGenode = { kernel = kernels.genode; }; - isNone = { kernel = kernels.none; }; - - isAndroid = [ { abi = abis.android; } { abi = abis.androideabi; } ]; - 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 ]; - isLLVMLibc = [ { abi = abis.llvm; } ]; + isx86 = { + cpu = { + family = "x86"; + }; + }; + isAarch32 = { + cpu = { + family = "arm"; + bits = 32; + }; + }; + isArmv7 = map ( + { arch, ... }: + { + cpu = { inherit arch; }; + } + ) (filter (cpu: hasPrefix "armv7" cpu.arch or "") (attrValues cpuTypes)); + isAarch64 = { + cpu = { + family = "arm"; + bits = 64; + }; + }; + isAarch = { + cpu = { + family = "arm"; + }; + }; + isMicroBlaze = { + cpu = { + family = "microblaze"; + }; + }; + isMips = { + cpu = { + family = "mips"; + }; + }; + isMips32 = { + cpu = { + family = "mips"; + bits = 32; + }; + }; + isMips64 = { + cpu = { + family = "mips"; + bits = 64; + }; + }; + isMips64n32 = { + cpu = { + family = "mips"; + bits = 64; + }; + abi = { + abi = "n32"; + }; + }; + isMips64n64 = { + cpu = { + family = "mips"; + bits = 64; + }; + abi = { + abi = "64"; + }; + }; + isMmix = { + cpu = { + family = "mmix"; + }; + }; + isRiscV = { + cpu = { + family = "riscv"; + }; + }; + isRiscV32 = { + cpu = { + family = "riscv"; + bits = 32; + }; + }; + isRiscV64 = { + cpu = { + family = "riscv"; + bits = 64; + }; + }; + isRx = { + cpu = { + family = "rx"; + }; + }; + isSparc = { + cpu = { + family = "sparc"; + }; + }; + isSparc64 = { + cpu = { + family = "sparc"; + bits = 64; + }; + }; + isWasm = { + cpu = { + family = "wasm"; + }; + }; + isMsp430 = { + cpu = { + family = "msp430"; + }; + }; + isVc4 = { + cpu = { + family = "vc4"; + }; + }; + isAvr = { + cpu = { + family = "avr"; + }; + }; + isAlpha = { + cpu = { + family = "alpha"; + }; + }; + isOr1k = { + cpu = { + family = "or1k"; + }; + }; + isM68k = { + cpu = { + family = "m68k"; + }; + }; + isS390 = { + cpu = { + family = "s390"; + }; + }; + isS390x = { + cpu = { + family = "s390"; + bits = 64; + }; + }; + isLoongArch64 = { + cpu = { + family = "loongarch"; + bits = 64; + }; + }; + isJavaScript = { + cpu = cpuTypes.javascript; + }; + + is32bit = { + cpu = { + bits = 32; + }; + }; + is64bit = { + cpu = { + bits = 64; + }; + }; + isILP32 = + [ + { + cpu = { + family = "wasm"; + bits = 32; + }; + } + ] + ++ map + (a: { + abi = { + abi = a; + }; + }) + [ + "n32" + "ilp32" + "x32" + ]; + isBigEndian = { + cpu = { + significantByte = significantBytes.bigEndian; + }; + }; + isLittleEndian = { + cpu = { + significantByte = significantBytes.littleEndian; + }; + }; + + isBSD = { + kernel = { + families = { inherit (kernelFamilies) bsd; }; + }; + }; + isDarwin = { + kernel = { + families = { inherit (kernelFamilies) darwin; }; + }; + }; + isUnix = [ + isBSD + isDarwin + isLinux + isSunOS + isCygwin + isRedox + ]; + + isMacOS = { + kernel = kernels.macos; + }; + isiOS = { + kernel = kernels.ios; + }; + isLinux = { + kernel = kernels.linux; + }; + isSunOS = { + kernel = kernels.solaris; + }; + isFreeBSD = { + kernel = { + name = "freebsd"; + }; + }; + isNetBSD = { + kernel = kernels.netbsd; + }; + isOpenBSD = { + kernel = kernels.openbsd; + }; + isWindows = { + kernel = kernels.windows; + }; + isCygwin = { + kernel = kernels.windows; + abi = abis.cygnus; + }; + isMinGW = { + kernel = kernels.windows; + abi = abis.gnu; + }; + isWasi = { + kernel = kernels.wasi; + }; + isRedox = { + kernel = kernels.redox; + }; + isGhcjs = { + kernel = kernels.ghcjs; + }; + isGenode = { + kernel = kernels.genode; + }; + isNone = { + kernel = kernels.none; + }; + + isAndroid = [ + { abi = abis.android; } + { abi = abis.androideabi; } + ]; + 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 + ]; + isLLVMLibc = [ { abi = abis.llvm; } ]; isEfi = [ - { cpu = { family = "arm"; version = "6"; }; } - { cpu = { family = "arm"; version = "7"; }; } - { cpu = { family = "arm"; version = "8"; }; } - { cpu = { family = "riscv"; }; } - { cpu = { family = "x86"; }; } + { + cpu = { + family = "arm"; + version = "6"; + }; + } + { + cpu = { + family = "arm"; + version = "7"; + }; + } + { + cpu = { + family = "arm"; + version = "8"; + }; + } + { + cpu = { + family = "riscv"; + }; + } + { + cpu = { + family = "x86"; + }; + } ]; - isElf = { kernel.execFormat = execFormats.elf; }; - isMacho = { kernel.execFormat = execFormats.macho; }; + isElf = { + kernel.execFormat = execFormats.elf; + }; + isMacho = { + kernel.execFormat = execFormats.macho; + }; }; # given two patterns, return a pattern which is their logical AND. # Since a pattern is a list-of-disjuncts, this needs to - patternLogicalAnd = pat1_: pat2_: + patternLogicalAnd = + pat1_: pat2_: let # patterns can be either a list or a (bare) singleton; turn # them into singletons for uniform handling pat1 = toList pat1_; pat2 = toList pat2_; in - concatMap (attr1: - map (attr2: - recursiveUpdateUntil - (path: subattr1: subattr2: - if (builtins.intersectAttrs subattr1 subattr2) == {} || subattr1 == subattr2 - then true - else throw '' - pattern conflict at path ${toString path}: - ${toJSON subattr1} - ${toJSON subattr2} - '') - attr1 - attr2 - ) - pat2) - pat1; - - matchAnyAttrs = patterns: - if isList patterns then attrs: any (pattern: matchAttrs pattern attrs) patterns - else matchAttrs patterns; + concatMap ( + attr1: + map ( + attr2: + recursiveUpdateUntil ( + path: subattr1: subattr2: + if (builtins.intersectAttrs subattr1 subattr2) == { } || subattr1 == subattr2 then + true + else + throw '' + pattern conflict at path ${toString path}: + ${toJSON subattr1} + ${toJSON subattr2} + '' + ) attr1 attr2 + ) pat2 + ) pat1; + + matchAnyAttrs = + patterns: + if isList patterns then + attrs: any (pattern: matchAttrs pattern attrs) patterns + else + matchAttrs patterns; predicates = mapAttrs (_: matchAnyAttrs) patterns; @@ -164,7 +469,9 @@ rec { # that `lib.meta.availableOn` can distinguish them from the patterns which # apply only to the `parsed` field. - platformPatterns = mapAttrs (_: p: { parsed = {}; } // p) { - isStatic = { isStatic = true; }; + platformPatterns = mapAttrs (_: p: { parsed = { }; } // p) { + isStatic = { + isStatic = true; + }; }; } diff --git a/lib/systems/parse.nix b/lib/systems/parse.nix index e3e6ef16c6b0..a7f65e69bf4c 100644 --- a/lib/systems/parse.nix +++ b/lib/systems/parse.nix @@ -55,19 +55,23 @@ let types ; - setTypes = type: - mapAttrs (name: value: + setTypes = + type: + mapAttrs ( + name: value: assert type.check value; - setType type.name ({ inherit name; } // value)); + setType type.name ({ inherit name; } // value) + ); # gnu-config will ignore the portion of a triple matching the # regex `e?abi.*$` when determining the validity of a triple. In # other words, `i386-linuxabichickenlips` is a valid triple. - removeAbiSuffix = x: - let found = match "(.*)e?abi.*" x; - in if found == null - then x - else elemAt found 0; + removeAbiSuffix = + x: + let + found = match "(.*)e?abi.*" x; + in + if found == null then x else elemAt found 0; in @@ -84,14 +88,20 @@ rec { types.significantByte = enum (attrValues significantBytes); significantBytes = setTypes types.openSignificantByte { - bigEndian = {}; - littleEndian = {}; + bigEndian = { }; + littleEndian = { }; }; ################################################################################ # Reasonable power of 2 - types.bitWidth = enum [ 8 16 32 64 128 ]; + types.bitWidth = enum [ + 8 + 16 + 32 + 64 + 128 + ]; ################################################################################ @@ -99,87 +109,307 @@ rec { name = "cpu-type"; description = "instruction set architecture name and information"; merge = mergeOneOption; - check = x: types.bitWidth.check x.bits - && (if 8 < x.bits - then types.significantByte.check x.significantByte - else !(x ? significantByte)); + check = + x: + types.bitWidth.check x.bits + && (if 8 < x.bits then types.significantByte.check x.significantByte else !(x ? significantByte)); }; types.cpuType = enum (attrValues cpuTypes); - cpuTypes = let inherit (significantBytes) bigEndian littleEndian; in setTypes types.openCpuType { - arm = { bits = 32; significantByte = littleEndian; family = "arm"; }; - armv5tel = { bits = 32; significantByte = littleEndian; family = "arm"; version = "5"; arch = "armv5t"; }; - armv6m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "6"; arch = "armv6-m"; }; - armv6l = { bits = 32; significantByte = littleEndian; family = "arm"; version = "6"; arch = "armv6"; }; - armv7a = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; arch = "armv7-a"; }; - armv7r = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; arch = "armv7-r"; }; - armv7m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; arch = "armv7-m"; }; - armv7l = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; arch = "armv7"; }; - armv8a = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; arch = "armv8-a"; }; - armv8r = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; arch = "armv8-a"; }; - armv8m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; arch = "armv8-m"; }; - aarch64 = { bits = 64; significantByte = littleEndian; family = "arm"; version = "8"; arch = "armv8-a"; }; - aarch64_be = { bits = 64; significantByte = bigEndian; family = "arm"; version = "8"; arch = "armv8-a"; }; - - i386 = { bits = 32; significantByte = littleEndian; family = "x86"; arch = "i386"; }; - i486 = { bits = 32; significantByte = littleEndian; family = "x86"; arch = "i486"; }; - i586 = { bits = 32; significantByte = littleEndian; family = "x86"; arch = "i586"; }; - i686 = { bits = 32; significantByte = littleEndian; family = "x86"; arch = "i686"; }; - x86_64 = { bits = 64; significantByte = littleEndian; family = "x86"; arch = "x86-64"; }; - - microblaze = { bits = 32; significantByte = bigEndian; family = "microblaze"; }; - microblazeel = { bits = 32; significantByte = littleEndian; family = "microblaze"; }; - - mips = { bits = 32; significantByte = bigEndian; family = "mips"; }; - mipsel = { bits = 32; significantByte = littleEndian; family = "mips"; }; - mips64 = { bits = 64; significantByte = bigEndian; family = "mips"; }; - mips64el = { bits = 64; significantByte = littleEndian; family = "mips"; }; - - mmix = { bits = 64; significantByte = bigEndian; family = "mmix"; }; - - m68k = { bits = 32; significantByte = bigEndian; family = "m68k"; }; - - powerpc = { bits = 32; significantByte = bigEndian; family = "power"; }; - powerpc64 = { bits = 64; significantByte = bigEndian; family = "power"; }; - powerpc64le = { bits = 64; significantByte = littleEndian; family = "power"; }; - powerpcle = { bits = 32; significantByte = littleEndian; family = "power"; }; - - riscv32 = { bits = 32; significantByte = littleEndian; family = "riscv"; }; - riscv64 = { bits = 64; significantByte = littleEndian; family = "riscv"; }; - - s390 = { bits = 32; significantByte = bigEndian; family = "s390"; }; - s390x = { bits = 64; significantByte = bigEndian; family = "s390"; }; - - sparc = { bits = 32; significantByte = bigEndian; family = "sparc"; }; - sparc64 = { bits = 64; significantByte = bigEndian; family = "sparc"; }; - - wasm32 = { bits = 32; significantByte = littleEndian; family = "wasm"; }; - wasm64 = { bits = 64; significantByte = littleEndian; family = "wasm"; }; - - alpha = { bits = 64; significantByte = littleEndian; family = "alpha"; }; - - rx = { bits = 32; significantByte = littleEndian; family = "rx"; }; - msp430 = { bits = 16; significantByte = littleEndian; family = "msp430"; }; - avr = { bits = 8; family = "avr"; }; - - vc4 = { bits = 32; significantByte = littleEndian; family = "vc4"; }; - - or1k = { bits = 32; significantByte = bigEndian; family = "or1k"; }; - - loongarch64 = { bits = 64; significantByte = littleEndian; family = "loongarch"; }; - - javascript = { bits = 32; significantByte = littleEndian; family = "javascript"; }; - }; + cpuTypes = + let + inherit (significantBytes) bigEndian littleEndian; + in + setTypes types.openCpuType { + arm = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + }; + armv5tel = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "5"; + arch = "armv5t"; + }; + armv6m = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "6"; + arch = "armv6-m"; + }; + armv6l = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "6"; + arch = "armv6"; + }; + armv7a = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "7"; + arch = "armv7-a"; + }; + armv7r = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "7"; + arch = "armv7-r"; + }; + armv7m = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "7"; + arch = "armv7-m"; + }; + armv7l = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "7"; + arch = "armv7"; + }; + armv8a = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "8"; + arch = "armv8-a"; + }; + armv8r = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "8"; + arch = "armv8-a"; + }; + armv8m = { + bits = 32; + significantByte = littleEndian; + family = "arm"; + version = "8"; + arch = "armv8-m"; + }; + aarch64 = { + bits = 64; + significantByte = littleEndian; + family = "arm"; + version = "8"; + arch = "armv8-a"; + }; + aarch64_be = { + bits = 64; + significantByte = bigEndian; + family = "arm"; + version = "8"; + arch = "armv8-a"; + }; + + i386 = { + bits = 32; + significantByte = littleEndian; + family = "x86"; + arch = "i386"; + }; + i486 = { + bits = 32; + significantByte = littleEndian; + family = "x86"; + arch = "i486"; + }; + i586 = { + bits = 32; + significantByte = littleEndian; + family = "x86"; + arch = "i586"; + }; + i686 = { + bits = 32; + significantByte = littleEndian; + family = "x86"; + arch = "i686"; + }; + x86_64 = { + bits = 64; + significantByte = littleEndian; + family = "x86"; + arch = "x86-64"; + }; + + microblaze = { + bits = 32; + significantByte = bigEndian; + family = "microblaze"; + }; + microblazeel = { + bits = 32; + significantByte = littleEndian; + family = "microblaze"; + }; + + mips = { + bits = 32; + significantByte = bigEndian; + family = "mips"; + }; + mipsel = { + bits = 32; + significantByte = littleEndian; + family = "mips"; + }; + mips64 = { + bits = 64; + significantByte = bigEndian; + family = "mips"; + }; + mips64el = { + bits = 64; + significantByte = littleEndian; + family = "mips"; + }; + + mmix = { + bits = 64; + significantByte = bigEndian; + family = "mmix"; + }; + + m68k = { + bits = 32; + significantByte = bigEndian; + family = "m68k"; + }; + + powerpc = { + bits = 32; + significantByte = bigEndian; + family = "power"; + }; + powerpc64 = { + bits = 64; + significantByte = bigEndian; + family = "power"; + }; + powerpc64le = { + bits = 64; + significantByte = littleEndian; + family = "power"; + }; + powerpcle = { + bits = 32; + significantByte = littleEndian; + family = "power"; + }; + + riscv32 = { + bits = 32; + significantByte = littleEndian; + family = "riscv"; + }; + riscv64 = { + bits = 64; + significantByte = littleEndian; + family = "riscv"; + }; + + s390 = { + bits = 32; + significantByte = bigEndian; + family = "s390"; + }; + s390x = { + bits = 64; + significantByte = bigEndian; + family = "s390"; + }; + + sparc = { + bits = 32; + significantByte = bigEndian; + family = "sparc"; + }; + sparc64 = { + bits = 64; + significantByte = bigEndian; + family = "sparc"; + }; + + wasm32 = { + bits = 32; + significantByte = littleEndian; + family = "wasm"; + }; + wasm64 = { + bits = 64; + significantByte = littleEndian; + family = "wasm"; + }; + + alpha = { + bits = 64; + significantByte = littleEndian; + family = "alpha"; + }; + + rx = { + bits = 32; + significantByte = littleEndian; + family = "rx"; + }; + msp430 = { + bits = 16; + significantByte = littleEndian; + family = "msp430"; + }; + avr = { + bits = 8; + family = "avr"; + }; + + vc4 = { + bits = 32; + significantByte = littleEndian; + family = "vc4"; + }; + + or1k = { + bits = 32; + significantByte = bigEndian; + family = "or1k"; + }; + + loongarch64 = { + bits = 64; + significantByte = littleEndian; + family = "loongarch"; + }; + + javascript = { + bits = 32; + significantByte = littleEndian; + family = "javascript"; + }; + }; # GNU build systems assume that older NetBSD architectures are using a.out. - gnuNetBSDDefaultExecFormat = cpu: - if (cpu.family == "arm" && cpu.bits == 32) || - (cpu.family == "sparc" && cpu.bits == 32) || - (cpu.family == "m68k" && cpu.bits == 32) || - (cpu.family == "x86" && cpu.bits == 32) - then execFormats.aout - else execFormats.elf; + gnuNetBSDDefaultExecFormat = + cpu: + if + (cpu.family == "arm" && cpu.bits == 32) + || (cpu.family == "sparc" && cpu.bits == 32) + || (cpu.family == "m68k" && cpu.bits == 32) + || (cpu.family == "x86" && cpu.bits == 32) + then + execFormats.aout + else + execFormats.elf; # Determine when two CPUs are compatible with each other. That is, # can code built for system B run on system A? For that to happen, @@ -197,56 +427,59 @@ rec { # Note: Since 22.11 the archs of a mode switching CPU are no longer considered # pairwise compatible. Mode switching implies that binaries built for A # and B respectively can't be executed at the same time. - isCompatible = with cpuTypes; a: b: any id [ - # x86 - (b == i386 && isCompatible a i486) - (b == i486 && isCompatible a i586) - (b == i586 && isCompatible a i686) + isCompatible = + with cpuTypes; + a: b: + any id [ + # x86 + (b == i386 && isCompatible a i486) + (b == i486 && isCompatible a i586) + (b == i586 && isCompatible a i686) - # XXX: Not true in some cases. Like in WSL mode. - (b == i686 && isCompatible a x86_64) + # XXX: Not true in some cases. Like in WSL mode. + (b == i686 && isCompatible a x86_64) - # ARMv4 - (b == arm && isCompatible a armv5tel) + # ARMv4 + (b == arm && isCompatible a armv5tel) - # ARMv5 - (b == armv5tel && isCompatible a armv6l) + # ARMv5 + (b == armv5tel && isCompatible a armv6l) - # ARMv6 - (b == armv6l && isCompatible a armv6m) - (b == armv6m && isCompatible a armv7l) + # ARMv6 + (b == armv6l && isCompatible a armv6m) + (b == armv6m && isCompatible a armv7l) - # ARMv7 - (b == armv7l && isCompatible a armv7a) - (b == armv7l && isCompatible a armv7r) - (b == armv7l && isCompatible a armv7m) + # ARMv7 + (b == armv7l && isCompatible a armv7a) + (b == armv7l && isCompatible a armv7r) + (b == armv7l && isCompatible a armv7m) - # ARMv8 - (b == aarch64 && a == armv8a) - (b == armv8a && isCompatible a aarch64) - (b == armv8r && isCompatible a armv8a) - (b == armv8m && isCompatible a armv8a) + # ARMv8 + (b == aarch64 && a == armv8a) + (b == armv8a && isCompatible a aarch64) + (b == armv8r && isCompatible a armv8a) + (b == armv8m && isCompatible a armv8a) - # PowerPC - (b == powerpc && isCompatible a powerpc64) - (b == powerpcle && isCompatible a powerpc64le) + # PowerPC + (b == powerpc && isCompatible a powerpc64) + (b == powerpcle && isCompatible a powerpc64le) - # MIPS - (b == mips && isCompatible a mips64) - (b == mipsel && isCompatible a mips64el) + # MIPS + (b == mips && isCompatible a mips64) + (b == mipsel && isCompatible a mips64el) - # RISCV - (b == riscv32 && isCompatible a riscv64) + # RISCV + (b == riscv32 && isCompatible a riscv64) - # SPARC - (b == sparc && isCompatible a sparc64) + # SPARC + (b == sparc && isCompatible a sparc64) - # WASM - (b == wasm32 && isCompatible a wasm64) + # WASM + (b == wasm32 && isCompatible a wasm64) - # identity - (b == a) - ]; + # identity + (b == a) + ]; ################################################################################ @@ -259,16 +492,16 @@ rec { types.vendor = enum (attrValues vendors); vendors = setTypes types.openVendor { - apple = {}; - pc = {}; - knuth = {}; + apple = { }; + pc = { }; + knuth = { }; # Actually matters, unlocking some MinGW-w64-specific options in GCC. See # bottom of https://sourceforge.net/p/mingw-w64/wiki2/Unicode%20apps/ - w64 = {}; + w64 = { }; - none = {}; - unknown = {}; + none = { }; + unknown = { }; }; ################################################################################ @@ -282,13 +515,13 @@ rec { types.execFormat = enum (attrValues execFormats); execFormats = setTypes types.openExecFormat { - aout = {}; # a.out - elf = {}; - macho = {}; - pe = {}; - wasm = {}; + aout = { }; # a.out + elf = { }; + macho = { }; + pe = { }; + wasm = { }; - unknown = {}; + unknown = { }; }; ################################################################################ @@ -302,8 +535,8 @@ rec { types.kernelFamily = enum (attrValues kernelFamilies); kernelFamilies = setTypes types.openKernelFamily { - bsd = {}; - darwin = {}; + bsd = { }; + darwin = { }; }; ################################################################################ @@ -312,39 +545,93 @@ rec { name = "kernel"; description = "kernel name and information"; merge = mergeOneOption; - check = x: types.execFormat.check x.execFormat - && all types.kernelFamily.check (attrValues x.families); + check = + x: types.execFormat.check x.execFormat && all types.kernelFamily.check (attrValues x.families); }; types.kernel = enum (attrValues kernels); - kernels = let - inherit (execFormats) elf pe wasm unknown macho; - inherit (kernelFamilies) bsd darwin; - in setTypes types.openKernel { - # TODO(@Ericson2314): Don't want to mass-rebuild yet to keeping 'darwin' as - # the normalized name for macOS. - macos = { execFormat = macho; families = { inherit darwin; }; name = "darwin"; }; - ios = { execFormat = macho; families = { inherit darwin; }; }; - freebsd = { execFormat = elf; families = { inherit bsd; }; name = "freebsd"; }; - linux = { execFormat = elf; families = { }; }; - netbsd = { execFormat = elf; families = { inherit bsd; }; }; - none = { execFormat = unknown; families = { }; }; - openbsd = { execFormat = elf; families = { inherit bsd; }; }; - solaris = { execFormat = elf; families = { }; }; - wasi = { execFormat = wasm; families = { }; }; - redox = { execFormat = elf; families = { }; }; - windows = { execFormat = pe; families = { }; }; - ghcjs = { execFormat = unknown; families = { }; }; - genode = { execFormat = elf; families = { }; }; - mmixware = { execFormat = unknown; families = { }; }; - } // { # aliases - # 'darwin' is the kernel for all of them. We choose macOS by default. - darwin = kernels.macos; - watchos = kernels.ios; - tvos = kernels.ios; - win32 = kernels.windows; - }; + kernels = + let + inherit (execFormats) + elf + pe + wasm + unknown + macho + ; + inherit (kernelFamilies) bsd darwin; + in + setTypes types.openKernel { + # TODO(@Ericson2314): Don't want to mass-rebuild yet to keeping 'darwin' as + # the normalized name for macOS. + macos = { + execFormat = macho; + families = { inherit darwin; }; + name = "darwin"; + }; + ios = { + execFormat = macho; + families = { inherit darwin; }; + }; + freebsd = { + execFormat = elf; + families = { inherit bsd; }; + name = "freebsd"; + }; + linux = { + execFormat = elf; + families = { }; + }; + netbsd = { + execFormat = elf; + families = { inherit bsd; }; + }; + none = { + execFormat = unknown; + families = { }; + }; + openbsd = { + execFormat = elf; + families = { inherit bsd; }; + }; + solaris = { + execFormat = elf; + families = { }; + }; + wasi = { + execFormat = wasm; + families = { }; + }; + redox = { + execFormat = elf; + families = { }; + }; + windows = { + execFormat = pe; + families = { }; + }; + ghcjs = { + execFormat = unknown; + families = { }; + }; + genode = { + execFormat = elf; + families = { }; + }; + mmixware = { + execFormat = unknown; + families = { }; + }; + } + // { + # aliases + # 'darwin' is the kernel for all of them. We choose macOS by default. + darwin = kernels.macos; + watchos = kernels.ios; + tvos = kernels.ios; + win32 = kernels.windows; + }; ################################################################################ @@ -357,22 +644,27 @@ rec { types.abi = enum (attrValues abis); abis = setTypes types.openAbi { - cygnus = {}; - msvc = {}; + cygnus = { }; + msvc = { }; # Note: eabi is specific to ARM and PowerPC. # On PowerPC, this corresponds to PPCEABI. # On ARM, this corresponds to ARMEABI. - eabi = { float = "soft"; }; - eabihf = { float = "hard"; }; + eabi = { + float = "soft"; + }; + eabihf = { + float = "hard"; + }; # Other architectures should use ELF in embedded situations. - elf = {}; + elf = { }; - androideabi = {}; - android = { + androideabi = { }; + android = { assertions = [ - { assertion = platform: !platform.isAarch32; + { + assertion = platform: !platform.isAarch32; message = '' The "android" ABI is not for 32-bit ARM. Use "androideabi" instead. ''; @@ -380,46 +672,72 @@ rec { ]; }; - gnueabi = { float = "soft"; }; - gnueabihf = { float = "hard"; }; - gnu = { + gnueabi = { + float = "soft"; + }; + gnueabihf = { + float = "hard"; + }; + gnu = { assertions = [ - { assertion = platform: !platform.isAarch32; + { + assertion = platform: !platform.isAarch32; message = '' The "gnu" ABI is ambiguous on 32-bit ARM. Use "gnueabi" or "gnueabihf" instead. ''; } - { assertion = platform: !(platform.isPower64 && platform.isBigEndian); + { + assertion = platform: !(platform.isPower64 && platform.isBigEndian); message = '' The "gnu" ABI is ambiguous on big-endian 64-bit PowerPC. Use "gnuabielfv2" or "gnuabielfv1" instead. ''; } ]; }; - gnuabi64 = { abi = "64"; }; - muslabi64 = { abi = "64"; }; + gnuabi64 = { + abi = "64"; + }; + muslabi64 = { + abi = "64"; + }; # NOTE: abi=n32 requires a 64-bit MIPS chip! That is not a typo. # It is basically the 64-bit abi with 32-bit pointers. Details: # https://www.linux-mips.org/pub/linux/mips/doc/ABI/MIPS-N32-ABI-Handbook.pdf - gnuabin32 = { abi = "n32"; }; - muslabin32 = { abi = "n32"; }; + gnuabin32 = { + abi = "n32"; + }; + muslabin32 = { + abi = "n32"; + }; - gnuabielfv2 = { abi = "elfv2"; }; - gnuabielfv1 = { abi = "elfv1"; }; + gnuabielfv2 = { + abi = "elfv2"; + }; + gnuabielfv1 = { + abi = "elfv1"; + }; - musleabi = { float = "soft"; }; - musleabihf = { float = "hard"; }; - musl = {}; + musleabi = { + float = "soft"; + }; + musleabihf = { + float = "hard"; + }; + musl = { }; - uclibceabi = { float = "soft"; }; - uclibceabihf = { float = "hard"; }; - uclibc = {}; + uclibceabi = { + float = "soft"; + }; + uclibceabihf = { + float = "hard"; + }; + uclibc = { }; # LLVM libc - llvm = {}; + llvm = { }; - unknown = {}; + unknown = { }; }; ################################################################################ @@ -428,121 +746,211 @@ rec { name = "system"; description = "fully parsed representation of llvm- or nix-style platform tuple"; merge = mergeOneOption; - check = { cpu, vendor, kernel, abi }: - types.cpuType.check cpu - && types.vendor.check vendor - && types.kernel.check kernel - && types.abi.check abi; + check = + { + cpu, + vendor, + kernel, + abi, + }: + types.cpuType.check cpu + && types.vendor.check vendor + && types.kernel.check kernel + && types.abi.check abi; }; isSystem = isType "system"; - mkSystem = components: + mkSystem = + components: assert types.parsedPlatform.check components; setType "system" components; - mkSkeletonFromList = l: { - "1" = if elemAt l 0 == "avr" - then { cpu = elemAt l 0; kernel = "none"; abi = "unknown"; } - else throw "system string '${lib.concatStringsSep "-" l}' with 1 component is ambiguous"; - "2" = # We only do 2-part hacks for things Nix already supports - if elemAt l 1 == "cygwin" - then { cpu = elemAt l 0; kernel = "windows"; abi = "cygnus"; } - # MSVC ought to be the default ABI so this case isn't needed. But then it - # becomes difficult to handle the gnu* variants for Aarch32 correctly for - # minGW. So it's easier to make gnu* the default for the MinGW, but - # hack-in MSVC for the non-MinGW case right here. - else if elemAt l 1 == "windows" - then { cpu = elemAt l 0; kernel = "windows"; abi = "msvc"; } - else if (elemAt l 1) == "elf" - then { cpu = elemAt l 0; vendor = "unknown"; kernel = "none"; abi = elemAt l 1; } - else { cpu = elemAt l 0; kernel = elemAt l 1; }; - "3" = - # cpu-kernel-environment - if elemAt l 1 == "linux" || - elem (elemAt l 2) ["eabi" "eabihf" "elf" "gnu"] - then { - cpu = elemAt l 0; - kernel = elemAt l 1; - abi = elemAt l 2; - vendor = "unknown"; - } - # cpu-vendor-os - else if elemAt l 1 == "apple" || - elem (elemAt l 2) [ "redox" "mmixware" "ghcjs" "mingw32" ] || - hasPrefix "freebsd" (elemAt l 2) || - hasPrefix "netbsd" (elemAt l 2) || - hasPrefix "openbsd" (elemAt l 2) || - hasPrefix "genode" (elemAt l 2) || - hasPrefix "wasm32" (elemAt l 0) - then { - cpu = elemAt l 0; + mkSkeletonFromList = + l: + { + "1" = + if elemAt l 0 == "avr" then + { + cpu = elemAt l 0; + kernel = "none"; + abi = "unknown"; + } + else + throw "system string '${lib.concatStringsSep "-" l}' with 1 component is ambiguous"; + "2" = # We only do 2-part hacks for things Nix already supports + if elemAt l 1 == "cygwin" then + { + cpu = elemAt l 0; + kernel = "windows"; + abi = "cygnus"; + } + # MSVC ought to be the default ABI so this case isn't needed. But then it + # becomes difficult to handle the gnu* variants for Aarch32 correctly for + # minGW. So it's easier to make gnu* the default for the MinGW, but + # hack-in MSVC for the non-MinGW case right here. + else if elemAt l 1 == "windows" then + { + cpu = elemAt l 0; + kernel = "windows"; + abi = "msvc"; + } + else if (elemAt l 1) == "elf" then + { + cpu = elemAt l 0; + vendor = "unknown"; + kernel = "none"; + abi = elemAt l 1; + } + else + { + cpu = elemAt l 0; + kernel = elemAt l 1; + }; + "3" = + # cpu-kernel-environment + if + elemAt l 1 == "linux" + || elem (elemAt l 2) [ + "eabi" + "eabihf" + "elf" + "gnu" + ] + then + { + cpu = elemAt l 0; + kernel = elemAt l 1; + abi = elemAt l 2; + vendor = "unknown"; + } + # cpu-vendor-os + else if + elemAt l 1 == "apple" + || elem (elemAt l 2) [ + "redox" + "mmixware" + "ghcjs" + "mingw32" + ] + || hasPrefix "freebsd" (elemAt l 2) + || hasPrefix "netbsd" (elemAt l 2) + || hasPrefix "openbsd" (elemAt l 2) + || hasPrefix "genode" (elemAt l 2) + || hasPrefix "wasm32" (elemAt l 0) + then + { + cpu = elemAt l 0; + vendor = elemAt l 1; + kernel = + if elemAt l 2 == "mingw32" then + "windows" # autotools breaks on -gnu for window + else + elemAt l 2; + } + else + throw "system string '${lib.concatStringsSep "-" l}' with 3 components is ambiguous"; + "4" = { + cpu = elemAt l 0; vendor = elemAt l 1; - kernel = if elemAt l 2 == "mingw32" - then "windows" # autotools breaks on -gnu for window - else elemAt l 2; - } - else throw "system string '${lib.concatStringsSep "-" l}' with 3 components is ambiguous"; - "4" = { cpu = elemAt l 0; vendor = elemAt l 1; kernel = elemAt l 2; abi = elemAt l 3; }; - }.${toString (length l)} + kernel = elemAt l 2; + abi = elemAt l 3; + }; + } + .${toString (length l)} or (throw "system string '${lib.concatStringsSep "-" l}' has invalid number of hyphen-separated components"); # This should revert the job done by config.guess from the gcc compiler. - mkSystemFromSkeleton = { cpu - , # Optional, but fallback too complex for here. - # Inferred below instead. - vendor ? assert false; null - , kernel - , # Also inferred below - abi ? assert false; null - } @ args: let - getCpu = name: cpuTypes.${name} or (throw "Unknown CPU type: ${name}"); - getVendor = name: vendors.${name} or (throw "Unknown vendor: ${name}"); - getKernel = name: kernels.${name} or (throw "Unknown kernel: ${name}"); - getAbi = name: abis.${name} or (throw "Unknown ABI: ${name}"); - - parsed = { - cpu = getCpu args.cpu; - vendor = - /**/ if args ? vendor then getVendor args.vendor - else if isDarwin parsed then vendors.apple - else if isWindows parsed then vendors.pc - else vendors.unknown; - kernel = if hasPrefix "darwin" args.kernel then getKernel "darwin" - else if hasPrefix "netbsd" args.kernel then getKernel "netbsd" - else getKernel (removeAbiSuffix args.kernel); - abi = - /**/ if args ? abi then getAbi args.abi - else if isLinux parsed || isWindows parsed then - if isAarch32 parsed then - if versionAtLeast (parsed.cpu.version or "0") "6" - then abis.gnueabihf - else abis.gnueabi - # Default ppc64 BE to ELFv2 - else if isPower64 parsed && isBigEndian parsed then abis.gnuabielfv2 - else abis.gnu - else abis.unknown; - }; - - in mkSystem parsed; + mkSystemFromSkeleton = + { + cpu, + # Optional, but fallback too complex for here. + # Inferred below instead. + vendor ? + assert false; + null, + kernel, + # Also inferred below + abi ? + assert false; + null, + }@args: + let + getCpu = name: cpuTypes.${name} or (throw "Unknown CPU type: ${name}"); + getVendor = name: vendors.${name} or (throw "Unknown vendor: ${name}"); + getKernel = name: kernels.${name} or (throw "Unknown kernel: ${name}"); + getAbi = name: abis.${name} or (throw "Unknown ABI: ${name}"); + + parsed = { + cpu = getCpu args.cpu; + vendor = + if args ? vendor then + getVendor args.vendor + else if isDarwin parsed then + vendors.apple + else if isWindows parsed then + vendors.pc + else + vendors.unknown; + kernel = + if hasPrefix "darwin" args.kernel then + getKernel "darwin" + else if hasPrefix "netbsd" args.kernel then + getKernel "netbsd" + else + getKernel (removeAbiSuffix args.kernel); + abi = + if args ? abi then + getAbi args.abi + else if isLinux parsed || isWindows parsed then + if isAarch32 parsed then + if versionAtLeast (parsed.cpu.version or "0") "6" then abis.gnueabihf else abis.gnueabi + # Default ppc64 BE to ELFv2 + else if isPower64 parsed && isBigEndian parsed then + abis.gnuabielfv2 + else + abis.gnu + else + abis.unknown; + }; + + in + mkSystem parsed; mkSystemFromString = s: mkSystemFromSkeleton (mkSkeletonFromList (splitString "-" s)); - kernelName = kernel: - kernel.name + toString (kernel.version or ""); - - doubleFromSystem = { cpu, kernel, abi, ... }: - /**/ if abi == abis.cygnus then "${cpu.name}-cygwin" - else if kernel.families ? darwin then "${cpu.name}-darwin" - else "${cpu.name}-${kernelName kernel}"; - - tripleFromSystem = { cpu, vendor, kernel, abi, ... } @ sys: assert isSystem sys; let - optExecFormat = - optionalString (kernel.name == "netbsd" && - gnuNetBSDDefaultExecFormat cpu != kernel.execFormat) - kernel.execFormat.name; - optAbi = optionalString (abi != abis.unknown) "-${abi.name}"; - in "${cpu.name}-${vendor.name}-${kernelName kernel}${optExecFormat}${optAbi}"; + kernelName = kernel: kernel.name + toString (kernel.version or ""); + + doubleFromSystem = + { + cpu, + kernel, + abi, + ... + }: + if abi == abis.cygnus then + "${cpu.name}-cygwin" + else if kernel.families ? darwin then + "${cpu.name}-darwin" + else + "${cpu.name}-${kernelName kernel}"; + + tripleFromSystem = + { + cpu, + vendor, + kernel, + abi, + ... + }@sys: + assert isSystem sys; + let + optExecFormat = optionalString ( + kernel.name == "netbsd" && gnuNetBSDDefaultExecFormat cpu != kernel.execFormat + ) kernel.execFormat.name; + optAbi = optionalString (abi != abis.unknown) "-${abi.name}"; + in + "${cpu.name}-${vendor.name}-${kernelName kernel}${optExecFormat}${optAbi}"; ################################################################################ diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index b8ae864eeb21..f5f1fb5e7c2d 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -119,61 +119,118 @@ let testingThrow = expr: { expr = (builtins.tryEval (builtins.seq expr "didn't throw")); - expected = { success = false; value = false; }; + expected = { + success = false; + value = false; + }; }; testingEval = expr: { expr = (builtins.tryEval expr).success; expected = true; }; - testSanitizeDerivationName = { name, expected }: - let - drv = derivation { - name = strings.sanitizeDerivationName name; - builder = "x"; - system = "x"; + testSanitizeDerivationName = + { name, expected }: + let + drv = derivation { + name = strings.sanitizeDerivationName name; + builder = "x"; + system = "x"; + }; + in + { + # Evaluate the derivation so an invalid name would be caught + expr = builtins.seq drv.drvPath drv.name; + inherit expected; }; - in { - # Evaluate the derivation so an invalid name would be caught - expr = builtins.seq drv.drvPath drv.name; - inherit expected; - }; in runTests { -# CUSTOMIZATION + # CUSTOMIZATION testFunctionArgsMakeOverridable = { - expr = functionArgs (makeOverridable ({ a, b, c ? null}: {})); - expected = { a = false; b = false; c = true; }; + expr = functionArgs ( + makeOverridable ( + { + a, + b, + c ? null, + }: + { } + ) + ); + expected = { + a = false; + b = false; + c = true; + }; }; testFunctionArgsMakeOverridableOverride = { - expr = functionArgs (makeOverridable ({ a, b, c ? null }: {}) { a = 1; b = 2; }).override; - expected = { a = false; b = false; c = true; }; + expr = + functionArgs + (makeOverridable + ( + { + a, + b, + c ? null, + }: + { } + ) + { + a = 1; + b = 2; + } + ).override; + expected = { + a = false; + b = false; + c = true; + }; }; testCallPackageWithOverridePreservesArguments = let - f = { a ? 0, b }: {}; - f' = callPackageWith { a = 1; b = 2; } f {}; - in { + f = + { + a ? 0, + b, + }: + { }; + f' = callPackageWith { + a = 1; + b = 2; + } f { }; + in + { expr = functionArgs f'.override; expected = functionArgs f; }; testCallPackagesWithOverridePreservesArguments = let - f = { a ? 0, b }: { nested = {}; }; - f' = callPackagesWith { a = 1; b = 2; } f {}; - in { + f = + { + a ? 0, + b, + }: + { + nested = { }; + }; + f' = callPackagesWith { + a = 1; + b = 2; + } f { }; + in + { expr = functionArgs f'.nested.override; expected = functionArgs f; }; -# TRIVIAL + # TRIVIAL testId = { expr = id 1; @@ -194,16 +251,19 @@ runTests { }; testPipeEmpty = { - expr = pipe 2 []; + expr = pipe 2 [ ]; expected = 2; }; testPipeStrings = { - expr = pipe [ 3 4 ] [ - (map toString) - (map (s: s + "\n")) - concatStrings - ]; + expr = + pipe + [ 3 4 ] + [ + (map toString) + (map (s: s + "\n")) + concatStrings + ]; expected = '' 3 4 @@ -238,34 +298,61 @@ runTests { }; testComposeExtensions = { - expr = let obj = makeExtensible (self: { foo = self.bar; }); - f = self: super: { bar = false; baz = true; }; - g = self: super: { bar = super.baz or false; }; - f_o_g = composeExtensions f g; - composed = obj.extend f_o_g; - in composed.foo; + expr = + let + obj = makeExtensible (self: { + foo = self.bar; + }); + f = self: super: { + bar = false; + baz = true; + }; + g = self: super: { bar = super.baz or false; }; + f_o_g = composeExtensions f g; + composed = obj.extend f_o_g; + in + composed.foo; expected = true; }; testComposeManyExtensions0 = { - expr = let obj = makeExtensible (self: { foo = true; }); - emptyComposition = composeManyExtensions []; - composed = obj.extend emptyComposition; - in composed.foo; + expr = + let + obj = makeExtensible (self: { + foo = true; + }); + emptyComposition = composeManyExtensions [ ]; + composed = obj.extend emptyComposition; + in + composed.foo; expected = true; }; testComposeManyExtensions = - let f = self: super: { bar = false; baz = true; }; - g = self: super: { bar = super.baz or false; }; - h = self: super: { qux = super.bar or false; }; - obj = makeExtensible (self: { foo = self.qux; }); - in { - expr = let composition = composeManyExtensions [f g h]; - composed = obj.extend composition; - in composed.foo; - expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo; - }; + let + f = self: super: { + bar = false; + baz = true; + }; + g = self: super: { bar = super.baz or false; }; + h = self: super: { qux = super.bar or false; }; + obj = makeExtensible (self: { + foo = self.qux; + }); + in + { + expr = + let + composition = composeManyExtensions [ + f + g + h + ]; + composed = obj.extend composition; + in + composed.foo; + expected = (obj.extend (composeExtensions f (composeExtensions g h))).foo; + }; testBitAnd = { expr = (bitAnd 3 10); @@ -304,52 +391,89 @@ runTests { testToBaseDigits = { expr = toBaseDigits 2 6; - expected = [ 1 1 0 ]; + expected = [ + 1 + 1 + 0 + ]; }; testFunctionArgsFunctor = { expr = functionArgs { __functor = self: { a, b }: null; }; - expected = { a = false; b = false; }; + expected = { + a = false; + b = false; + }; }; testFunctionArgsSetFunctionArgs = { expr = functionArgs (setFunctionArgs (args: args.x) { x = false; }); - expected = { x = false; }; + expected = { + x = false; + }; }; -# STRINGS + # STRINGS testConcatMapStrings = { - expr = concatMapStrings (x: x + ";") ["a" "b" "c"]; + expr = concatMapStrings (x: x + ";") [ + "a" + "b" + "c" + ]; expected = "a;b;c;"; }; testConcatStringsSep = { - expr = concatStringsSep "," ["a" "b" "c"]; + expr = concatStringsSep "," [ + "a" + "b" + "c" + ]; expected = "a,b,c"; }; testConcatMapAttrsStringSepExamples = { - expr = concatMapAttrsStringSep "\n" (name: value: "${name}: foo-${value}") { a = "0.1.0"; b = "0.2.0"; }; + expr = concatMapAttrsStringSep "\n" (name: value: "${name}: foo-${value}") { + a = "0.1.0"; + b = "0.2.0"; + }; expected = "a: foo-0.1.0\nb: foo-0.2.0"; }; testConcatLines = { - expr = concatLines ["a" "b" "c"]; + expr = concatLines [ + "a" + "b" + "c" + ]; expected = "a\nb\nc\n"; }; testMakeIncludePathWithPkgs = { - expr = (makeIncludePath [ - # makeIncludePath preferably selects the "dev" output - { dev.outPath = "/dev"; out.outPath = "/out"; outPath = "/default"; } - # "out" is used if "dev" is not found - { out.outPath = "/out"; outPath = "/default"; } - # And it returns the derivation directly if there's no "out" either - { outPath = "/default"; } - # Same if the output is specified explicitly, even if there's a "dev" - { dev.outPath = "/dev"; outPath = "/default"; outputSpecified = true; } - ]); + expr = ( + makeIncludePath [ + # makeIncludePath preferably selects the "dev" output + { + dev.outPath = "/dev"; + out.outPath = "/out"; + outPath = "/default"; + } + # "out" is used if "dev" is not found + { + out.outPath = "/out"; + outPath = "/default"; + } + # And it returns the derivation directly if there's no "out" either + { outPath = "/default"; } + # Same if the output is specified explicitly, even if there's a "dev" + { + dev.outPath = "/dev"; + outPath = "/default"; + outputSpecified = true; + } + ] + ); expected = "/dev/include:/out/include:/default/include:/default/include"; }; @@ -364,7 +488,12 @@ runTests { }; testMakeIncludePathWithManyString = { - expr = (makeIncludePath [ "/usr" "/usr/local" ]); + expr = ( + makeIncludePath [ + "/usr" + "/usr/local" + ] + ); expected = "/usr/include:/usr/local/include"; }; @@ -376,21 +505,23 @@ runTests { # Test various strings are trimmed correctly testTrimString = { expr = - let - testValues = f: mapAttrs (_: f) { - empty = ""; - cr = "\r"; - lf = "\n"; - tab = "\t"; - spaces = " "; - leading = " Hello, world"; - trailing = "Hello, world "; - mixed = " Hello, world "; - mixed-tabs = " \t\tHello, world \t \t "; - multiline = " Hello,\n world! "; - multiline-crlf = " Hello,\r\n world! "; - }; - in + let + testValues = + f: + mapAttrs (_: f) { + empty = ""; + cr = "\r"; + lf = "\n"; + tab = "\t"; + spaces = " "; + leading = " Hello, world"; + trailing = "Hello, world "; + mixed = " Hello, world "; + mixed-tabs = " \t\tHello, world \t \t "; + multiline = " Hello,\n world! "; + multiline-crlf = " Hello,\r\n world! "; + }; + in { leading = testValues (strings.trimWith { start = true; }); trailing = testValues (strings.trimWith { end = true; }); @@ -441,12 +572,21 @@ runTests { testSplitStringsSimple = { expr = strings.splitString "." "a.b.c.d"; - expected = [ "a" "b" "c" "d" ]; + expected = [ + "a" + "b" + "c" + "d" + ]; }; testSplitStringsEmpty = { expr = strings.splitString "." "a..b"; - expected = [ "a" "" "b" ]; + expected = [ + "a" + "" + "b" + ]; }; testSplitStringsOne = { @@ -461,17 +601,34 @@ runTests { testSplitStringsFirstEmpty = { expr = strings.splitString "/" "/a/b/c"; - expected = [ "" "a" "b" "c" ]; + expected = [ + "" + "a" + "b" + "c" + ]; }; testSplitStringsLastEmpty = { expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:"; - expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ]; + expected = [ + "2001" + "db8" + "0" + "0042" + "" + "8a2e" + "370" + "" + ]; }; testSplitStringsRegex = { expr = strings.splitString "\\[{}]()^$?*+|." "A\\[{}]()^$?*+|.B"; - expected = [ "A" "B" ]; + expected = [ + "A" + "B" + ]; }; testEscapeShellArg = { @@ -485,7 +642,11 @@ runTests { }; testEscapeShellArgs = { - expr = strings.escapeShellArgs ["one" "two three" "four'five"]; + expr = strings.escapeShellArgs [ + "one" + "two three" + "four'five" + ]; expected = "one 'two three' 'four'\\''five'"; }; @@ -495,12 +656,18 @@ runTests { }; testSplitStringsDerivation = { - expr = take 3 (strings.splitString "/" (derivation { - name = "name"; - builder = "builder"; - system = "system"; - })); - expected = ["" "nix" "store"]; + expr = take 3 ( + strings.splitString "/" (derivation { + name = "name"; + builder = "builder"; + system = "system"; + }) + ); + expected = [ + "" + "nix" + "store" + ]; }; testSplitVersionSingle = { @@ -510,12 +677,19 @@ runTests { testSplitVersionDouble = { expr = versions.splitVersion "1.2"; - expected = [ "1" "2" ]; + expected = [ + "1" + "2" + ]; }; testSplitVersionTriple = { expr = versions.splitVersion "1.2.3"; - expected = [ "1" "2" "3" ]; + expected = [ + "1" + "2" + "3" + ]; }; testPadVersionLess = { @@ -533,28 +707,27 @@ runTests { expected = "1.2.3"; }; - testIsStorePath = { + testIsStorePath = { expr = - let goodPath = - "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"; - goodCAPath = "/1121rp0gvr1qya7hvy925g5kjwg66acz6sn1ra1hca09f1z5dsab"; - in { + let + goodPath = "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"; + goodCAPath = "/1121rp0gvr1qya7hvy925g5kjwg66acz6sn1ra1hca09f1z5dsab"; + in + { storePath = isStorePath goodPath; storePathDerivation = isStorePath (import ../.. { system = "x86_64-linux"; }).hello; - storePathAppendix = isStorePath - "${goodPath}/bin/python"; + storePathAppendix = isStorePath "${goodPath}/bin/python"; nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath))); asPath = isStorePath (/. + goodPath); otherPath = isStorePath "/something/else"; caPath = isStorePath goodCAPath; - caPathAppendix = isStorePath - "${goodCAPath}/bin/python"; + caPathAppendix = isStorePath "${goodCAPath}/bin/python"; caAsPath = isStorePath (/. + goodCAPath); otherVals = { - attrset = isStorePath {}; - list = isStorePath []; + attrset = isStorePath { }; + list = isStorePath [ ]; int = isStorePath 42; }; }; @@ -585,7 +758,10 @@ runTests { expr = '' ${toShellVars { STRing01 = "just a 'string'"; - _array_ = [ "with" "more strings" ]; + _array_ = [ + "with" + "more strings" + ]; assoc."with some" = '' strings possibly newlines @@ -685,7 +861,10 @@ runTests { ("Hello%20World" == strings.escapeURL "Hello World") ("Hello%2FWorld" == strings.escapeURL "Hello/World") ("42%25" == strings.escapeURL "42%") - ("%20%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%09%3A%2F%40%24%27%28%29%2A%2C%3B" == strings.escapeURL " ?&=#+%!<>#\"{}|\\^[]`\t:/@$'()*,;") + ( + "%20%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%09%3A%2F%40%24%27%28%29%2A%2C%3B" + == strings.escapeURL " ?&=#+%!<>#\"{}|\\^[]`\t:/@$'()*,;" + ) ]; testToSentenceCase = { @@ -693,9 +872,7 @@ runTests { expected = "Hello world"; }; - testToSentenceCasePath = testingThrow ( - strings.toSentenceCase ./. - ); + testToSentenceCasePath = testingThrow (strings.toSentenceCase ./.); testToInt = testAllTrue [ # Naive @@ -714,19 +891,84 @@ runTests { ]; testToIntFails = testAllTrue [ - ( builtins.tryEval (toInt "") == { success = false; value = false; } ) - ( builtins.tryEval (toInt "123 123") == { success = false; value = false; } ) - ( builtins.tryEval (toInt "0 123") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt "00") == { success = false; value = false; } ) - ( builtins.tryEval (toInt "01") == { success = false; value = false; } ) - ( builtins.tryEval (toInt "002") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " foo ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } ) - ( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } ) + ( + builtins.tryEval (toInt "") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt "123 123") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt "0 123") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " 0d ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " 1d ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " d0 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt "00") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt "01") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt "002") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " 002 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " foo ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " foo 123 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toInt " foo123 ") == { + success = false; + value = false; + } + ) ]; testToIntBase10 = testAllTrue [ @@ -760,47 +1002,147 @@ runTests { ]; testToIntBase10Fails = testAllTrue [ - ( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } ) - ( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } ) + ( + builtins.tryEval (toIntBase10 "") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 "123 123") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 "0 123") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " 0d ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " 1d ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " d0 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " foo ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " foo 123 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " foo 00123 ") == { + success = false; + value = false; + } + ) + ( + builtins.tryEval (toIntBase10 " foo00123 ") == { + success = false; + value = false; + } + ) ]; -# LISTS + # LISTS testFilter = { - expr = filter (x: x != "a") ["a" "b" "c" "a"]; - expected = ["b" "c"]; + expr = filter (x: x != "a") [ + "a" + "b" + "c" + "a" + ]; + expected = [ + "b" + "c" + ]; }; testIfilter0Example = { - expr = ifilter0 (i: v: i == 0 || v > 2) [ 1 2 3 ]; - expected = [ 1 3 ]; + expr = ifilter0 (i: v: i == 0 || v > 2) [ + 1 + 2 + 3 + ]; + expected = [ + 1 + 3 + ]; }; testIfilter0Empty = { expr = ifilter0 (i: v: abort "shouldn't be evaluated!") [ ]; expected = [ ]; }; testIfilter0IndexOnly = { - expr = length (ifilter0 (i: v: mod i 2 == 0) [ (throw "0") (throw "1") (throw "2") (throw "3")]); + expr = length ( + ifilter0 (i: v: mod i 2 == 0) [ + (throw "0") + (throw "1") + (throw "2") + (throw "3") + ] + ); expected = 2; }; testIfilter0All = { - expr = ifilter0 (i: v: true) [ 10 11 12 13 14 15 ]; - expected = [ 10 11 12 13 14 15 ]; + expr = ifilter0 (i: v: true) [ + 10 + 11 + 12 + 13 + 14 + 15 + ]; + expected = [ + 10 + 11 + 12 + 13 + 14 + 15 + ]; }; testIfilter0First = { - expr = ifilter0 (i: v: i == 0) [ 10 11 12 13 14 15 ]; + expr = ifilter0 (i: v: i == 0) [ + 10 + 11 + 12 + 13 + 14 + 15 + ]; expected = [ 10 ]; }; testIfilter0Last = { - expr = ifilter0 (i: v: i == 5) [ 10 11 12 13 14 15 ]; + expr = ifilter0 (i: v: i == 5) [ + 10 + 11 + 12 + 13 + 14 + 15 + ]; expected = [ 15 ]; }; @@ -811,7 +1153,8 @@ runTests { assoc = f builtins.add; # fold with non-associative operator nonAssoc = f builtins.sub; - in { + in + { expr = { assocRight = assoc foldr; # right fold with assoc operator is same as left fold @@ -839,22 +1182,27 @@ runTests { }; testFoldl'IntegerAdding = { - expr = foldl' (acc: el: acc + el) 0 [ 1 2 3 ]; + expr = foldl' (acc: el: acc + el) 0 [ + 1 + 2 + 3 + ]; expected = 6; }; # The accumulator isn't forced deeply testFoldl'NonDeep = { - expr = take 3 (foldl' - (acc: el: [ el ] ++ acc) - [ (abort "unevaluated list entry") ] - [ 1 2 3 ]); - expected = [ 3 2 1 ]; + expr = take 3 (foldl' (acc: el: [ el ] ++ acc) [ (abort "unevaluated list entry") ] [ 1 2 3 ]); + expected = [ + 3 + 2 + 1 + ]; }; # Compared to builtins.foldl', lib.foldl' evaluates the first accumulator strictly too testFoldl'StrictInitial = { - expr = (builtins.tryEval (foldl' (acc: el: el) (throw "hello") [])).success; + expr = (builtins.tryEval (foldl' (acc: el: el) (throw "hello") [ ])).success; expected = false; }; @@ -866,36 +1214,169 @@ runTests { }; testTake = testAllTrue [ - ([] == (take 0 [ 1 2 3 ])) - ([1] == (take 1 [ 1 2 3 ])) - ([ 1 2 ] == (take 2 [ 1 2 3 ])) - ([ 1 2 3 ] == (take 3 [ 1 2 3 ])) - ([ 1 2 3 ] == (take 4 [ 1 2 3 ])) + ( + [ ] == (take 0 [ + 1 + 2 + 3 + ]) + ) + ( + [ 1 ] == (take 1 [ + 1 + 2 + 3 + ]) + ) + ( + [ + 1 + 2 + ] == (take 2 [ + 1 + 2 + 3 + ]) + ) + ( + [ + 1 + 2 + 3 + ] == (take 3 [ + 1 + 2 + 3 + ]) + ) + ( + [ + 1 + 2 + 3 + ] == (take 4 [ + 1 + 2 + 3 + ]) + ) ]; - testDrop = let inherit (lib) drop; in testAllTrue [ - # list index -1 is out of bounds - # ([ 1 2 3 ] == (drop (-1) [ 1 2 3 ])) - (drop 0 [ 1 2 3 ] == [ 1 2 3 ]) - (drop 1 [ 1 2 3 ] == [ 2 3 ]) - (drop 2 [ 1 2 3 ] == [ 3 ]) - (drop 3 [ 1 2 3 ] == [ ]) - (drop 4 [ 1 2 3 ] == [ ]) - (drop 0 [ ] == [ ]) - (drop 1 [ ] == [ ]) - ]; + testDrop = + let + inherit (lib) drop; + in + testAllTrue [ + # list index -1 is out of bounds + # ([ 1 2 3 ] == (drop (-1) [ 1 2 3 ])) + ( + drop 0 [ + 1 + 2 + 3 + ] == [ + 1 + 2 + 3 + ] + ) + ( + drop 1 [ + 1 + 2 + 3 + ] == [ + 2 + 3 + ] + ) + ( + drop 2 [ + 1 + 2 + 3 + ] == [ 3 ] + ) + ( + drop 3 [ + 1 + 2 + 3 + ] == [ ] + ) + ( + drop 4 [ + 1 + 2 + 3 + ] == [ ] + ) + (drop 0 [ ] == [ ]) + (drop 1 [ ] == [ ]) + ]; - testDropEnd = let inherit (lib) dropEnd; in testAllTrue [ - (dropEnd 0 [ 1 2 3 ] == [ 1 2 3 ]) - (dropEnd 1 [ 1 2 3 ] == [ 1 2 ]) - (dropEnd 2 [ 1 2 3 ] == [ 1 ]) - (dropEnd 3 [ 1 2 3 ] == [ ]) - (dropEnd 4 [ 1 2 3 ] == [ ]) - (dropEnd 0 [ ] == [ ]) - (dropEnd 1 [ ] == [ ]) - (dropEnd (-1) [ 1 2 3 ] == [ 1 2 3 ]) - (dropEnd (-1) [ ] == [ ]) - ]; + testDropEnd = + let + inherit (lib) dropEnd; + in + testAllTrue [ + ( + dropEnd 0 [ + 1 + 2 + 3 + ] == [ + 1 + 2 + 3 + ] + ) + ( + dropEnd 1 [ + 1 + 2 + 3 + ] == [ + 1 + 2 + ] + ) + ( + dropEnd 2 [ + 1 + 2 + 3 + ] == [ 1 ] + ) + ( + dropEnd 3 [ + 1 + 2 + 3 + ] == [ ] + ) + ( + dropEnd 4 [ + 1 + 2 + 3 + ] == [ ] + ) + (dropEnd 0 [ ] == [ ]) + (dropEnd 1 [ ] == [ ]) + ( + dropEnd (-1) [ + 1 + 2 + 3 + ] == [ + 1 + 2 + 3 + ] + ) + (dropEnd (-1) [ ] == [ ]) + ]; testListHasPrefixExample1 = { expr = lists.hasPrefix [ 1 2 ] [ 1 2 3 4 ]; @@ -920,7 +1401,10 @@ runTests { testListRemovePrefixExample1 = { expr = lists.removePrefix [ 1 2 ] [ 1 2 3 4 ]; - expected = [ 3 4 ]; + expected = [ + 3 + 4 + ]; }; testListRemovePrefixExample2 = { expr = (builtins.tryEval (lists.removePrefix [ 0 1 ] [ 1 2 3 4 ])).success; @@ -928,7 +1412,10 @@ runTests { }; testListRemovePrefixEmptyPrefix = { expr = lists.removePrefix [ ] [ 1 2 ]; - expected = [ 1 2 ]; + expected = [ + 1 + 2 + ]; }; testListRemovePrefixEmptyList = { expr = (builtins.tryEval (lists.removePrefix [ 1 2 ] [ ])).success; @@ -936,20 +1423,43 @@ runTests { }; testFoldAttrs = { - expr = foldAttrs (n: a: [n] ++ a) [] [ - { a = 2; b = 7; } - { a = 3; c = 8; } - ]; - expected = { a = [ 2 3 ]; b = [7]; c = [8];}; + expr = + foldAttrs (n: a: [ n ] ++ a) + [ ] + [ + { + a = 2; + b = 7; + } + { + a = 3; + c = 8; + } + ]; + 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 ]; + expected = [ + 1 + 2 + ]; }; testListCommonPrefixExample2 = { expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ]; - expected = [ 1 2 3 ]; + expected = [ + 1 + 2 + 3 + ]; }; testListCommonPrefixExample3 = { expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ]; @@ -961,29 +1471,52 @@ runTests { }; testListCommonPrefixSame = { expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ]; - expected = [ 1 2 3 ]; + expected = [ + 1 + 2 + 3 + ]; }; testListCommonPrefixLazy = { - expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")]; + 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 { + in + { expr = lists.commonPrefix longList longList; expected = longList; }; testSort = { - expr = sort builtins.lessThan [ 40 2 30 42 ]; - expected = [2 30 40 42]; + expr = sort builtins.lessThan [ + 40 + 2 + 30 + 42 + ]; + expected = [ + 2 + 30 + 40 + 42 + ]; }; testSortOn = { - expr = sortOn stringLength [ "aa" "b" "cccc" ]; - expected = [ "b" "aa" "cccc" ]; + expr = sortOn stringLength [ + "aa" + "b" + "cccc" + ]; + expected = [ + "b" + "aa" + "cccc" + ]; }; testSortOnEmpty = { @@ -992,20 +1525,36 @@ runTests { }; testSortOnIncomparable = { - expr = - map - (x: x.f x.ok) - (sortOn (x: x.ok) [ - { ok = 1; f = x: x; } - { ok = 3; f = x: x + 3; } - { ok = 2; f = x: x; } - ]); - expected = [ 1 2 6 ]; + expr = map (x: x.f x.ok) ( + sortOn (x: x.ok) [ + { + ok = 1; + f = x: x; + } + { + ok = 3; + f = x: x + 3; + } + { + ok = 2; + f = x: x; + } + ] + ); + expected = [ + 1 + 2 + 6 + ]; }; testReplicate = { expr = replicate 3 "a"; - expected = ["a" "a" "a"]; + expected = [ + "a" + "a" + "a" + ]; }; testToIntShouldConvertStringToInt = { @@ -1015,21 +1564,32 @@ runTests { testToIntShouldThrowErrorIfItCouldNotConvertToInt = { expr = builtins.tryEval (toInt "\"foo\""); - expected = { success = false; value = false; }; + expected = { + success = false; + value = false; + }; }; testHasAttrByPathTrue = { - expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; }; + expr = hasAttrByPath [ "a" "b" ] { + a = { + b = "yey"; + }; + }; expected = true; }; testHasAttrByPathFalse = { - expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; }; + expr = hasAttrByPath [ "a" "b" ] { + a = { + c = "yey"; + }; + }; expected = false; }; testHasAttrByPathNonStrict = { - expr = hasAttrByPath [] (throw "do not use"); + expr = hasAttrByPath [ ] (throw "do not use"); expected = true; }; @@ -1060,31 +1620,50 @@ runTests { testLongestValidPathPrefix_two = { expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b = null; }; - expected = [ "a" "b" ]; + expected = [ + "a" + "b" + ]; }; testLongestValidPathPrefix_three = { expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c = null; }; - expected = [ "a" "b" "c" ]; + expected = [ + "a" + "b" + "c" + ]; }; testLongestValidPathPrefix_three_extra = { expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c.d = throw "nope"; }; - expected = [ "a" "b" "c" ]; + expected = [ + "a" + "b" + "c" + ]; }; testFindFirstIndexExample1 = { - expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ]; + expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ + 1 + 6 + 4 + ]; expected = 1; }; testFindFirstIndexExample2 = { - expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ]; + expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ + 1 + 6 + 4 + ]; expected = "a very specific default"; }; testFindFirstIndexEmpty = { - expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null []; + expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [ ]; expected = null; }; @@ -1094,13 +1673,23 @@ runTests { }; testFindFirstIndexSingleDefault = { - expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ]; + expr = lists.findFirstIndex (x: false) null [ + (abort "if the predicate doesn't access the value, it must not be evaluated") + ]; expected = null; }; 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; }; + 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 @@ -1110,41 +1699,63 @@ runTests { }; testFindFirstIndexLazy = { - expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ]; + 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 ]; + expr = lists.findFirst (x: x > 3) 7 [ + 1 + 6 + 4 + ]; expected = 6; }; testFindFirstExample2 = { - expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ]; + expr = lists.findFirst (x: x > 9) 7 [ + 1 + 6 + 4 + ]; expected = 7; }; testAllUnique_true = { - expr = allUnique [ 3 2 4 1 ]; + expr = allUnique [ + 3 + 2 + 4 + 1 + ]; expected = true; }; testAllUnique_false = { - expr = allUnique [ 3 2 3 4 ]; + expr = allUnique [ + 3 + 2 + 3 + 4 + ]; expected = false; }; -# ATTRSETS + # ATTRSETS testConcatMapAttrs = { - expr = concatMapAttrs - (name: value: { - ${name} = value; - ${name + value} = value; - }) - { - foo = "bar"; - foobar = "baz"; - }; + expr = + concatMapAttrs + (name: value: { + ${name} = value; + ${name + value} = value; + }) + { + foo = "bar"; + foobar = "baz"; + }; expected = { foo = "bar"; foobar = "baz"; @@ -1174,27 +1785,54 @@ runTests { # code from example testFoldlAttrs = { expr = { - example = foldlAttrs - (acc: name: value: { - sum = acc.sum + value; - names = acc.names ++ [ name ]; - }) - { sum = 0; names = [ ]; } - { - foo = 1; - bar = 10; - }; + example = + foldlAttrs + (acc: name: value: { + sum = acc.sum + value; + names = acc.names ++ [ name ]; + }) + { + sum = 0; + names = [ ]; + } + { + foo = 1; + bar = 10; + }; # should just return the initial value emptySet = foldlAttrs (throw "function not needed") 123 { }; # should just evaluate to the last value - valuesNotNeeded = foldlAttrs (acc: _name: _v: acc) 3 { z = throw "value z not needed"; a = throw "value a not needed"; }; + valuesNotNeeded = + foldlAttrs + ( + acc: _name: _v: + acc + ) + 3 + { + z = throw "value z not needed"; + a = throw "value a not needed"; + }; # the accumulator doesnt have to be an attrset it can be as trivial as being just a number or string - trivialAcc = foldlAttrs (acc: _name: v: acc * 10 + v) 1 { z = 1; a = 2; }; + trivialAcc = + foldlAttrs + ( + acc: _name: v: + acc * 10 + v + ) + 1 + { + z = 1; + a = 2; + }; }; expected = { example = { sum = 11; - names = [ "bar" "foo" ]; + names = [ + "bar" + "foo" + ]; }; emptySet = 123; valuesNotNeeded = 3; @@ -1202,151 +1840,304 @@ runTests { }; }; - testMergeAttrsListExample1 = { - expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ]; - expected = { a = 0; b = 1; c = 2; d = 3; }; - }; + 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; }; + 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) + 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 { + in + { expr = attrsets.mergeAttrsList list; expected = foldl' mergeAttrs { } list; }; # code from the example testRecursiveUpdateUntil = { - expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) { - # first attribute set - foo.bar = 1; - foo.baz = 2; - bar = 3; - } { - #second attribute set - foo.bar = 1; - foo.quz = 2; - baz = 4; - }; + expr = + recursiveUpdateUntil + ( + path: l: r: + path == [ "foo" ] + ) + { + # first attribute set + foo.bar = 1; + foo.baz = 2; + bar = 3; + } + { + #second attribute set + foo.bar = 1; + foo.quz = 2; + baz = 4; + }; expected = { foo.bar = 1; # 'foo.*' from the second set - foo.quz = 2; # - bar = 3; # 'bar' from the first set - baz = 4; # 'baz' from the second set + foo.quz = 2; + bar = 3; # 'bar' from the first set + baz = 4; # 'baz' from the second set }; }; testMatchAttrsMatchingExact = { - expr = matchAttrs { cpu = { bits = 64; }; } { cpu = { bits = 64; }; }; + expr = + matchAttrs + { + cpu = { + bits = 64; + }; + } + { + cpu = { + bits = 64; + }; + }; expected = true; }; testMatchAttrsMismatch = { - expr = matchAttrs { cpu = { bits = 128; }; } { cpu = { bits = 64; }; }; + expr = + matchAttrs + { + cpu = { + bits = 128; + }; + } + { + cpu = { + bits = 64; + }; + }; expected = false; }; testMatchAttrsMatchingImplicit = { - expr = matchAttrs { cpu = { }; } { cpu = { bits = 64; }; }; + expr = matchAttrs { cpu = { }; } { + cpu = { + bits = 64; + }; + }; expected = true; }; testMatchAttrsMissingAttrs = { - expr = matchAttrs { cpu = {}; } { }; + expr = matchAttrs { cpu = { }; } { }; expected = false; }; testOverrideExistingEmpty = { - expr = overrideExisting {} { a = 1; }; - expected = {}; + expr = overrideExisting { } { a = 1; }; + expected = { }; }; testOverrideExistingDisjoint = { expr = overrideExisting { b = 2; } { a = 1; }; - expected = { b = 2; }; + expected = { + b = 2; + }; }; testOverrideExistingOverride = { - expr = overrideExisting { a = 3; b = 2; } { a = 1; }; - expected = { a = 1; b = 2; }; - }; - - testListAttrsReverse = let - exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;}; - exampleSingletonList = [{name="foo"; value=1;}]; - in { - expr = { - isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs; - isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList; - testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]); - }; + expr = overrideExisting { + a = 3; + b = 2; + } { a = 1; }; expected = { - isReverseToAttrsToList = true; - isReverseToListToAttrs = true; - testDuplicatePruningBehaviour = [{name="a"; value=2;}]; + a = 1; + b = 2; }; }; - testAttrsToListsCanDealWithFunctions = testingEval ( - attrsToList { someFunc= a: a + 1;} - ); + testListAttrsReverse = + let + exampleAttrs = { + foo = 1; + bar = "asdf"; + baz = [ + 1 + 3 + 3 + 7 + ]; + fnord = null; + }; + exampleSingletonList = [ + { + name = "foo"; + value = 1; + } + ]; + in + { + expr = { + isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs; + isReverseToAttrsToList = + attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList; + testDuplicatePruningBehaviour = attrsToList ( + builtins.listToAttrs [ + { + name = "a"; + value = 2; + } + { + name = "a"; + value = 1; + } + ] + ); + }; + expected = { + isReverseToAttrsToList = true; + isReverseToListToAttrs = true; + testDuplicatePruningBehaviour = [ + { + name = "a"; + value = 2; + } + ]; + }; + }; + + testAttrsToListsCanDealWithFunctions = testingEval (attrsToList { + someFunc = a: a + 1; + }); -# FIXED-POINTS + # FIXED-POINTS testFix = { - expr = fix (x: {a = if x ? a then "a" else "b";}); - expected = {a = "a";}; + expr = fix (x: { + a = if x ? a then "a" else "b"; + }); + expected = { + a = "a"; + }; }; testToExtension = { expr = [ - (fix (final: { a = 0; c = final.a; })) - (fix (extends (toExtension { a = 1; b = 2; }) (final: { a = 0; c = final.a; }))) - (fix (extends (toExtension (prev: { a = 1; b = prev.a; })) (final: { a = 0; c = final.a; }))) - (fix (extends (toExtension (final: prev: { a = 1; b = prev.a; c = final.a + 1; })) (final: { a = 0; c = final.a; }))) + (fix (final: { + a = 0; + c = final.a; + })) + (fix ( + extends + (toExtension { + a = 1; + b = 2; + }) + (final: { + a = 0; + c = final.a; + }) + )) + (fix ( + extends + (toExtension (prev: { + a = 1; + b = prev.a; + })) + (final: { + a = 0; + c = final.a; + }) + )) + (fix ( + extends + (toExtension ( + final: prev: { + a = 1; + b = prev.a; + c = final.a + 1; + } + )) + (final: { + a = 0; + c = final.a; + }) + )) ]; expected = [ - { a = 0; c = 0; } - { a = 1; b = 2; c = 1; } - { a = 1; b = 0; c = 1; } - { a = 1; b = 0; c = 2; } + { + a = 0; + c = 0; + } + { + a = 1; + b = 2; + c = 1; + } + { + a = 1; + b = 0; + c = 1; + } + { + a = 1; + b = 0; + c = 2; + } ]; }; -# GENERATORS -# these tests assume attributes are converted to lists -# in alphabetical order + # GENERATORS + # these tests assume attributes are converted to lists + # in alphabetical order testMkKeyValueDefault = { - expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar"; + expr = generators.mkKeyValueDefault { } ":" "f:oo" "bar"; expected = ''f\:oo:bar''; }; testMkValueString = { - expr = let - vals = { - int = 42; - string = ''fo"o''; - bool = true; - bool2 = false; - null = null; - # float = 42.23; # floats are strange - }; - in mapAttrs - (const (generators.mkValueStringDefault {})) - vals; + expr = + let + vals = { + int = 42; + string = ''fo"o''; + bool = true; + bool2 = false; + null = null; + # float = 42.23; # floats are strange + }; + in + mapAttrs (const (generators.mkValueStringDefault { })) vals; expected = { int = "42"; string = ''fo"o''; @@ -1358,7 +2149,7 @@ runTests { }; testToKeyValue = { - expr = generators.toKeyValue {} { + expr = generators.toKeyValue { } { key = "value"; "other=key" = "baz"; }; @@ -1369,12 +2160,15 @@ runTests { }; testToINIEmpty = { - expr = generators.toINI {} {}; + expr = generators.toINI { } { }; expected = ""; }; testToINIEmptySection = { - expr = generators.toINI {} { foo = {}; bar = {}; }; + expr = generators.toINI { } { + foo = { }; + bar = { }; + }; expected = '' [bar] @@ -1383,7 +2177,13 @@ runTests { }; testToINIDuplicateKeys = { - expr = generators.toINI { listsAsDuplicateKeys = true; } { foo.bar = true; baz.qux = [ 1 false ]; }; + expr = generators.toINI { listsAsDuplicateKeys = true; } { + foo.bar = true; + baz.qux = [ + 1 + false + ]; + }; expected = '' [baz] qux=1 @@ -1395,7 +2195,7 @@ runTests { }; testToINIDefaultEscapes = { - expr = generators.toINI {} { + expr = generators.toINI { } { "no [ and ] allowed unescaped" = { "and also no = in keys" = 42; }; @@ -1407,7 +2207,7 @@ runTests { }; testToINIDefaultFull = { - expr = generators.toINI {} { + expr = generators.toINI { } { "section 1" = { attribute1 = 5; x = "Me-se JarJar Binx"; @@ -1430,14 +2230,13 @@ runTests { }; testToINIWithGlobalSectionEmpty = { - expr = generators.toINIWithGlobalSection {} { + expr = generators.toINIWithGlobalSection { } { globalSection = { }; sections = { }; }; - expected = '' - ''; + expected = ''''; }; testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI = @@ -1451,17 +2250,17 @@ runTests { "he\\h=he" = "this is okay"; }; }; - in { - expr = - generators.toINIWithGlobalSection {} { - globalSection = {}; - sections = sections; - }; - expected = generators.toINI {} sections; - }; + in + { + expr = generators.toINIWithGlobalSection { } { + globalSection = { }; + sections = sections; + }; + expected = generators.toINI { } sections; + }; testToINIWithGlobalSectionFull = { - expr = generators.toINIWithGlobalSection {} { + expr = generators.toINIWithGlobalSection { } { globalSection = { foo = "bar"; test = false; @@ -1505,7 +2304,8 @@ runTests { integer = 38; name = "value"; subsection.value = "test"; - };}; + }; + }; expected = '' [extra] ${"\t"}boolean = true @@ -1536,73 +2336,109 @@ runTests { # right now only invocation check testToJSONSimple = - let val = { - foobar = [ "baz" 1 2 3 ]; - }; - in { - expr = generators.toJSON {} val; + let + val = { + foobar = [ + "baz" + 1 + 2 + 3 + ]; + }; + in + { + expr = generators.toJSON { } val; # trivial implementation expected = builtins.toJSON val; - }; + }; # right now only invocation check testToYAMLSimple = - let val = { - list = [ { one = 1; } { two = 2; } ]; - all = 42; - }; - in { - expr = generators.toYAML {} val; + let + val = { + list = [ + { one = 1; } + { two = 2; } + ]; + all = 42; + }; + in + { + expr = generators.toYAML { } val; # trivial implementation expected = builtins.toJSON val; - }; + }; testToPretty = let - deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; }; - in { - expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec { - int = 42; - float = 0.1337; - bool = true; - emptystring = ""; - string = "fn\${o}\"r\\d"; - newlinestring = "\n"; - path = /. + "/foo"; - null_ = null; - function = x: x; - functionArgs = { arg ? 4, foo }: arg; - list = [ 3 4 function [ false ] ]; - emptylist = []; - attrs = { foo = null; "foo b/ar" = "baz"; }; - emptyattrs = {}; - drv = deriv; - }; - expected = rec { - int = "42"; - float = "0.1337"; - bool = "true"; - emptystring = ''""''; - string = ''"fn\''${o}\"r\\d"''; - newlinestring = "\"\\n\""; - path = "/foo"; - null_ = "null"; - function = ""; - functionArgs = ""; - list = "[ 3 4 ${function} [ false ] ]"; - emptylist = "[ ]"; - attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }"; - emptyattrs = "{ }"; - drv = ""; + deriv = derivation { + name = "test"; + builder = "/bin/sh"; + system = "aarch64-linux"; + }; + in + { + expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec { + int = 42; + float = 0.1337; + bool = true; + emptystring = ""; + string = "fn\${o}\"r\\d"; + newlinestring = "\n"; + path = /. + "/foo"; + null_ = null; + function = x: x; + functionArgs = + { + arg ? 4, + foo, + }: + arg; + list = [ + 3 + 4 + function + [ false ] + ]; + emptylist = [ ]; + attrs = { + foo = null; + "foo b/ar" = "baz"; + }; + emptyattrs = { }; + drv = deriv; + }; + expected = rec { + int = "42"; + float = "0.1337"; + bool = "true"; + emptystring = ''""''; + string = ''"fn\''${o}\"r\\d"''; + newlinestring = "\"\\n\""; + path = "/foo"; + null_ = "null"; + function = ""; + functionArgs = ""; + list = "[ 3 4 ${function} [ false ] ]"; + emptylist = "[ ]"; + attrs = "{ foo = null; \"foo b/ar\" = \"baz\"; }"; + emptyattrs = "{ }"; + drv = ""; + }; }; - }; testToPrettyLimit = let a.b = 1; a.c = a; - in { - expr = generators.toPretty { } (generators.withRecursion { throwOnDepthLimit = false; depthLimit = 2; } a); + in + { + expr = generators.toPretty { } ( + generators.withRecursion { + throwOnDepthLimit = false; + depthLimit = 2; + } a + ); expected = "{\n b = 1;\n c = {\n b = \"\";\n c = {\n b = \"\";\n c = \"\";\n };\n };\n}"; }; @@ -1610,31 +2446,46 @@ runTests { let a.b = 1; a.c = a; - in { - expr = (builtins.tryEval - (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))).success; + in + { + expr = + (builtins.tryEval (generators.toPretty { } (generators.withRecursion { depthLimit = 2; } a))) + .success; expected = false; }; testWithRecursionDealsWithFunctors = let functor = { - __functor = self: { a, b, }: null; + __functor = self: { a, b }: null; }; a = { value = "1234"; b = functor; c.d = functor; }; - in { - expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a); + in + { + expr = generators.toPretty { } ( + generators.withRecursion { + depthLimit = 1; + throwOnDepthLimit = false; + } a + ); expected = "{\n b = ;\n c = {\n d = \"\";\n };\n value = \"\";\n}"; }; testToPrettyMultiline = { expr = mapAttrs (const (generators.toPretty { })) { - list = [ 3 4 [ false ] ]; - attrs = { foo = null; bar.foo = "baz"; }; + list = [ + 3 + 4 + [ false ] + ]; + attrs = { + foo = null; + bar.foo = "baz"; + }; newlinestring = "\n"; multilinestring = '' hello @@ -1679,9 +2530,11 @@ runTests { }; testToPrettyAllowPrettyValues = { - expr = generators.toPretty { allowPrettyValues = true; } - { __pretty = v: "«" + v + "»"; val = "foo"; }; - expected = "«foo»"; + expr = generators.toPretty { allowPrettyValues = true; } { + __pretty = v: "«" + v + "»"; + val = "foo"; + }; + expected = "«foo»"; }; testToPlistUnescaped = { @@ -1696,15 +2549,24 @@ runTests { newlinestring = "\n"; path = /. + "/foo"; null_ = null; - list = [ 3 4 "test" ]; - emptylist = []; - attrs = { foo = null; "foo b/ar" = "baz"; }; - emptyattrs = {}; + list = [ + 3 + 4 + "test" + ]; + emptylist = [ ]; + attrs = { + foo = null; + "foo b/ar" = "baz"; + }; + emptyattrs = { }; "keys are not " = "and < neither are string values"; }; }; }; - expected = { value = builtins.readFile ./test-to-plist-unescaped-expected.plist; }; + expected = { + value = builtins.readFile ./test-to-plist-unescaped-expected.plist; + }; }; testToPlistEscaped = { @@ -1719,29 +2581,43 @@ runTests { newlinestring = "\n"; path = /. + "/foo"; null_ = null; - list = [ 3 4 "test" ]; - emptylist = []; - attrs = { foo = null; "foo b/ar" = "baz"; }; - emptyattrs = {}; + list = [ + 3 + 4 + "test" + ]; + emptylist = [ ]; + attrs = { + foo = null; + "foo b/ar" = "baz"; + }; + emptyattrs = { }; "keys are " = "and < so are string values"; }; }; }; - expected = { value = builtins.readFile ./test-to-plist-escaped-expected.plist; }; + expected = { + value = builtins.readFile ./test-to-plist-escaped-expected.plist; + }; }; testToLuaEmptyAttrSet = { - expr = generators.toLua {} {}; + expr = generators.toLua { } { }; expected = ''{}''; }; testToLuaEmptyList = { - expr = generators.toLua {} []; + expr = generators.toLua { } [ ]; expected = ''{}''; }; testToLuaListOfVariousTypes = { - expr = generators.toLua {} [ null 43 3.14159 true ]; + expr = generators.toLua { } [ + null + 43 + 3.14159 + true + ]; expected = '' { nil, @@ -1752,12 +2628,12 @@ runTests { }; testToLuaString = { - expr = generators.toLua {} ''double-quote (") and single quotes (')''; + expr = generators.toLua { } ''double-quote (") and single quotes (')''; expected = ''"double-quote (\") and single quotes (')"''; }; testToLuaAttrsetWithLuaInline = { - expr = generators.toLua {} { x = generators.mkLuaInline ''"abc" .. "def"''; }; + expr = generators.toLua { } { x = generators.mkLuaInline ''"abc" .. "def"''; }; expected = '' { ["x"] = ("abc" .. "def") @@ -1765,7 +2641,7 @@ runTests { }; testToLuaAttrsetWithSpaceInKey = { - expr = generators.toLua {} { "some space and double-quote (\")" = 42; }; + expr = generators.toLua { } { "some space and double-quote (\")" = 42; }; expected = '' { ["some space and double-quote (\")"] = 42 @@ -1773,17 +2649,25 @@ runTests { }; testToLuaWithoutMultiline = { - expr = generators.toLua { multiline = false; } [ 41 43 ]; + expr = generators.toLua { multiline = false; } [ + 41 + 43 + ]; expected = ''{ 41, 43 }''; }; testToLuaEmptyBindings = { - expr = generators.toLua { asBindings = true; } {}; + expr = generators.toLua { asBindings = true; } { }; expected = ""; }; testToLuaBindings = { - expr = generators.toLua { asBindings = true; } { x1 = 41; _y = { a = 43; }; }; + expr = generators.toLua { asBindings = true; } { + x1 = 41; + _y = { + a = 43; + }; + }; expected = '' _y = { ["a"] = 43 @@ -1800,7 +2684,17 @@ runTests { }; testToLuaIndentedBindings = { - expr = generators.toLua { asBindings = true; indent = " "; } { x = { y = 42; }; }; + expr = + generators.toLua + { + asBindings = true; + indent = " "; + } + { + x = { + y = 42; + }; + }; expected = " x = {\n [\"y\"] = 42\n }\n"; }; @@ -1813,8 +2707,11 @@ runTests { ); testToLuaBasicExample = { - expr = generators.toLua {} { - cmd = [ "typescript-language-server" "--stdio" ]; + expr = generators.toLua { } { + cmd = [ + "typescript-language-server" + "--stdio" + ]; settings.workspace.library = generators.mkLuaInline ''vim.api.nvim_get_runtime_file("", true)''; }; expected = '' @@ -1831,25 +2728,33 @@ runTests { }''; }; -# CLI + # CLI testToGNUCommandLine = { - expr = cli.toGNUCommandLine {} { + expr = cli.toGNUCommandLine { } { data = builtins.toJSON { id = 0; }; X = "PUT"; retry = 3; retry-delay = null; - url = [ "https://example.com/foo" "https://example.com/bar" ]; + url = [ + "https://example.com/foo" + "https://example.com/bar" + ]; silent = false; verbose = true; }; expected = [ - "-X" "PUT" - "--data" "{\"id\":0}" - "--retry" "3" - "--url" "https://example.com/foo" - "--url" "https://example.com/bar" + "-X" + "PUT" + "--data" + "{\"id\":0}" + "--retry" + "3" + "--url" + "https://example.com/foo" + "--url" + "https://example.com/bar" "--verbose" ]; }; @@ -1860,7 +2765,10 @@ runTests { X = "PUT"; retry = 3; retry-delay = null; - url = [ "https://example.com/foo" "https://example.com/bar" ]; + url = [ + "https://example.com/foo" + "https://example.com/bar" + ]; silent = false; verbose = true; }; @@ -1876,12 +2784,15 @@ runTests { }; testToGNUCommandLineShell = { - expr = cli.toGNUCommandLineShell {} { + expr = cli.toGNUCommandLineShell { } { data = builtins.toJSON { id = 0; }; X = "PUT"; retry = 3; retry-delay = null; - url = [ "https://example.com/foo" "https://example.com/bar" ]; + url = [ + "https://example.com/foo" + "https://example.com/bar" + ]; silent = false; verbose = true; }; @@ -1922,51 +2833,75 @@ runTests { testFreeformOptions = { expr = let - submodule = { lib, ... }: { - freeformType = lib.types.attrsOf (lib.types.submodule { - options.bar = lib.mkOption {}; - }); - options.bar = lib.mkOption {}; - }; + submodule = + { lib, ... }: + { + freeformType = lib.types.attrsOf ( + lib.types.submodule { + options.bar = lib.mkOption { }; + } + ); + options.bar = lib.mkOption { }; + }; - module = { lib, ... }: { - options.foo = lib.mkOption { - type = lib.types.submodule submodule; + module = + { lib, ... }: + { + options.foo = lib.mkOption { + type = lib.types.submodule submodule; + }; }; - }; - options = (evalModules { - modules = [ module ]; - }).options; + options = + (evalModules { + modules = [ module ]; + }).options; - locs = filter (o: ! o.internal) (optionAttrSetToDocList options); - in map (o: o.loc) locs; - expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "" "bar" ] [ "foo" "bar" ] ]; + locs = filter (o: !o.internal) (optionAttrSetToDocList options); + in + map (o: o.loc) locs; + expected = [ + [ + "_module" + "args" + ] + [ "foo" ] + [ + "foo" + "" + "bar" + ] + [ + "foo" + "bar" + ] + ]; }; testAttrsWithName = { - expr = let - eval = evalModules { - modules = [ - { - options = { - foo = lib.mkOption { - type = lib.types.attrsWith { - placeholder = "MyCustomPlaceholder"; - elemType = lib.types.submodule { - options.bar = lib.mkOption { - type = lib.types.int; - default = 42; + expr = + let + eval = evalModules { + modules = [ + { + options = { + foo = lib.mkOption { + type = lib.types.attrsWith { + placeholder = "MyCustomPlaceholder"; + elemType = lib.types.submodule { + options.bar = lib.mkOption { + type = lib.types.int; + default = 42; + }; }; }; }; }; - }; - } - ]; - }; - opt = eval.options.foo; - in + } + ]; + }; + opt = eval.options.foo; + in (opt.type.getSubOptions opt.loc).bar.loc; expected = [ "foo" @@ -1978,110 +2913,342 @@ runTests { testShowOptionWithPlaceholder = { # , *, should not be escaped. It is used as a placeholder by convention. # Other symbols should be escaped. `{}` - expr = lib.showOption ["" "" "*" "{foo}"]; + expr = lib.showOption [ + "" + "" + "*" + "{foo}" + ]; expected = "..*.\"{foo}\""; }; testCartesianProductOfEmptySet = { - expr = cartesianProduct {}; - expected = [ {} ]; + expr = cartesianProduct { }; + expected = [ { } ]; }; testCartesianProductOfOneSet = { - expr = cartesianProduct { a = [ 1 2 3 ]; }; - expected = [ { a = 1; } { a = 2; } { a = 3; } ]; + expr = cartesianProduct { + a = [ + 1 + 2 + 3 + ]; + }; + expected = [ + { a = 1; } + { a = 2; } + { a = 3; } + ]; }; testCartesianProductOfTwoSets = { - expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; }; + expr = cartesianProduct { + a = [ 1 ]; + b = [ + 10 + 20 + ]; + }; expected = [ - { a = 1; b = 10; } - { a = 1; b = 20; } + { + a = 1; + b = 10; + } + { + a = 1; + b = 20; + } ]; }; testCartesianProductOfTwoSetsWithOneEmpty = { - expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; }; + expr = cartesianProduct { + a = [ ]; + b = [ + 10 + 20 + ]; + }; expected = [ ]; }; testCartesianProductOfThreeSets = { expr = cartesianProduct { - a = [ 1 2 3 ]; - b = [ 10 20 30 ]; - c = [ 100 200 300 ]; + a = [ + 1 + 2 + 3 + ]; + b = [ + 10 + 20 + 30 + ]; + c = [ + 100 + 200 + 300 + ]; }; expected = [ - { a = 1; b = 10; c = 100; } - { a = 1; b = 10; c = 200; } - { a = 1; b = 10; c = 300; } + { + a = 1; + b = 10; + c = 100; + } + { + a = 1; + b = 10; + c = 200; + } + { + a = 1; + b = 10; + c = 300; + } - { a = 1; b = 20; c = 100; } - { a = 1; b = 20; c = 200; } - { a = 1; b = 20; c = 300; } + { + a = 1; + b = 20; + c = 100; + } + { + a = 1; + b = 20; + c = 200; + } + { + a = 1; + b = 20; + c = 300; + } - { a = 1; b = 30; c = 100; } - { a = 1; b = 30; c = 200; } - { a = 1; b = 30; c = 300; } + { + a = 1; + b = 30; + c = 100; + } + { + a = 1; + b = 30; + c = 200; + } + { + a = 1; + b = 30; + c = 300; + } - { a = 2; b = 10; c = 100; } - { a = 2; b = 10; c = 200; } - { a = 2; b = 10; c = 300; } + { + a = 2; + b = 10; + c = 100; + } + { + a = 2; + b = 10; + c = 200; + } + { + a = 2; + b = 10; + c = 300; + } - { a = 2; b = 20; c = 100; } - { a = 2; b = 20; c = 200; } - { a = 2; b = 20; c = 300; } + { + a = 2; + b = 20; + c = 100; + } + { + a = 2; + b = 20; + c = 200; + } + { + a = 2; + b = 20; + c = 300; + } - { a = 2; b = 30; c = 100; } - { a = 2; b = 30; c = 200; } - { a = 2; b = 30; c = 300; } + { + a = 2; + b = 30; + c = 100; + } + { + a = 2; + b = 30; + c = 200; + } + { + a = 2; + b = 30; + c = 300; + } - { a = 3; b = 10; c = 100; } - { a = 3; b = 10; c = 200; } - { a = 3; b = 10; c = 300; } + { + a = 3; + b = 10; + c = 100; + } + { + a = 3; + b = 10; + c = 200; + } + { + a = 3; + b = 10; + c = 300; + } - { a = 3; b = 20; c = 100; } - { a = 3; b = 20; c = 200; } - { a = 3; b = 20; c = 300; } + { + a = 3; + b = 20; + c = 100; + } + { + a = 3; + b = 20; + c = 200; + } + { + a = 3; + b = 20; + c = 300; + } - { a = 3; b = 30; c = 100; } - { a = 3; b = 30; c = 200; } - { a = 3; b = 30; c = 300; } + { + a = 3; + b = 30; + c = 100; + } + { + a = 3; + b = 30; + c = 200; + } + { + a = 3; + b = 30; + c = 300; + } ]; }; testMapCartesianProductOfOneSet = { - expr = mapCartesianProduct ({a}: a * 2) { a = [ 1 2 3 ]; }; - expected = [ 2 4 6 ]; + expr = mapCartesianProduct ({ a }: a * 2) { + a = [ + 1 + 2 + 3 + ]; + }; + expected = [ + 2 + 4 + 6 + ]; }; testMapCartesianProductOfTwoSets = { - expr = mapCartesianProduct ({a,b}: a + b) { a = [ 1 ]; b = [ 10 20 ]; }; - expected = [ 11 21 ]; + expr = mapCartesianProduct ({ a, b }: a + b) { + a = [ 1 ]; + b = [ + 10 + 20 + ]; + }; + expected = [ + 11 + 21 + ]; }; testMapCartesianProcutOfTwoSetsWithOneEmpty = { - expr = mapCartesianProduct (x: x.a + x.b) { a = [ ]; b = [ 10 20 ]; }; + expr = mapCartesianProduct (x: x.a + x.b) { + a = [ ]; + b = [ + 10 + 20 + ]; + }; expected = [ ]; }; testMapCartesianProductOfThreeSets = { - expr = mapCartesianProduct ({a,b,c}: a + b + c) { - a = [ 1 2 3 ]; - b = [ 10 20 30 ]; - c = [ 100 200 300 ]; - }; - expected = [ 111 211 311 121 221 321 131 231 331 112 212 312 122 222 322 132 232 332 113 213 313 123 223 323 133 233 333 ]; + expr = + mapCartesianProduct + ( + { + a, + b, + c, + }: + a + b + c + ) + { + a = [ + 1 + 2 + 3 + ]; + b = [ + 10 + 20 + 30 + ]; + c = [ + 100 + 200 + 300 + ]; + }; + expected = [ + 111 + 211 + 311 + 121 + 221 + 321 + 131 + 231 + 331 + 112 + 212 + 312 + 122 + 222 + 322 + 132 + 232 + 332 + 113 + 213 + 313 + 123 + 223 + 323 + 133 + 233 + 333 + ]; }; # The example from the showAttrPath documentation testShowAttrPathExample = { - expr = showAttrPath [ "foo" "10" "bar" ]; + expr = showAttrPath [ + "foo" + "10" + "bar" + ]; expected = "foo.\"10\".bar"; }; testShowAttrPathEmpty = { - expr = showAttrPath []; + expr = showAttrPath [ ]; expected = ""; }; @@ -2099,41 +3266,91 @@ runTests { testGroupBy = { expr = groupBy (n: toString (mod n 5)) (range 0 16); expected = { - "0" = [ 0 5 10 15 ]; - "1" = [ 1 6 11 16 ]; - "2" = [ 2 7 12 ]; - "3" = [ 3 8 13 ]; - "4" = [ 4 9 14 ]; + "0" = [ + 0 + 5 + 10 + 15 + ]; + "1" = [ + 1 + 6 + 11 + 16 + ]; + "2" = [ + 2 + 7 + 12 + ]; + "3" = [ + 3 + 8 + 13 + ]; + "4" = [ + 4 + 9 + 14 + ]; }; }; testGroupBy' = { - expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ]; - expected = { false = 3; true = 12; }; + expr = groupBy' builtins.add 0 (x: boolToString (x > 2)) [ + 5 + 1 + 2 + 3 + 4 + ]; + expected = { + false = 3; + true = 12; + }; }; # The example from the updateManyAttrsByPath documentation testUpdateManyAttrsByPathExample = { expr = updateManyAttrsByPath [ { - path = [ "a" "b" ]; + path = [ + "a" + "b" + ]; update = old: { d = old.c; }; } { - path = [ "a" "b" "c" ]; + path = [ + "a" + "b" + "c" + ]; update = old: old + 1; } { - path = [ "x" "y" ]; + path = [ + "x" + "y" + ]; update = old: "xy"; } ] { a.b.c = 0; }; - expected = { a = { b = { d = 1; }; }; x = { y = "xy"; }; }; + expected = { + a = { + b = { + d = 1; + }; + }; + x = { + y = "xy"; + }; + }; }; # If there are no updates, the value is passed through testUpdateManyAttrsByPathNone = { - expr = updateManyAttrsByPath [] "something"; + expr = updateManyAttrsByPath [ ] "something"; expected = "something"; }; @@ -2184,18 +3401,25 @@ runTests { # Deeply nested attributes can be updated without affecting others testUpdateManyAttrsByPathDeep = { - expr = updateManyAttrsByPath [ - { - path = [ "a" "b" "c" ]; - update = old: old + 1; - } - ] { - a.b.c = 0; + expr = + updateManyAttrsByPath + [ + { + path = [ + "a" + "b" + "c" + ]; + update = old: old + 1; + } + ] + { + a.b.c = 0; - a.b.z = 0; - a.y.z = 0; - x.y.z = 0; - }; + a.b.z = 0; + a.y.z = 0; + x.y.z = 0; + }; expected = { a.b.c = 1; @@ -2207,18 +3431,24 @@ runTests { # Nested attributes are updated first testUpdateManyAttrsByPathNestedBeforehand = { - expr = updateManyAttrsByPath [ - { - path = [ "a" ]; - update = old: old // { x = old.b; }; - } - { - path = [ "a" "b" ]; - update = old: old + 1; - } - ] { - a.b = 0; - }; + expr = + updateManyAttrsByPath + [ + { + path = [ "a" ]; + update = old: old // { x = old.b; }; + } + { + path = [ + "a" + "b" + ]; + update = old: old + 1; + } + ] + { + a.b = 0; + }; expected = { a.b = 1; a.x = 1; @@ -2386,86 +3616,122 @@ runTests { }); # It's ok to add attribute names here when lazyDerivation is improved # in accordance with its inline comments. - expected = [ "drvPath" "meta" "name" "out" "outPath" "outputName" "outputs" "system" "type" ]; + expected = [ + "drvPath" + "meta" + "name" + "out" + "outPath" + "outputName" + "outputs" + "system" + "type" + ]; }; testLazyDerivationIsLazyInDerivationForPassthruAttr = { - expr = (lazyDerivation { - derivation = throw "not lazy enough"; - passthru.tests = "whatever is in tests"; - }).tests; + expr = + (lazyDerivation { + derivation = throw "not lazy enough"; + passthru.tests = "whatever is in tests"; + }).tests; expected = "whatever is in tests"; }; testLazyDerivationIsLazyInDerivationForPassthruAttr2 = { # passthru.tests is not a special case. It works for any attr. - expr = (lazyDerivation { - derivation = throw "not lazy enough"; - passthru.foo = "whatever is in foo"; - }).foo; + expr = + (lazyDerivation { + derivation = throw "not lazy enough"; + passthru.foo = "whatever is in foo"; + }).foo; expected = "whatever is in foo"; }; testLazyDerivationIsLazyInDerivationForMeta = { - expr = (lazyDerivation { - derivation = throw "not lazy enough"; - meta = "whatever is in meta"; - }).meta; + expr = + (lazyDerivation { + derivation = throw "not lazy enough"; + meta = "whatever is in meta"; + }).meta; expected = "whatever is in meta"; }; - testLazyDerivationReturnsDerivationAttrs = let - derivation = { - type = "derivation"; - outputs = ["out"]; - out = "test out"; - outPath = "test outPath"; - outputName = "out"; - drvPath = "test drvPath"; - name = "test name"; - system = "test system"; - meta = "test meta"; - }; - in { - expr = lazyDerivation { inherit derivation; }; - expected = derivation; - }; - - testOptionalDrvAttr = let - mkDerivation = args: derivation (args // { - builder = "builder"; - system = "system"; - __ignoreNulls = true; - }); - in { - expr = (mkDerivation { - name = "foo"; - x = optionalDrvAttr true 1; - y = optionalDrvAttr false 1; - }).drvPath; - expected = (mkDerivation { - name = "foo"; - x = 1; - }).drvPath; - }; - - testLazyDerivationMultiOutputReturnsDerivationAttrs = let - derivation = { - type = "derivation"; - outputs = ["out" "dev"]; - dev = "test dev"; - out = "test out"; - outPath = "test outPath"; - outputName = "out"; - drvPath = "test drvPath"; - name = "test name"; - system = "test system"; - meta.position = "/hi:23"; + testLazyDerivationReturnsDerivationAttrs = + let + derivation = { + type = "derivation"; + outputs = [ "out" ]; + out = "test out"; + outPath = "test outPath"; + outputName = "out"; + drvPath = "test drvPath"; + name = "test name"; + system = "test system"; + meta = "test meta"; + }; + in + { + expr = lazyDerivation { inherit derivation; }; + expected = derivation; + }; + + testOptionalDrvAttr = + let + mkDerivation = + args: + derivation ( + args + // { + builder = "builder"; + system = "system"; + __ignoreNulls = true; + } + ); + in + { + expr = + (mkDerivation { + name = "foo"; + x = optionalDrvAttr true 1; + y = optionalDrvAttr false 1; + }).drvPath; + expected = + (mkDerivation { + name = "foo"; + x = 1; + }).drvPath; + }; + + testLazyDerivationMultiOutputReturnsDerivationAttrs = + let + derivation = { + type = "derivation"; + outputs = [ + "out" + "dev" + ]; + dev = "test dev"; + out = "test out"; + outPath = "test outPath"; + outputName = "out"; + drvPath = "test drvPath"; + name = "test name"; + system = "test system"; + meta.position = "/hi:23"; + }; + in + { + expr = lazyDerivation { + inherit derivation; + outputs = [ + "out" + "dev" + ]; + passthru.meta.position = "/hi:23"; + }; + expected = derivation; }; - in { - expr = lazyDerivation { inherit derivation; outputs = ["out" "dev"]; passthru.meta.position = "/hi:23"; }; - expected = derivation; - }; testTypeDescriptionInt = { expr = (with types; int).description; @@ -2476,7 +3742,7 @@ runTests { expected = "positive integer, meaning >0"; }; testTypeDescriptionIntsPositiveOrEnumAuto = { - expr = (with types; either ints.positive (enum ["auto"])).description; + expr = (with types; either ints.positive (enum [ "auto" ])).description; expected = ''positive integer, meaning >0, or value "auto" (singular enum)''; }; testTypeDescriptionListOfPositive = { @@ -2504,11 +3770,26 @@ runTests { expected = "string or list of boolean"; }; testTypeDescriptionOneOfListOfStrOrBool = { - expr = (with types; oneOf [ (listOf bool) str ]).description; + expr = + ( + with types; + oneOf [ + (listOf bool) + str + ] + ).description; expected = "(list of boolean) or string"; }; testTypeDescriptionOneOfListOfStrOrBoolOrNumber = { - expr = (with types; oneOf [ (listOf bool) str number ]).description; + expr = + ( + with types; + oneOf [ + (listOf bool) + str + number + ] + ).description; expected = "(list of boolean) or string or signed integer or floating point number"; }; testTypeDescriptionEitherListOfBoolOrEitherStringOrNumber = { @@ -2532,14 +3813,30 @@ runTests { expected = "signed integer or list of (boolean or string)"; }; testTypeFunctionToPropagateFunctionArgs = { - expr = lib.functionArgs ((types.functionTo types.null).merge [] [ - { - value = {a, b ? false, ... }: null; - } - { - value = {b, c ? false, ... }: null; - } - ]); + expr = lib.functionArgs ( + (types.functionTo types.null).merge + [ ] + [ + { + value = + { + a, + b ? false, + ... + }: + null; + } + { + value = + { + b, + c ? false, + ... + }: + null; + } + ] + ); expected = { a = false; b = false; @@ -2547,7 +3844,7 @@ runTests { }; }; -# Meta + # Meta testGetExe'Output = { expr = getExe' { type = "derivation"; @@ -2567,13 +3864,9 @@ runTests { expected = "somelonghash/bin/mainProgram"; }; - testGetExe'FailureFirstArg = testingThrow ( - getExe' "not a derivation" "executable" - ); + testGetExe'FailureFirstArg = testingThrow (getExe' "not a derivation" "executable"); - testGetExe'FailureSecondArg = testingThrow ( - getExe' { type = "derivation"; } "dir/executable" - ); + testGetExe'FailureSecondArg = testingThrow (getExe' { type = "derivation"; } "dir/executable"); testGetLicenseFromSpdxIdOrExamples = { expr = [ @@ -2600,7 +3893,8 @@ runTests { }; testPlatformMatchAttrs = { - expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux").parsed; + expr = meta.platformMatch (systems.elaborate "x86_64-linux") (systems.elaborate "x86_64-linux") + .parsed; expected = true; }; @@ -2651,68 +3945,87 @@ runTests { let mergedType = types.mergeTypes types.str types.str; in - { - expr = mergedType.name; - expected = "str"; - }; + { + expr = mergedType.name; + expected = "str"; + }; testMergeTypesFail = let mergedType = types.mergeTypes types.str types.int; in - { - expr = types.isType "merge-error" mergedType; - expected = true; - }; + { + expr = types.isType "merge-error" mergedType; + expected = true; + }; testMergeTypesEnum = let - enumAB = lib.types.enum ["A" "B"]; - enumXY = lib.types.enum ["X" "Y"]; + enumAB = lib.types.enum [ + "A" + "B" + ]; + enumXY = lib.types.enum [ + "X" + "Y" + ]; merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ] in - { - expr = { - checkA = merged.check "A"; - checkB = merged.check "B"; - checkX = merged.check "X"; - checkY = merged.check "Y"; - checkC = merged.check "C"; - }; - expected = { - checkA = true; - checkB = true; - checkX = true; - checkY = true; - checkC = false; + { + expr = { + checkA = merged.check "A"; + checkB = merged.check "B"; + checkX = merged.check "X"; + checkY = merged.check "Y"; + checkC = merged.check "C"; + }; + expected = { + checkA = true; + checkB = true; + checkX = true; + checkY = true; + checkC = false; + }; }; - }; # Check that `packagesFromDirectoryRecursive` can be used to create scopes # for sub-directories - testPackagesFromDirectoryNestedScopes = let - inherit (lib) makeScope recurseIntoAttrs; - emptyScope = makeScope lib.callPackageWith (_: {}); - in { - expr = lib.filterAttrsRecursive (name: value: !lib.elem name [ "callPackage" "newScope" "overrideScope" "packages" ]) (packagesFromDirectoryRecursive { - inherit (emptyScope) callPackage newScope; - directory = ./packages-from-directory/scope; - }); - expected = lib.recurseIntoAttrs { - a = "a"; - b = "b"; - # Note: Other files/directories in `./test-data/c/` are ignored and can be - # used by `package.nix`. - c = "c"; - my-namespace = lib.recurseIntoAttrs { - d = "d"; - e = "e"; - f = "f"; - my-sub-namespace = lib.recurseIntoAttrs { - g = "g"; - h = "h"; + testPackagesFromDirectoryNestedScopes = + let + inherit (lib) makeScope recurseIntoAttrs; + emptyScope = makeScope lib.callPackageWith (_: { }); + in + { + expr = + lib.filterAttrsRecursive + ( + name: value: + !lib.elem name [ + "callPackage" + "newScope" + "overrideScope" + "packages" + ] + ) + (packagesFromDirectoryRecursive { + inherit (emptyScope) callPackage newScope; + directory = ./packages-from-directory/scope; + }); + expected = lib.recurseIntoAttrs { + a = "a"; + b = "b"; + # Note: Other files/directories in `./test-data/c/` are ignored and can be + # used by `package.nix`. + c = "c"; + my-namespace = lib.recurseIntoAttrs { + d = "d"; + e = "e"; + f = "f"; + my-sub-namespace = lib.recurseIntoAttrs { + g = "g"; + h = "h"; + }; }; }; }; - }; } diff --git a/lib/tests/modules/types.nix b/lib/tests/modules/types.nix index 7c43a6819e0e..449fd51fd49c 100644 --- a/lib/tests/modules/types.nix +++ b/lib/tests/modules/types.nix @@ -1,7 +1,8 @@ { lib, ... }: let inherit (builtins) - storeDir; + storeDir + ; inherit (lib) types mkOption diff --git a/lib/tests/release.nix b/lib/tests/release.nix index 30712735d237..7ab6c24e38e6 100644 --- a/lib/tests/release.nix +++ b/lib/tests/release.nix @@ -1,24 +1,32 @@ -{ # The pkgs used for dependencies for the testing itself +{ + # The pkgs used for dependencies for the testing itself # Don't test properties of pkgs.lib, but rather the lib in the parent directory system ? builtins.currentSystem, - pkgs ? import ../.. { inherit system; } // { lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; }, + pkgs ? import ../.. { inherit system; } // { + lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; + }, # For testing someone may edit impure.nix to return cross pkgs, use `pkgsBuildBuild` directly so everything here works. pkgsBB ? pkgs.pkgsBuildBuild, nix ? pkgs-nixVersions.stable, - nixVersions ? [ pkgs-nixVersions.minimum nix pkgs-nixVersions.latest ], + nixVersions ? [ + pkgs-nixVersions.minimum + nix + pkgs-nixVersions.latest + ], pkgs-nixVersions ? import ./nix-for-tests.nix { pkgs = pkgsBB; }, }: let lib = import ../.; - testWithNix = nix: + testWithNix = + nix: import ./test-with-nix.nix { inherit lib nix; pkgs = pkgsBB; }; in - pkgsBB.symlinkJoin { - name = "nixpkgs-lib-tests"; - paths = map testWithNix nixVersions; - } +pkgsBB.symlinkJoin { + name = "nixpkgs-lib-tests"; + paths = map testWithNix nixVersions; +} diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix index 9097f03965aa..941f887304d3 100644 --- a/lib/tests/systems.nix +++ b/lib/tests/systems.nix @@ -6,7 +6,7 @@ let lib = import ../default.nix; mseteq = x: y: { - expr = lib.sort lib.lessThan x; + expr = lib.sort lib.lessThan x; expected = lib.sort lib.lessThan y; }; @@ -20,99 +20,273 @@ let 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; + 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 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 :). + # 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); + (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-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ]; - 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" ]; - testriscv32 = mseteq riscv32 [ "riscv32-linux" "riscv32-netbsd" "riscv32-none" ]; - testriscv64 = mseteq riscv64 [ "riscv64-linux" "riscv64-netbsd" "riscv64-none" ]; - tests390x = mseteq s390x [ "s390x-linux" "s390x-none" ]; - testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ]; + 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-freebsd" + "i686-genode" + "i686-netbsd" + "i686-openbsd" + "i686-cygwin" + "i686-windows" + "i686-none" + "i686-darwin" + ]; + 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" + ]; + testriscv32 = mseteq riscv32 [ + "riscv32-linux" + "riscv32-netbsd" + "riscv32-none" + ]; + testriscv64 = mseteq riscv64 [ + "riscv64-linux" + "riscv64-netbsd" + "riscv64-none" + ]; + tests390x = mseteq s390x [ + "s390x-linux" + "s390x-none" + ]; + testx86_64 = mseteq x86_64 [ + "x86_64-linux" + "x86_64-darwin" + "x86_64-freebsd" + "x86_64-genode" + "x86_64-redox" + "x86_64-openbsd" + "x86_64-netbsd" + "x86_64-cygwin" + "x86_64-solaris" + "x86_64-windows" + "x86_64-none" + ]; - testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ]; - testdarwin = mseteq darwin [ "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" ]; - testfreebsd = mseteq freebsd [ "aarch64-freebsd" "i686-freebsd" "x86_64-freebsd" ]; - testgenode = mseteq genode [ "aarch64-genode" "i686-genode" "x86_64-genode" ]; - testredox = mseteq redox [ "x86_64-redox" ]; - testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */); - testillumos = mseteq illumos [ "x86_64-solaris" ]; - testlinux = mseteq linux [ "aarch64-linux" "armv5tel-linux" "armv6l-linux" "armv7a-linux" "armv7l-linux" "i686-linux" "loongarch64-linux" "m68k-linux" "microblaze-linux" "microblazeel-linux" "mips-linux" "mips64-linux" "mips64el-linux" "mipsel-linux" "powerpc64-linux" "powerpc64le-linux" "riscv32-linux" "riscv64-linux" "s390-linux" "s390x-linux" "x86_64-linux" ]; - testnetbsd = mseteq netbsd [ "aarch64-netbsd" "armv6l-netbsd" "armv7a-netbsd" "armv7l-netbsd" "i686-netbsd" "m68k-netbsd" "mipsel-netbsd" "powerpc-netbsd" "riscv32-netbsd" "riscv64-netbsd" "x86_64-netbsd" ]; - testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ]; - testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "aarch64-windows" "i686-windows" "x86_64-windows" ]; - testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox); -}) + testcygwin = mseteq cygwin [ + "i686-cygwin" + "x86_64-cygwin" + ]; + testdarwin = mseteq darwin [ + "x86_64-darwin" + "i686-darwin" + "aarch64-darwin" + "armv7a-darwin" + ]; + testfreebsd = mseteq freebsd [ + "aarch64-freebsd" + "i686-freebsd" + "x86_64-freebsd" + ]; + testgenode = mseteq genode [ + "aarch64-genode" + "i686-genode" + "x86_64-genode" + ]; + testredox = mseteq redox [ "x86_64-redox" ]; + testgnu = mseteq gnu ( + linux # ++ kfreebsd ++ ... + ); + testillumos = mseteq illumos [ "x86_64-solaris" ]; + testlinux = mseteq linux [ + "aarch64-linux" + "armv5tel-linux" + "armv6l-linux" + "armv7a-linux" + "armv7l-linux" + "i686-linux" + "loongarch64-linux" + "m68k-linux" + "microblaze-linux" + "microblazeel-linux" + "mips-linux" + "mips64-linux" + "mips64el-linux" + "mipsel-linux" + "powerpc64-linux" + "powerpc64le-linux" + "riscv32-linux" + "riscv64-linux" + "s390-linux" + "s390x-linux" + "x86_64-linux" + ]; + testnetbsd = mseteq netbsd [ + "aarch64-netbsd" + "armv6l-netbsd" + "armv7a-netbsd" + "armv7l-netbsd" + "i686-netbsd" + "m68k-netbsd" + "mipsel-netbsd" + "powerpc-netbsd" + "riscv32-netbsd" + "riscv64-netbsd" + "x86_64-netbsd" + ]; + testopenbsd = mseteq openbsd [ + "i686-openbsd" + "x86_64-openbsd" + ]; + testwindows = mseteq windows [ + "i686-cygwin" + "x86_64-cygwin" + "aarch64-windows" + "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_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; - }; - test_elaborate_config_over_system = { - expr = (lib.systems.elaborate { config = "i686-unknown-linux-gnu"; system = "x86_64-linux"; }).system; - expected = "i686-linux"; - }; - test_elaborate_config_over_parsed = { - expr = (lib.systems.elaborate { config = "i686-unknown-linux-gnu"; parsed = (lib.systems.elaborate "x86_64-linux").parsed; }).parsed.cpu.arch; - expected = "i686"; - }; - test_elaborate_system_over_parsed = { - expr = (lib.systems.elaborate { system = "i686-linux"; parsed = (lib.systems.elaborate "x86_64-linux").parsed; }).parsed.cpu.arch; - expected = "i686"; - }; -} + 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; + }; + test_elaborate_config_over_system = { + expr = + (lib.systems.elaborate { + config = "i686-unknown-linux-gnu"; + system = "x86_64-linux"; + }).system; + expected = "i686-linux"; + }; + test_elaborate_config_over_parsed = { + expr = + (lib.systems.elaborate { + config = "i686-unknown-linux-gnu"; + parsed = (lib.systems.elaborate "x86_64-linux").parsed; + }).parsed.cpu.arch; + expected = "i686"; + }; + test_elaborate_system_over_parsed = { + expr = + (lib.systems.elaborate { + system = "i686-linux"; + parsed = (lib.systems.elaborate "x86_64-linux").parsed; + }).parsed.cpu.arch; + expected = "i686"; + }; + } -# Generate test cases to assert that a change in any non-function attribute makes a platform unequal -// lib.concatMapAttrs (platformAttrName: origValue: { + # 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: "<>"; - 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; - staticEmulatorAvailable = null; - isCompatible = null; - }?${platformAttrName}; - }; + ${"test_equals_unequal_${platformAttrName}"} = + let + modified = + assert origValue != arbitraryValue; + lib.systems.elaborate "x86_64-linux" // { ${platformAttrName} = arbitraryValue; }; + arbitraryValue = x: "<>"; + 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; + staticEmulatorAvailable = null; + isCompatible = null; + } ? ${platformAttrName}; + }; -}) (lib.systems.elaborate "x86_64-linux" /* arbitrary choice, just to get all the elaborated attrNames */) + }) + ( + lib.systems.elaborate "x86_64-linux" # arbitrary choice, just to get all the elaborated attrNames + ) ) diff --git a/lib/trivial.nix b/lib/trivial.nix index d3ed7d9eda9e..eba6dd69b12e 100644 --- a/lib/trivial.nix +++ b/lib/trivial.nix @@ -11,11 +11,13 @@ let toBaseDigits version versionSuffix - warn; + warn + ; inherit (lib) isString ; -in { +in +{ ## Simple (higher order) functions @@ -23,7 +25,6 @@ in { The identity function For when you need a function that does “nothing”. - # Inputs `x` @@ -44,7 +45,6 @@ in { Ignores the second argument. If called with only one argument, constructs a function that always returns a static value. - # Inputs `x` @@ -72,9 +72,7 @@ in { ::: */ - const = - x: - y: x; + const = x: y: x; /** Pipes a value through a list of functions, left to right. @@ -140,7 +138,6 @@ in { /** Concatenate two lists - # Inputs `x` @@ -173,7 +170,6 @@ in { /** boolean “or” - # Inputs `x` @@ -189,7 +185,6 @@ in { /** boolean “and” - # Inputs `x` @@ -205,7 +200,6 @@ in { /** boolean “exclusive or” - # Inputs `x` @@ -232,7 +226,6 @@ in { boolean values. Calling `toString` on a bool instead returns "1" and "" (sic!). - # Inputs `b` @@ -252,7 +245,6 @@ in { mergeAttrs :: attrs -> attrs -> attrs - # Inputs `x` @@ -263,7 +255,6 @@ in { : Right attribute set (higher precedence for equal keys) - # Examples :::{.example} ## `lib.trivial.mergeAttrs` usage example @@ -275,14 +266,11 @@ in { ::: */ - mergeAttrs = - x: - y: x // y; + mergeAttrs = x: y: x // y; /** Flip the order of the arguments of a binary function. - # Inputs `f` @@ -314,12 +302,13 @@ in { ::: */ - flip = f: a: b: f b a; + flip = + f: a: b: + f b a; /** Return `maybeValue` if not null, otherwise return `default`. - # Inputs `default` @@ -330,7 +319,6 @@ in { : 2\. Function argument - # Examples :::{.example} ## `lib.trivial.defaultTo` usage example @@ -346,14 +334,11 @@ in { ::: */ - defaultTo = default: maybeValue: - if maybeValue != null then maybeValue - else default; + defaultTo = default: maybeValue: if maybeValue != null then maybeValue else default; /** Apply function if the supplied argument is non-null. - # Inputs `f` @@ -364,7 +349,6 @@ in { : Argument to check for null before passing it to `f` - # Examples :::{.example} ## `lib.trivial.mapNullable` usage example @@ -378,16 +362,25 @@ in { ::: */ - mapNullable = - f: - a: if a == null then a else f a; + mapNullable = f: a: if a == null then a else f a; # Pull in some builtins not included elsewhere. inherit (builtins) - pathExists readFile isBool - isInt isFloat add sub lessThan - seq deepSeq genericClosure - bitAnd bitOr bitXor; + pathExists + readFile + isBool + isInt + isFloat + add + sub + lessThan + seq + deepSeq + genericClosure + bitAnd + bitOr + bitXor + ; ## nixpkgs version strings @@ -422,7 +415,6 @@ in { Whether a feature is supported in all supported releases (at the time of release branch-off, if applicable). See `oldestSupportedRelease`. - # Inputs `release` @@ -433,15 +425,13 @@ in { isInOldestRelease = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2411) "lib.isInOldestRelease is deprecated. Use lib.oldestSupportedReleaseIsAtLeast instead." - lib.oldestSupportedReleaseIsAtLeast; + lib.oldestSupportedReleaseIsAtLeast; /** Alias for `isInOldestRelease` introduced in 24.11. Use `isInOldestRelease` in expressions outside of Nixpkgs for greater compatibility. - */ - oldestSupportedReleaseIsAtLeast = - release: - release <= lib.trivial.oldestSupportedRelease; + */ + oldestSupportedReleaseIsAtLeast = release: release <= lib.trivial.oldestSupportedRelease; /** Returns the current nixpkgs release code name. @@ -455,16 +445,15 @@ in { Returns the current nixpkgs version suffix as string. */ versionSuffix = - let suffixFile = ../.version-suffix; - in if pathExists suffixFile - then lib.strings.fileContents suffixFile - else "pre-git"; + let + suffixFile = ../.version-suffix; + in + if pathExists suffixFile then lib.strings.fileContents suffixFile else "pre-git"; /** Attempts to return the the current revision of nixpkgs and returns the supplied default value otherwise. - # Inputs `default` @@ -481,11 +470,14 @@ in { default: let revisionFile = "${toString ./..}/.git-revision"; - gitRepo = "${toString ./..}/.git"; - in if lib.pathIsGitRepo gitRepo - then lib.commitIdFromGitRepo gitRepo - else if lib.pathExists revisionFile then lib.fileContents revisionFile - else default; + gitRepo = "${toString ./..}/.git"; + in + if lib.pathIsGitRepo gitRepo then + lib.commitIdFromGitRepo gitRepo + else if lib.pathExists revisionFile then + lib.fileContents revisionFile + else + default; nixpkgsVersion = warn "lib.nixpkgsVersion is a deprecated alias of lib.version." version; @@ -512,14 +504,13 @@ in { inPureEvalMode :: bool ``` */ - inPureEvalMode = ! builtins ? currentSystem; + inPureEvalMode = !builtins ? currentSystem; ## Integer operations /** Return minimum of two numbers. - # Inputs `x` @@ -535,7 +526,6 @@ in { /** Return maximum of two numbers. - # Inputs `x` @@ -551,7 +541,6 @@ in { /** Integer modulus - # Inputs `base` @@ -562,7 +551,6 @@ in { : 2\. Function argument - # Examples :::{.example} ## `lib.trivial.mod` usage example @@ -578,7 +566,6 @@ in { */ mod = base: int: base - (int * (builtins.div base int)); - ## Comparisons /** @@ -588,7 +575,6 @@ in { a == b, compare a b => 0 a > b, compare a b => 1 - # Inputs `a` @@ -599,12 +585,14 @@ in { : 2\. Function argument */ - compare = a: b: - if a < b - then -1 - else if a > b - then 1 - else 0; + compare = + a: b: + if a < b then + -1 + else if a > b then + 1 + else + 0; /** Split type into two subtypes by predicate `p`, take all elements @@ -612,7 +600,6 @@ in { second subtype, compare elements of a single subtype with `yes` and `no` respectively. - # Inputs `p` @@ -661,10 +648,12 @@ in { */ splitByAndCompare = p: yes: no: a: b: - if p a - then if p b then yes a b else -1 - else if p b then 1 else no a b; - + if p a then + if p b then yes a b else -1 + else if p b then + 1 + else + no a b; /** Reads a JSON file. @@ -713,8 +702,7 @@ in { importJSON :: path -> any ``` */ - importJSON = path: - builtins.fromJSON (builtins.readFile path); + importJSON = path: builtins.fromJSON (builtins.readFile path); /** Reads a TOML file. @@ -761,11 +749,9 @@ in { importTOML :: path -> any ``` */ - importTOML = path: - builtins.fromTOML (builtins.readFile path); + importTOML = path: builtins.fromTOML (builtins.readFile path); /** - `warn` *`message`* *`value`* Print a warning before returning the second argument. @@ -792,19 +778,26 @@ in { warn = # Since Nix 2.23, https://github.com/NixOS/nix/pull/10592 builtins.warn or ( - let mustAbort = lib.elem (builtins.getEnv "NIX_ABORT_ON_WARN") ["1" "true" "yes"]; + let + mustAbort = lib.elem (builtins.getEnv "NIX_ABORT_ON_WARN") [ + "1" + "true" + "yes" + ]; in - # Do not eta reduce v, so that we have the same strictness as `builtins.warn`. - msg: v: - # `builtins.warn` requires a string message, so we enforce that in our implementation, so that callers aren't accidentally incompatible with newer Nix versions. - assert isString msg; - if mustAbort - then builtins.trace "evaluation warning: ${msg}" (abort "NIX_ABORT_ON_WARN=true; warnings are treated as unrecoverable errors.") - else builtins.trace "evaluation warning: ${msg}" v + # Do not eta reduce v, so that we have the same strictness as `builtins.warn`. + msg: v: + # `builtins.warn` requires a string message, so we enforce that in our implementation, so that callers aren't accidentally incompatible with newer Nix versions. + assert isString msg; + if mustAbort then + builtins.trace "evaluation warning: ${msg}" ( + abort "NIX_ABORT_ON_WARN=true; warnings are treated as unrecoverable errors." + ) + else + builtins.trace "evaluation warning: ${msg}" v ); /** - `warnIf` *`condition`* *`message`* *`value`* Like `warn`, but only warn when the first argument is `true`. @@ -832,7 +825,6 @@ in { warnIf = cond: msg: if cond then warn msg else x: x; /** - `warnIfNot` *`condition`* *`message`* *`value`* Like `warnIf`, but negated: warn if the first argument is `false`. @@ -870,7 +862,6 @@ in { Calls can be juxtaposed using function application, as `(r: r) a = a`, so `(r: r) (r: r) a = a`, and so forth. - # Inputs `cond` @@ -904,7 +895,6 @@ in { /** Like throwIfNot, but negated (throw if the first argument is `true`). - # Inputs `cond` @@ -926,7 +916,6 @@ in { /** Check if the elements in a list are valid values from a enum, returning the identity function, or throwing an error message otherwise. - # Inputs `msg` @@ -960,12 +949,13 @@ in { ::: */ - checkListOfEnum = msg: valid: given: + checkListOfEnum = + msg: valid: given: let unexpected = lib.subtractLists valid given; in - lib.throwIfNot (unexpected == []) - "${msg}: ${builtins.concatStringsSep ", " (builtins.map builtins.toString unexpected)} unexpected; valid ones: ${builtins.concatStringsSep ", " (builtins.map builtins.toString valid)}"; + lib.throwIfNot (unexpected == [ ]) + "${msg}: ${builtins.concatStringsSep ", " (builtins.map builtins.toString unexpected)} unexpected; valid ones: ${builtins.concatStringsSep ", " (builtins.map builtins.toString valid)}"; info = msg: builtins.trace "INFO: ${msg}"; @@ -984,7 +974,6 @@ in { function of the { a, b ? foo, ... }: format, but some facilities like callPackage expect to be able to query expected arguments. - # Inputs `f` @@ -995,11 +984,11 @@ in { : 2\. Function argument */ - setFunctionArgs = f: args: - { # TODO: Should we add call-time "type" checking like built in? - __functor = self: f; - __functionArgs = args; - }; + setFunctionArgs = f: args: { + # TODO: Should we add call-time "type" checking like built in? + __functor = self: f; + __functionArgs = args; + }; /** Extract the expected function arguments from a function. @@ -1008,37 +997,35 @@ in { has the same return type and semantics as builtins.functionArgs. setFunctionArgs : (a → b) → Map String Bool. - # Inputs `f` : 1\. Function argument */ - functionArgs = f: - if f ? __functor - then f.__functionArgs or (functionArgs (f.__functor f)) - else builtins.functionArgs f; + functionArgs = + f: + if f ? __functor then + f.__functionArgs or (functionArgs (f.__functor f)) + else + builtins.functionArgs f; /** Check whether something is a function or something annotated with function args. - # Inputs `f` : 1\. Function argument */ - isFunction = f: builtins.isFunction f || - (f ? __functor && isFunction (f.__functor f)); + isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f)); /** `mirrorFunctionArgs f g` creates a new function `g'` with the same behavior as `g` (`g' x == g x`) but its function arguments mirroring `f` (`lib.functionArgs g' == lib.functionArgs f`). - # Inputs `f` @@ -1084,21 +1071,18 @@ in { let fArgs = functionArgs f; in - g: - setFunctionArgs g fArgs; + g: setFunctionArgs g fArgs; /** Turns any non-callable values into constant functions. Returns callable values as is. - # Inputs `v` : Any value - # Examples :::{.example} ## `lib.trivial.toFunction` usage example @@ -1113,11 +1097,7 @@ in { ::: */ - toFunction = - v: - if isFunction v - then v - else k: v; + toFunction = v: if isFunction v then v else k: v; /** Convert a hexadecimal string to it's integer representation. @@ -1138,12 +1118,15 @@ in { => 9223372036854775807 ``` */ - fromHexString = value: - let - noPrefix = lib.strings.removePrefix "0x" (lib.strings.toLower value); - in let - parsed = builtins.fromTOML "v=0x${noPrefix}"; - in parsed.v; + fromHexString = + value: + let + noPrefix = lib.strings.removePrefix "0x" (lib.strings.toLower value); + in + let + parsed = builtins.fromTOML "v=0x${noPrefix}"; + in + parsed.v; /** Convert the given positive integer to a string of its hexadecimal @@ -1155,20 +1138,19 @@ in { toHexString 250 => "FA" */ - toHexString = let - hexDigits = { - "10" = "A"; - "11" = "B"; - "12" = "C"; - "13" = "D"; - "14" = "E"; - "15" = "F"; - }; - toHexDigit = d: - if d < 10 - then toString d - else hexDigits.${toString d}; - in i: lib.concatMapStrings toHexDigit (toBaseDigits 16 i); + toHexString = + let + hexDigits = { + "10" = "A"; + "11" = "B"; + "12" = "C"; + "13" = "D"; + "14" = "E"; + "15" = "F"; + }; + toHexDigit = d: if d < 10 then toString d else hexDigits.${toString d}; + in + i: lib.concatMapStrings toHexDigit (toBaseDigits 16 i); /** `toBaseDigits base i` converts the positive integer i to a list of its @@ -1180,7 +1162,6 @@ in { toBaseDigits 16 250 => [ 15 10 ] - # Inputs `base` @@ -1191,21 +1172,23 @@ in { : 2\. Function argument */ - toBaseDigits = base: i: + toBaseDigits = + base: i: let - go = i: - if i < base - then [i] + go = + i: + if i < base then + [ i ] else let r = i - ((i / base) * base); q = (i - r) / base; in - [r] ++ go q; + [ r ] ++ go q; in - assert (isInt base); - assert (isInt i); - assert (base >= 2); - assert (i >= 0); - lib.reverseList (go i); + assert (isInt base); + assert (isInt i); + assert (base >= 2); + assert (i >= 0); + lib.reverseList (go i); } diff --git a/lib/types.nix b/lib/types.nix index ee1410127b1a..11b1b5463bc7 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -67,1170 +67,1411 @@ let mergeOptionDecls ; - inAttrPosSuffix = v: name: - let pos = builtins.unsafeGetAttrPos name v; in + inAttrPosSuffix = + v: name: + let + pos = builtins.unsafeGetAttrPos name v; + in if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}"; # Internal functor to help for migrating functor.wrapped to functor.payload.elemType # Note that individual attributes can be overriden if needed. - elemTypeFunctor = name: { elemType, ... }@payload: { - inherit name payload; - wrappedDeprecationMessage = makeWrappedDeprecationMessage payload; - type = outer_types.types.${name}; - binOp = a: b: + elemTypeFunctor = + name: + { elemType, ... }@payload: + { + inherit name payload; + wrappedDeprecationMessage = makeWrappedDeprecationMessage payload; + type = outer_types.types.${name}; + binOp = + a: b: + let + merged = a.elemType.typeMerge b.elemType.functor; + in + if merged == null then null else { elemType = merged; }; + }; + makeWrappedDeprecationMessage = + payload: + { loc }: + lib.warn '' + The deprecated `${lib.optionalString (loc != null) "type."}functor.wrapped` attribute ${ + lib.optionalString (loc != null) "of the option `${showOption loc}` " + }is accessed, use `${lib.optionalString (loc != null) "type."}nestedTypes.elemType` instead. + '' payload.elemType; + + outer_types = rec { + isType = type: x: (x._type or "") == type; + + setType = + typeName: value: + value + // { + _type = typeName; + }; + + # Default type merging function + # takes two type functors and return the merged type + defaultTypeMerge = + f: f': let - merged = a.elemType.typeMerge b.elemType.functor; + mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor; + mergedPayload = f.binOp f.payload f'.payload; + + hasPayload = + assert (f'.payload != null) == (f.payload != null); + f.payload != null; + hasWrapped = + assert (f'.wrapped != null) == (f.wrapped != null); + f.wrapped != null; + + typeFromPayload = if mergedPayload == null then null else f.type mergedPayload; + typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped; in - if merged == null - then - null - else - { elemType = merged; }; - }; - makeWrappedDeprecationMessage = payload: { loc }: lib.warn '' - The deprecated `${lib.optionalString (loc != null) "type."}functor.wrapped` attribute ${lib.optionalString (loc != null) "of the option `${showOption loc}` "}is accessed, use `${lib.optionalString (loc != null) "type."}nestedTypes.elemType` instead. - '' payload.elemType; - - - outer_types = -rec { - isType = type: x: (x._type or "") == type; - - setType = typeName: value: value // { - _type = typeName; - }; - + # Abort early: cannot merge different types + if f.name != f'.name then + null + else - # Default type merging function - # takes two type functors and return the merged type - defaultTypeMerge = f: f': - let - mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor; - mergedPayload = f.binOp f.payload f'.payload; + if hasPayload then + # Just return the payload if returning wrapped is deprecated + if f ? wrappedDeprecationMessage then + typeFromPayload + else if hasWrapped then + # Has both wrapped and payload + throw '' + Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported. - hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null; - hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null; + Use either `functor.payload` or `functor.wrapped` but not both. - typeFromPayload = if mergedPayload == null then null else f.type mergedPayload; - typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped; - in - # Abort early: cannot merge different types - if f.name != f'.name - then null - else - - if hasPayload then - # Just return the payload if returning wrapped is deprecated - if f ? wrappedDeprecationMessage then - typeFromPayload + If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition. + '' + else + typeFromPayload else if hasWrapped then - # Has both wrapped and payload - throw '' - Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported. - - Use either `functor.payload` or `functor.wrapped` but not both. - - If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition. - '' - else - typeFromPayload - else - if hasWrapped then typeFromWrapped else f.type; - # Default type functor - defaultFunctor = name: { - inherit name; - type = types.${name} or null; - wrapped = null; - payload = null; - binOp = a: b: null; - }; - - isOptionType = isType "option-type"; - mkOptionType = - { # Human-readable representation of the type, should be equivalent to - # the type function name. - name - , # Description of the type, defined recursively by embedding the wrapped type if any. - description ? null - # A hint for whether or not this description needs parentheses. Possible values: - # - "noun": a noun phrase - # Example description: "positive integer", - # - "conjunction": a phrase with a potentially ambiguous "or" connective - # Example description: "int or string" - # - "composite": a phrase with an "of" connective - # Example description: "list of string" - # - "nonRestrictiveClause": a noun followed by a comma and a clause - # Example description: "positive integer, meaning >0" - # See the `optionDescriptionPhrase` function. - , descriptionClass ? null - , # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING! - # Function applied to each definition that must return false when a definition - # does not match the type. It should not check more than the root of the value, - # because checking nested values reduces laziness, leading to unnecessary - # infinite recursions in the module system. - # Further checks of nested values should be performed by throwing in - # the merge function. - # Strict and deep type checking can be performed by calling lib.deepSeq on - # the merged value. - # - # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change, - # https://github.com/NixOS/nixpkgs/pull/173568 and - # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this, - # https://github.com/NixOS/nixpkgs/issues/191124 and - # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore - # this disclaimer. - check ? (x: true) - , # Merge a list of definitions together into a single value. - # This function is called with two arguments: the location of - # the option in the configuration as a list of strings - # (e.g. ["boot" "loader "grub" "enable"]), and a list of - # definition values and locations (e.g. [ { file = "/foo.nix"; - # value = 1; } { file = "/bar.nix"; value = 2 } ]). - merge ? mergeDefaultOption - , # Whether this type has a value representing nothingness. If it does, - # this should be a value of the form { value = ; } - # If it doesn't, this should be {} - # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. - emptyValue ? {} - , # Return a flat attrset of sub-options. Used to generate - # documentation. - getSubOptions ? prefix: {} - , # List of modules if any, or null if none. - getSubModules ? null - , # Function for building the same option type with a different list of - # modules. - substSubModules ? m: null - , # Function that merge type declarations. - # internal, takes a functor as argument and returns the merged type. - # returning null means the type is not mergeable - typeMerge ? defaultTypeMerge functor - , # The type functor. - # internal, representation of the type as an attribute set. - # name: name of the type - # type: type function. - # wrapped: the type wrapped in case of compound types. - # payload: values of the type, two payloads of the same type must be - # combinable with the binOp binary operation. - # binOp: binary operation that merge two payloads of the same type. - functor ? defaultFunctor name - , # The deprecation message to display when this type is used by an option - # If null, the type isn't deprecated - deprecationMessage ? null - , # The types that occur in the definition of this type. This is used to - # issue deprecation warnings recursively. Can also be used to reuse - # nested types - nestedTypes ? {} - }: - { _type = "option-type"; - inherit - name check merge emptyValue getSubOptions getSubModules substSubModules - typeMerge deprecationMessage nestedTypes descriptionClass; - functor = - if functor ? wrappedDeprecationMessage then - functor // { - wrapped = functor.wrappedDeprecationMessage { - loc = null; - }; - } - else - functor; - description = if description == null then name else description; + # Default type functor + defaultFunctor = name: { + inherit name; + type = types.${name} or null; + wrapped = null; + payload = null; + binOp = a: b: null; }; - # optionDescriptionPhrase :: (str -> bool) -> optionType -> str - # - # Helper function for producing unambiguous but readable natural language - # descriptions of types. - # - # Parameters - # - # optionDescriptionPhase unparenthesize optionType - # - # `unparenthesize`: A function from descriptionClass string to boolean. - # It must return true when the class of phrase will fit unambiguously into - # the description of the caller. - # - # `optionType`: The option type to parenthesize or not. - # The option whose description we're returning. - # - # Return value - # - # The description of the `optionType`, with parentheses if there may be an - # ambiguity. - optionDescriptionPhrase = unparenthesize: t: - if unparenthesize (t.descriptionClass or null) - then t.description - else "(${t.description})"; - - # When adding new types don't forget to document them in - # nixos/doc/manual/development/option-types.section.md! - types = rec { - - raw = mkOptionType { - name = "raw"; - description = "raw value"; - descriptionClass = "noun"; - check = value: true; - merge = mergeOneOption; - }; + isOptionType = isType "option-type"; + mkOptionType = + { + # Human-readable representation of the type, should be equivalent to + # the type function name. + name, + # Description of the type, defined recursively by embedding the wrapped type if any. + description ? null, + # A hint for whether or not this description needs parentheses. Possible values: + # - "noun": a noun phrase + # Example description: "positive integer", + # - "conjunction": a phrase with a potentially ambiguous "or" connective + # Example description: "int or string" + # - "composite": a phrase with an "of" connective + # Example description: "list of string" + # - "nonRestrictiveClause": a noun followed by a comma and a clause + # Example description: "positive integer, meaning >0" + # See the `optionDescriptionPhrase` function. + descriptionClass ? null, + # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING! + # Function applied to each definition that must return false when a definition + # does not match the type. It should not check more than the root of the value, + # because checking nested values reduces laziness, leading to unnecessary + # infinite recursions in the module system. + # Further checks of nested values should be performed by throwing in + # the merge function. + # Strict and deep type checking can be performed by calling lib.deepSeq on + # the merged value. + # + # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change, + # https://github.com/NixOS/nixpkgs/pull/173568 and + # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this, + # https://github.com/NixOS/nixpkgs/issues/191124 and + # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore + # this disclaimer. + check ? (x: true), + # Merge a list of definitions together into a single value. + # This function is called with two arguments: the location of + # the option in the configuration as a list of strings + # (e.g. ["boot" "loader "grub" "enable"]), and a list of + # definition values and locations (e.g. [ { file = "/foo.nix"; + # value = 1; } { file = "/bar.nix"; value = 2 } ]). + merge ? mergeDefaultOption, + # Whether this type has a value representing nothingness. If it does, + # this should be a value of the form { value = ; } + # If it doesn't, this should be {} + # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`. + emptyValue ? { }, + # Return a flat attrset of sub-options. Used to generate + # documentation. + getSubOptions ? prefix: { }, + # List of modules if any, or null if none. + getSubModules ? null, + # Function for building the same option type with a different list of + # modules. + substSubModules ? m: null, + # Function that merge type declarations. + # internal, takes a functor as argument and returns the merged type. + # returning null means the type is not mergeable + typeMerge ? defaultTypeMerge functor, + # The type functor. + # internal, representation of the type as an attribute set. + # name: name of the type + # type: type function. + # wrapped: the type wrapped in case of compound types. + # payload: values of the type, two payloads of the same type must be + # combinable with the binOp binary operation. + # binOp: binary operation that merge two payloads of the same type. + functor ? defaultFunctor name, + # The deprecation message to display when this type is used by an option + # If null, the type isn't deprecated + deprecationMessage ? null, + # The types that occur in the definition of this type. This is used to + # issue deprecation warnings recursively. Can also be used to reuse + # nested types + nestedTypes ? { }, + }: + { + _type = "option-type"; + inherit + name + check + merge + emptyValue + getSubOptions + getSubModules + substSubModules + typeMerge + deprecationMessage + nestedTypes + descriptionClass + ; + functor = + if functor ? wrappedDeprecationMessage then + functor + // { + wrapped = functor.wrappedDeprecationMessage { + loc = null; + }; + } + else + functor; + description = if description == null then name else description; + }; - anything = mkOptionType { - name = "anything"; - description = "anything"; - descriptionClass = "noun"; - check = value: true; - merge = loc: defs: - let - getType = value: - if isAttrs value && isStringLike value - then "stringCoercibleSet" - else builtins.typeOf value; - - # Returns the common type of all definitions, throws an error if they - # don't have the same type - commonType = foldl' (type: def: - if getType def.value == type - then type - else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" - ) (getType (head defs).value) defs; - - mergeFunction = { - # Recursively merge attribute sets - set = (attrsOf anything).merge; - # This is the type of packages, only accept a single definition - stringCoercibleSet = mergeOneOption; - lambda = loc: defs: arg: anything.merge - (loc ++ [ "" ]) - (map (def: { - file = def.file; - value = def.value arg; - }) defs); - # Otherwise fall back to only allowing all equal definitions - }.${commonType} or mergeEqualOption; - in mergeFunction loc defs; - }; + # optionDescriptionPhrase :: (str -> bool) -> optionType -> str + # + # Helper function for producing unambiguous but readable natural language + # descriptions of types. + # + # Parameters + # + # optionDescriptionPhase unparenthesize optionType + # + # `unparenthesize`: A function from descriptionClass string to boolean. + # It must return true when the class of phrase will fit unambiguously into + # the description of the caller. + # + # `optionType`: The option type to parenthesize or not. + # The option whose description we're returning. + # + # Return value + # + # The description of the `optionType`, with parentheses if there may be an + # ambiguity. + optionDescriptionPhrase = + unparenthesize: t: + if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})"; + + # When adding new types don't forget to document them in + # nixos/doc/manual/development/option-types.section.md! + types = rec { + + raw = mkOptionType { + name = "raw"; + description = "raw value"; + descriptionClass = "noun"; + check = value: true; + merge = mergeOneOption; + }; - unspecified = mkOptionType { - name = "unspecified"; - description = "unspecified value"; - descriptionClass = "noun"; - }; + anything = mkOptionType { + name = "anything"; + description = "anything"; + descriptionClass = "noun"; + check = value: true; + merge = + loc: defs: + let + getType = + value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value; + + # Returns the common type of all definitions, throws an error if they + # don't have the same type + commonType = foldl' ( + type: def: + if getType def.value == type then + type + else + throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" + ) (getType (head defs).value) defs; + + mergeFunction = + { + # Recursively merge attribute sets + set = (attrsOf anything).merge; + # This is the type of packages, only accept a single definition + stringCoercibleSet = mergeOneOption; + lambda = + loc: defs: arg: + anything.merge (loc ++ [ "" ]) ( + map (def: { + file = def.file; + value = def.value arg; + }) defs + ); + # Otherwise fall back to only allowing all equal definitions + } + .${commonType} or mergeEqualOption; + in + mergeFunction loc defs; + }; - bool = mkOptionType { - name = "bool"; - description = "boolean"; - descriptionClass = "noun"; - check = isBool; - merge = mergeEqualOption; - }; + unspecified = mkOptionType { + name = "unspecified"; + description = "unspecified value"; + descriptionClass = "noun"; + }; + + bool = mkOptionType { + name = "bool"; + description = "boolean"; + descriptionClass = "noun"; + check = isBool; + merge = mergeEqualOption; + }; - boolByOr = mkOptionType { - name = "boolByOr"; - description = "boolean (merged using or)"; - descriptionClass = "noun"; - check = isBool; - merge = loc: defs: - foldl' - (result: def: + boolByOr = mkOptionType { + name = "boolByOr"; + description = "boolean (merged using or)"; + descriptionClass = "noun"; + check = isBool; + merge = + loc: defs: + foldl' ( + result: def: # Under the assumption that .check always runs before merge, we can assume that all defs.*.value # have been forced, and therefore we assume we don't introduce order-dependent strictness here result || def.value - ) - false - defs; - }; + ) false defs; + }; - int = mkOptionType { - name = "int"; - description = "signed integer"; - descriptionClass = "noun"; - check = isInt; - merge = mergeEqualOption; - }; + int = mkOptionType { + name = "int"; + description = "signed integer"; + descriptionClass = "noun"; + check = isInt; + merge = mergeEqualOption; + }; - # Specialized subdomains of int - ints = - let - betweenDesc = lowest: highest: - "${toString lowest} and ${toString highest} (both inclusive)"; - between = lowest: highest: - assert lib.assertMsg (lowest <= highest) - "ints.between: lowest must be smaller than highest"; - addCheck int (x: x >= lowest && x <= highest) // { - name = "intBetween"; - description = "integer between ${betweenDesc lowest highest}"; + # Specialized subdomains of int + ints = + let + betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)"; + between = + lowest: highest: + assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest"; + addCheck int (x: x >= lowest && x <= highest) + // { + name = "intBetween"; + description = "integer between ${betweenDesc lowest highest}"; + }; + ign = + lowest: highest: name: docStart: + between lowest highest + // { + inherit name; + description = docStart + "; between ${betweenDesc lowest highest}"; + }; + unsign = + bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; + sign = + bit: range: + ign (0 - (range / 2)) ( + range / 2 - 1 + ) "signedInt${toString bit}" "${toString bit} bit signed integer"; + + in + { + # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md + /** + An int with a fixed range. + + # Example + :::{.example} + ## `lib.types.ints.between` usage example + + ```nix + (ints.between 0 100).check (-1) + => false + (ints.between 0 100).check (101) + => false + (ints.between 0 0).check 0 + => true + ``` + + ::: + */ + inherit between; + + unsigned = addCheck types.int (x: x >= 0) // { + name = "unsignedInt"; + description = "unsigned integer, meaning >=0"; + descriptionClass = "nonRestrictiveClause"; }; - ign = lowest: highest: name: docStart: - between lowest highest // { - inherit name; - description = docStart + "; between ${betweenDesc lowest highest}"; + positive = addCheck types.int (x: x > 0) // { + name = "positiveInt"; + description = "positive integer, meaning >0"; + descriptionClass = "nonRestrictiveClause"; }; - unsign = bit: range: ign 0 (range - 1) - "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; - sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1) - "signedInt${toString bit}" "${toString bit} bit signed integer"; - - in { - # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md - /** - An int with a fixed range. - - # Example - :::{.example} - ## `lib.types.ints.between` usage example - - ```nix - (ints.between 0 100).check (-1) - => false - (ints.between 0 100).check (101) - => false - (ints.between 0 0).check 0 - => true - ``` - - ::: - */ - inherit between; - - unsigned = addCheck types.int (x: x >= 0) // { - name = "unsignedInt"; - description = "unsigned integer, meaning >=0"; - descriptionClass = "nonRestrictiveClause"; - }; - positive = addCheck types.int (x: x > 0) // { - name = "positiveInt"; - description = "positive integer, meaning >0"; - descriptionClass = "nonRestrictiveClause"; + u8 = unsign 8 256; + u16 = unsign 16 65536; + # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808) + # the smallest int Nix accepts is -2^63 (-9223372036854775807) + u32 = unsign 32 4294967296; + # u64 = unsign 64 18446744073709551616; + + s8 = sign 8 256; + s16 = sign 16 65536; + s32 = sign 32 4294967296; }; - u8 = unsign 8 256; - u16 = unsign 16 65536; - # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808) - # the smallest int Nix accepts is -2^63 (-9223372036854775807) - u32 = unsign 32 4294967296; - # u64 = unsign 64 18446744073709551616; - - s8 = sign 8 256; - s16 = sign 16 65536; - s32 = sign 32 4294967296; - }; - # Alias of u16 for a port number - port = ints.u16; + # Alias of u16 for a port number + port = ints.u16; - float = mkOptionType { - name = "float"; - description = "floating point number"; - descriptionClass = "noun"; - check = isFloat; - merge = mergeEqualOption; - }; - - number = either int float; - - numbers = let - betweenDesc = lowest: highest: - "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)"; - in { - between = lowest: highest: - assert lib.assertMsg (lowest <= highest) - "numbers.between: lowest must be smaller than highest"; - addCheck number (x: x >= lowest && x <= highest) // { - name = "numberBetween"; - description = "integer or floating point number between ${betweenDesc lowest highest}"; - }; - - nonnegative = addCheck number (x: x >= 0) // { - name = "numberNonnegative"; - description = "nonnegative integer or floating point number, meaning >=0"; - descriptionClass = "nonRestrictiveClause"; - }; - positive = addCheck number (x: x > 0) // { - name = "numberPositive"; - description = "positive integer or floating point number, meaning >0"; - descriptionClass = "nonRestrictiveClause"; + float = mkOptionType { + name = "float"; + description = "floating point number"; + descriptionClass = "noun"; + check = isFloat; + merge = mergeEqualOption; }; - }; - str = mkOptionType { - name = "str"; - description = "string"; - descriptionClass = "noun"; - check = isString; - merge = mergeEqualOption; - }; + number = either int float; - nonEmptyStr = mkOptionType { - name = "nonEmptyStr"; - description = "non-empty string"; - descriptionClass = "noun"; - check = x: str.check x && builtins.match "[ \t\n]*" x == null; - inherit (str) merge; - }; + numbers = + let + betweenDesc = + lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)"; + in + { + between = + lowest: highest: + assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest"; + addCheck number (x: x >= lowest && x <= highest) + // { + name = "numberBetween"; + description = "integer or floating point number between ${betweenDesc lowest highest}"; + }; - # Allow a newline character at the end and trim it in the merge function. - singleLineStr = - let - inherit (strMatching "[^\n\r]*\n?") check merge; - in - mkOptionType { - name = "singleLineStr"; - description = "(optionally newline-terminated) single-line string"; + nonnegative = addCheck number (x: x >= 0) // { + name = "numberNonnegative"; + description = "nonnegative integer or floating point number, meaning >=0"; + descriptionClass = "nonRestrictiveClause"; + }; + positive = addCheck number (x: x > 0) // { + name = "numberPositive"; + description = "positive integer or floating point number, meaning >0"; + descriptionClass = "nonRestrictiveClause"; + }; + }; + + str = mkOptionType { + name = "str"; + description = "string"; descriptionClass = "noun"; - inherit check; - merge = loc: defs: - lib.removeSuffix "\n" (merge loc defs); + check = isString; + merge = mergeEqualOption; }; - strMatching = pattern: mkOptionType { - name = "strMatching ${escapeNixString pattern}"; - description = "string matching the pattern ${pattern}"; - descriptionClass = "noun"; - check = x: str.check x && builtins.match pattern x != null; - inherit (str) merge; - functor = defaultFunctor "strMatching" // { - type = payload: strMatching payload.pattern; - payload = { inherit pattern; }; - binOp = lhs: rhs: if lhs == rhs then lhs else null; + nonEmptyStr = mkOptionType { + name = "nonEmptyStr"; + description = "non-empty string"; + descriptionClass = "noun"; + check = x: str.check x && builtins.match "[ \t\n]*" x == null; + inherit (str) merge; }; - }; - # Merge multiple definitions by concatenating them (with the given - # separator between the values). - separatedString = sep: mkOptionType rec { - name = "separatedString"; - description = if sep == "" - then "Concatenated string" # for types.string. - else "strings concatenated with ${builtins.toJSON sep}" - ; - descriptionClass = "noun"; - check = isString; - merge = loc: defs: concatStringsSep sep (getValues defs); - functor = (defaultFunctor name) // { - payload = { inherit sep; }; - type = payload: types.separatedString payload.sep; - binOp = lhs: rhs: - if lhs.sep == rhs.sep then { inherit (lhs) sep; } - else null; - }; - }; + # Allow a newline character at the end and trim it in the merge function. + singleLineStr = + let + inherit (strMatching "[^\n\r]*\n?") check merge; + in + mkOptionType { + name = "singleLineStr"; + description = "(optionally newline-terminated) single-line string"; + descriptionClass = "noun"; + inherit check; + merge = loc: defs: lib.removeSuffix "\n" (merge loc defs); + }; - lines = separatedString "\n"; - commas = separatedString ","; - envVar = separatedString ":"; - - # Deprecated; should not be used because it quietly concatenates - # strings, which is usually not what you want. - # We use a lib.warn because `deprecationMessage` doesn't trigger in nested types such as `attrsOf string` - string = lib.warn - "The type `types.string` is deprecated. See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types." - (separatedString "" // { - name = "string"; - }); - - passwdEntry = entryType: addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) // { - name = "passwdEntry ${entryType.name}"; - description = "${optionDescriptionPhrase (class: class == "noun") entryType}, not containing newlines or colons"; - descriptionClass = "nonRestrictiveClause"; - }; + strMatching = + pattern: + mkOptionType { + name = "strMatching ${escapeNixString pattern}"; + description = "string matching the pattern ${pattern}"; + descriptionClass = "noun"; + check = x: str.check x && builtins.match pattern x != null; + inherit (str) merge; + functor = defaultFunctor "strMatching" // { + type = payload: strMatching payload.pattern; + payload = { inherit pattern; }; + binOp = lhs: rhs: if lhs == rhs then lhs else null; + }; + }; - attrs = mkOptionType { - name = "attrs"; - description = "attribute set"; - check = isAttrs; - merge = loc: foldl' (res: def: res // def.value) {}; - emptyValue = { value = {}; }; - }; + # Merge multiple definitions by concatenating them (with the given + # separator between the values). + separatedString = + sep: + mkOptionType rec { + name = "separatedString"; + description = + if sep == "" then + "Concatenated string" # for types.string. + else + "strings concatenated with ${builtins.toJSON sep}"; + descriptionClass = "noun"; + check = isString; + merge = loc: defs: concatStringsSep sep (getValues defs); + functor = (defaultFunctor name) // { + payload = { inherit sep; }; + type = payload: types.separatedString payload.sep; + binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null; + }; + }; - # A package is a top-level store path (/nix/store/hash-name). This includes: - # - derivations - # - more generally, attribute sets with an `outPath` or `__toString` attribute - # pointing to a store path, e.g. flake inputs - # - 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"; - check = x: isDerivation x || isStorePath x; - merge = loc: defs: - let res = mergeOneOption loc defs; - in if builtins.isPath res || (builtins.isString res && ! builtins.hasContext res) - then toDerivation res - else res; - }; + lines = separatedString "\n"; + commas = separatedString ","; + envVar = separatedString ":"; + + # Deprecated; should not be used because it quietly concatenates + # strings, which is usually not what you want. + # We use a lib.warn because `deprecationMessage` doesn't trigger in nested types such as `attrsOf string` + string = + lib.warn + "The type `types.string` is deprecated. See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types." + ( + separatedString "" + // { + name = "string"; + } + ); + + passwdEntry = + entryType: + addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) + // { + name = "passwdEntry ${entryType.name}"; + description = "${ + optionDescriptionPhrase (class: class == "noun") entryType + }, not containing newlines or colons"; + descriptionClass = "nonRestrictiveClause"; + }; - shellPackage = package // { - check = x: isDerivation x && hasAttr "shellPath" x; - }; + attrs = mkOptionType { + name = "attrs"; + description = "attribute set"; + check = isAttrs; + merge = loc: foldl' (res: def: res // def.value) { }; + emptyValue = { + value = { }; + }; + }; - pkgs = addCheck - (unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs // { - name = "pkgs"; + # A package is a top-level store path (/nix/store/hash-name). This includes: + # - derivations + # - more generally, attribute sets with an `outPath` or `__toString` attribute + # pointing to a store path, e.g. flake inputs + # - 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"; - description = "Nixpkgs package set"; - }) - (x: (x._type or null) == "pkgs"); - - path = pathWith { - absolute = true; - }; + check = x: isDerivation x || isStorePath x; + merge = + loc: defs: + let + res = mergeOneOption loc defs; + in + if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then + toDerivation res + else + res; + }; - pathInStore = pathWith { - inStore = true; - }; + shellPackage = package // { + check = x: isDerivation x && hasAttr "shellPath" x; + }; - pathWith = { - inStore ? null, - absolute ? null, - }: - throwIf (inStore != null && absolute != null && inStore && !absolute) "In pathWith, inStore means the path must be absolute" mkOptionType { - name = "path"; - description = ( - (if absolute == null then "" else (if absolute then "absolute " else "relative ")) + - "path" + - (if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store")) - ); - descriptionClass = "noun"; + pkgs = addCheck ( + unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs + // { + name = "pkgs"; + descriptionClass = "noun"; + description = "Nixpkgs package set"; + } + ) (x: (x._type or null) == "pkgs"); + + path = pathWith { + absolute = true; + }; - merge = mergeEqualOption; - functor = defaultFunctor "path" // { - type = pathWith; - payload = {inherit inStore absolute; }; - binOp = lhs: rhs: if lhs == rhs then lhs else null; - }; + pathInStore = pathWith { + inStore = true; + }; - check = x: - let - isInStore = builtins.match "${builtins.storeDir}/[^.].*" (toString x) != null; - isAbsolute = builtins.substring 0 1 (toString x) == "/"; - isExpectedType = ( - if inStore == null || inStore then - isStringLike x - else - isString x # Do not allow a true path, which could be copied to the store later on. + pathWith = + { + inStore ? null, + absolute ? null, + }: + throwIf (inStore != null && absolute != null && inStore && !absolute) + "In pathWith, inStore means the path must be absolute" + mkOptionType + { + name = "path"; + description = ( + (if absolute == null then "" else (if absolute then "absolute " else "relative ")) + + "path" + + ( + if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store") + ) ); - in - isExpectedType && (inStore == null || inStore == isInStore) && (absolute == null || absolute == isAbsolute); - }; + descriptionClass = "noun"; - listOf = elemType: mkOptionType rec { - name = "listOf"; - description = "list of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; - descriptionClass = "composite"; - check = isList; - merge = loc: defs: - map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: - imap1 (m: def': - (mergeDefinitions - (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) - elemType - [{ inherit (def) file; value = def'; }] - ).optionalValue - ) def.value - ) defs))); - emptyValue = { value = []; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); - getSubModules = elemType.getSubModules; - substSubModules = m: listOf (elemType.substSubModules m); - functor = (elemTypeFunctor name { inherit elemType; }) // { - type = payload: types.listOf payload.elemType; - }; - nestedTypes.elemType = elemType; - }; + merge = mergeEqualOption; + functor = defaultFunctor "path" // { + type = pathWith; + payload = { inherit inStore absolute; }; + binOp = lhs: rhs: if lhs == rhs then lhs else null; + }; - nonEmptyListOf = elemType: - let list = addCheck (types.listOf elemType) (l: l != []); - in list // { - description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}"; - emptyValue = { }; # no .value attr, meaning unset - substSubModules = m: nonEmptyListOf (elemType.substSubModules m); - }; + check = + x: + let + isInStore = builtins.match "${builtins.storeDir}/[^.].*" (toString x) != null; + isAbsolute = builtins.substring 0 1 (toString x) == "/"; + isExpectedType = ( + if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on. + ); + in + isExpectedType + && (inStore == null || inStore == isInStore) + && (absolute == null || absolute == isAbsolute); + }; + + listOf = + elemType: + mkOptionType rec { + name = "listOf"; + description = "list of ${ + optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType + }"; + descriptionClass = "composite"; + check = isList; + merge = + loc: defs: + map (x: x.value) ( + filter (x: x ? value) ( + concatLists ( + imap1 ( + n: def: + imap1 ( + m: def': + (mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [ + { + inherit (def) file; + value = def'; + } + ]).optionalValue + ) def.value + ) defs + ) + ) + ); + emptyValue = { + value = [ ]; + }; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]); + getSubModules = elemType.getSubModules; + substSubModules = m: listOf (elemType.substSubModules m); + functor = (elemTypeFunctor name { inherit elemType; }) // { + type = payload: types.listOf payload.elemType; + }; + nestedTypes.elemType = elemType; + }; - attrsOf = elemType: attrsWith { inherit elemType; }; + nonEmptyListOf = + elemType: + let + list = addCheck (types.listOf elemType) (l: l != [ ]); + in + list + // { + description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}"; + emptyValue = { }; # no .value attr, meaning unset + substSubModules = m: nonEmptyListOf (elemType.substSubModules m); + }; - # A version of attrsOf that's lazy in its values at the expense of - # conditional definitions not working properly. E.g. defining a value with - # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with - # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an - # error that it's not defined. Use only if conditional definitions don't make sense. - lazyAttrsOf = elemType: attrsWith { inherit elemType; lazy = true; }; + attrsOf = elemType: attrsWith { inherit elemType; }; + + # A version of attrsOf that's lazy in its values at the expense of + # conditional definitions not working properly. E.g. defining a value with + # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with + # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an + # error that it's not defined. Use only if conditional definitions don't make sense. + lazyAttrsOf = + elemType: + attrsWith { + inherit elemType; + lazy = true; + }; - # base type for lazyAttrsOf and attrsOf - attrsWith = - let - # Push down position info. - pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value); - binOp = lhs: rhs: + # base type for lazyAttrsOf and attrsOf + attrsWith = let - elemType = lhs.elemType.typeMerge rhs.elemType.functor; - lazy = - if lhs.lazy == rhs.lazy then - lhs.lazy - else - null; - placeholder = - if lhs.placeholder == rhs.placeholder then - lhs.placeholder - else if lhs.placeholder == "name" then - rhs.placeholder - else if rhs.placeholder == "name" then - lhs.placeholder + # Push down position info. + pushPositions = map ( + def: + mapAttrs (n: v: { + inherit (def) file; + value = v; + }) def.value + ); + binOp = + lhs: rhs: + let + elemType = lhs.elemType.typeMerge rhs.elemType.functor; + lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null; + placeholder = + if lhs.placeholder == rhs.placeholder then + lhs.placeholder + else if lhs.placeholder == "name" then + rhs.placeholder + else if rhs.placeholder == "name" then + lhs.placeholder + else + null; + in + if elemType == null || lazy == null || placeholder == null then + null else - null; + { + inherit elemType lazy placeholder; + }; in - if elemType == null || lazy == null || placeholder == null then - null - else - { - inherit elemType lazy placeholder; + { + elemType, + lazy ? false, + placeholder ? "name", + }: + mkOptionType { + name = if lazy then "lazyAttrsOf" else "attrsOf"; + description = + (if lazy then "lazy attribute set" else "attribute set") + + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; + descriptionClass = "composite"; + check = isAttrs; + merge = + if lazy then + ( + # Lazy merge Function + loc: defs: + zipAttrsWith + ( + name: defs: + let + merged = mergeDefinitions (loc ++ [ name ]) elemType defs; + # mergedValue will trigger an appropriate error when accessed + in + merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue + ) + # Push down position info. + (pushPositions defs) + ) + else + ( + # Non-lazy merge Function + loc: defs: + mapAttrs (n: v: v.value) ( + filterAttrs (n: v: v ? value) ( + zipAttrsWith (name: defs: (mergeDefinitions (loc ++ [ name ]) elemType (defs)).optionalValue) + # Push down position info. + (pushPositions defs) + ) + ) + ); + emptyValue = { + value = { }; }; - in - { - elemType, - lazy ? false, - placeholder ? "name", - }: - mkOptionType { - name = if lazy then "lazyAttrsOf" else "attrsOf"; - description = (if lazy then "lazy attribute set" else "attribute set") + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; - descriptionClass = "composite"; - check = isAttrs; - merge = if lazy then ( - # Lazy merge Function - loc: defs: - zipAttrsWith (name: defs: - let merged = mergeDefinitions (loc ++ [name]) elemType defs; - # mergedValue will trigger an appropriate error when accessed - in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue - ) - # Push down position info. - (pushPositions defs) - ) else ( - # Non-lazy merge Function - loc: defs: - mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: - (mergeDefinitions (loc ++ [name]) elemType (defs)).optionalValue - ) - # Push down position info. - (pushPositions defs))) - ); - emptyValue = { value = {}; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${placeholder}>"]); - getSubModules = elemType.getSubModules; - substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy placeholder; }; - functor = (elemTypeFunctor "attrsWith" { - inherit elemType lazy placeholder; - }) // { - # Custom type merging required because of the "placeholder" attribute - inherit binOp; - }; - nestedTypes.elemType = elemType; - }; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]); + getSubModules = elemType.getSubModules; + substSubModules = + m: + attrsWith { + elemType = elemType.substSubModules m; + inherit lazy placeholder; + }; + functor = + (elemTypeFunctor "attrsWith" { + inherit elemType lazy placeholder; + }) + // { + # Custom type merging required because of the "placeholder" attribute + inherit binOp; + }; + nestedTypes.elemType = elemType; + }; - # TODO: deprecate this in the future: - loaOf = elemType: types.attrsOf elemType // { - name = "loaOf"; - deprecationMessage = "Mixing lists with attribute values is no longer" - + " possible; please use `types.attrsOf` instead. See" - + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; - nestedTypes.elemType = elemType; - }; + # TODO: deprecate this in the future: + loaOf = + elemType: + types.attrsOf elemType + // { + name = "loaOf"; + deprecationMessage = + "Mixing lists with attribute values is no longer" + + " possible; please use `types.attrsOf` instead. See" + + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; + nestedTypes.elemType = elemType; + }; - attrTag = tags: - let tags_ = tags; in - let - tags = - mapAttrs - (n: opt: - builtins.addErrorContext "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}" ( + attrTag = + tags: + let + tags_ = tags; + in + let + tags = mapAttrs ( + n: opt: + builtins.addErrorContext + "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}" + ( throwIf (opt._type or null != "option") "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${ - if opt?_type then - if opt._type == "option-type" - then "was a bare type, not wrapped in mkOption." - else "was of type ${lib.strings.escapeNixString opt._type}." - else "was not."}" - opt // { - declarations = opt.declarations or ( - let pos = builtins.unsafeGetAttrPos n tags_; - in if pos == null then [] else [ pos.file ] - ); - declarationPositions = opt.declarationPositions or ( - let pos = builtins.unsafeGetAttrPos n tags_; - in if pos == null then [] else [ pos ] - ); + if opt ? _type then + if opt._type == "option-type" then + "was a bare type, not wrapped in mkOption." + else + "was of type ${lib.strings.escapeNixString opt._type}." + else + "was not." + }" + opt + // { + declarations = + opt.declarations or ( + let + pos = builtins.unsafeGetAttrPos n tags_; + in + if pos == null then [ ] else [ pos.file ] + ); + declarationPositions = + opt.declarationPositions or ( + let + pos = builtins.unsafeGetAttrPos n tags_; + in + if pos == null then [ ] else [ pos ] + ); } - )) - tags_; - choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags); - in - mkOptionType { - name = "attrTag"; - description = "attribute-tagged union"; - descriptionClass = "noun"; - getSubOptions = prefix: - mapAttrs - (tagName: tagOption: { - "${lib.showOption prefix}" = - tagOption // { - loc = prefix ++ [ tagName ]; - }; - }) - tags; - check = v: isAttrs v && length (attrNames v) == 1 && tags?${head (attrNames v)}; - merge = loc: defs: - let - choice = head (attrNames (head defs).value); - checkedValueDefs = map - (def: - assert (length (attrNames def.value)) == 1; - if (head (attrNames def.value)) != choice - then throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}." - else { inherit (def) file; value = def.value.${choice}; }) - defs; - in - if tags?${choice} - then - { ${choice} = - (lib.modules.evalOptionValue - (loc ++ [choice]) - tags.${choice} - checkedValueDefs - ).value; - } - else throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}."; - nestedTypes = tags; - functor = defaultFunctor "attrTag" // { - type = { tags, ... }: types.attrTag tags; - payload = { inherit tags; }; - binOp = + ) + ) tags_; + choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags); + in + mkOptionType { + name = "attrTag"; + description = "attribute-tagged union"; + descriptionClass = "noun"; + getSubOptions = + prefix: + mapAttrs (tagName: tagOption: { + "${lib.showOption prefix}" = tagOption // { + loc = prefix ++ [ tagName ]; + }; + }) tags; + check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)}; + merge = + loc: defs: let - # Add metadata in the format that submodules work with - wrapOptionDecl = - option: { options = option; _file = ""; pos = null; }; + choice = head (attrNames (head defs).value); + checkedValueDefs = map ( + def: + assert (length (attrNames def.value)) == 1; + if (head (attrNames def.value)) != choice then + throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}." + else + { + inherit (def) file; + value = def.value.${choice}; + } + ) defs; in - a: b: { - tags = a.tags // b.tags // - mapAttrs - (tagName: bOpt: + if tags ? ${choice} then + { + ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value; + } + else + throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}."; + nestedTypes = tags; + functor = defaultFunctor "attrTag" // { + type = { tags, ... }: types.attrTag tags; + payload = { inherit tags; }; + binOp = + let + # Add metadata in the format that submodules work with + wrapOptionDecl = option: { + options = option; + _file = ""; + pos = null; + }; + in + a: b: { + tags = + a.tags + // b.tags + // mapAttrs ( + tagName: bOpt: lib.mergeOptionDecls # FIXME: loc is not accurate; should include prefix # Fortunately, it's only used for error messages, where a "relative" location is kinda ok. # It is also returned though, but use of the attribute seems rare? - [tagName] - [ (wrapOptionDecl a.tags.${tagName}) (wrapOptionDecl bOpt) ] + [ tagName ] + [ + (wrapOptionDecl a.tags.${tagName}) + (wrapOptionDecl bOpt) + ] // { # mergeOptionDecls is not idempotent in these attrs: declarations = a.tags.${tagName}.declarations ++ bOpt.declarations; declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions; } - ) - (builtins.intersectAttrs a.tags b.tags); + ) (builtins.intersectAttrs a.tags b.tags); + }; }; }; - }; - - # A value produced by `lib.mkLuaInline` - luaInline = mkOptionType { - name = "luaInline"; - description = "inline lua"; - descriptionClass = "noun"; - check = x: x._type or null == "lua-inline"; - merge = mergeEqualOption; - }; - uniq = unique { message = ""; }; - - unique = { message }: type: mkOptionType rec { - name = "unique"; - inherit (type) description descriptionClass check; - merge = mergeUniqueOption { inherit message; inherit (type) merge; }; - emptyValue = type.emptyValue; - getSubOptions = type.getSubOptions; - getSubModules = type.getSubModules; - substSubModules = m: uniq (type.substSubModules m); - functor = elemTypeFunctor name { elemType = type; } // { - type = payload: types.unique { inherit message; } payload.elemType; + # A value produced by `lib.mkLuaInline` + luaInline = mkOptionType { + name = "luaInline"; + description = "inline lua"; + descriptionClass = "noun"; + check = x: x._type or null == "lua-inline"; + merge = mergeEqualOption; }; - nestedTypes.elemType = type; - }; - # Null or value of ... - nullOr = elemType: mkOptionType rec { - name = "nullOr"; - description = "null or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType}"; - descriptionClass = "conjunction"; - check = x: x == null || elemType.check x; - merge = loc: defs: - let nrNulls = count (def: def.value == null) defs; in - if nrNulls == length defs then null - else if nrNulls != 0 then - throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." - else elemType.merge loc defs; - emptyValue = { value = null; }; - getSubOptions = elemType.getSubOptions; - getSubModules = elemType.getSubModules; - substSubModules = m: nullOr (elemType.substSubModules m); - functor = (elemTypeFunctor name { inherit elemType; }) // { - type = payload: types.nullOr payload.elemType; - }; - nestedTypes.elemType = elemType; - }; + uniq = unique { message = ""; }; + + unique = + { message }: + type: + mkOptionType rec { + name = "unique"; + inherit (type) description descriptionClass check; + merge = mergeUniqueOption { + inherit message; + inherit (type) merge; + }; + emptyValue = type.emptyValue; + getSubOptions = type.getSubOptions; + getSubModules = type.getSubModules; + substSubModules = m: uniq (type.substSubModules m); + functor = elemTypeFunctor name { elemType = type; } // { + type = payload: types.unique { inherit message; } payload.elemType; + }; + nestedTypes.elemType = type; + }; - functionTo = elemType: mkOptionType { - name = "functionTo"; - description = "function that evaluates to a(n) ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; - descriptionClass = "composite"; - check = isFunction; - merge = loc: defs: { - # An argument attribute has a default when it has a default in all definitions - __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) ( - lib.map (fn: lib.functionArgs fn.value) defs - ); - __functor = _: callerArgs: (mergeDefinitions (loc ++ [ "" ]) elemType (map (fn: { inherit (fn) file; value = fn.value callerArgs; }) defs)).mergedValue; - }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "" ]); - getSubModules = elemType.getSubModules; - substSubModules = m: functionTo (elemType.substSubModules m); - functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // { - type = payload: types.functionTo payload.elemType; - }; - nestedTypes.elemType = elemType; - }; + # Null or value of ... + nullOr = + elemType: + mkOptionType rec { + name = "nullOr"; + description = "null or ${ + optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType + }"; + descriptionClass = "conjunction"; + check = x: x == null || elemType.check x; + merge = + loc: defs: + let + nrNulls = count (def: def.value == null) defs; + in + if nrNulls == length defs then + null + else if nrNulls != 0 then + throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." + else + elemType.merge loc defs; + emptyValue = { + value = null; + }; + getSubOptions = elemType.getSubOptions; + getSubModules = elemType.getSubModules; + substSubModules = m: nullOr (elemType.substSubModules m); + functor = (elemTypeFunctor name { inherit elemType; }) // { + type = payload: types.nullOr payload.elemType; + }; + nestedTypes.elemType = elemType; + }; - # A submodule (like typed attribute set). See NixOS manual. - submodule = modules: submoduleWith { - shorthandOnlyDefinesConfig = true; - modules = toList modules; - }; + functionTo = + elemType: + mkOptionType { + name = "functionTo"; + description = "function that evaluates to a(n) ${ + optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType + }"; + descriptionClass = "composite"; + check = isFunction; + merge = loc: defs: { + # An argument attribute has a default when it has a default in all definitions + __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) ( + lib.map (fn: lib.functionArgs fn.value) defs + ); + __functor = + _: callerArgs: + (mergeDefinitions (loc ++ [ "" ]) elemType ( + map (fn: { + inherit (fn) file; + value = fn.value callerArgs; + }) defs + )).mergedValue; + }; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "" ]); + getSubModules = elemType.getSubModules; + substSubModules = m: functionTo (elemType.substSubModules m); + functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // { + type = payload: types.functionTo payload.elemType; + }; + nestedTypes.elemType = elemType; + }; - # A module to be imported in some other part of the configuration. - deferredModule = deferredModuleWith { }; - - # A module to be imported in some other part of the configuration. - # `staticModules`' options will be added to the documentation, unlike - # options declared via `config`. - deferredModuleWith = attrs@{ staticModules ? [] }: mkOptionType { - name = "deferredModule"; - description = "module"; - descriptionClass = "noun"; - check = x: isAttrs x || isFunction x || path.check x; - merge = loc: defs: { - imports = staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs; - }; - inherit (submoduleWith { modules = staticModules; }) - getSubOptions - getSubModules; - substSubModules = m: deferredModuleWith (attrs // { - staticModules = m; - }); - functor = defaultFunctor "deferredModuleWith" // { - type = types.deferredModuleWith; - payload = { - inherit staticModules; + # A submodule (like typed attribute set). See NixOS manual. + submodule = + modules: + submoduleWith { + shorthandOnlyDefinesConfig = true; + modules = toList modules; }; - binOp = lhs: rhs: { - staticModules = lhs.staticModules ++ rhs.staticModules; + + # A module to be imported in some other part of the configuration. + deferredModule = deferredModuleWith { }; + + # A module to be imported in some other part of the configuration. + # `staticModules`' options will be added to the documentation, unlike + # options declared via `config`. + deferredModuleWith = + attrs@{ + staticModules ? [ ], + }: + mkOptionType { + name = "deferredModule"; + description = "module"; + descriptionClass = "noun"; + check = x: isAttrs x || isFunction x || path.check x; + merge = loc: defs: { + imports = + staticModules + ++ map ( + def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value + ) defs; + }; + inherit (submoduleWith { modules = staticModules; }) + getSubOptions + getSubModules + ; + substSubModules = + m: + deferredModuleWith ( + attrs + // { + staticModules = m; + } + ); + functor = defaultFunctor "deferredModuleWith" // { + type = types.deferredModuleWith; + payload = { + inherit staticModules; + }; + binOp = lhs: rhs: { + staticModules = lhs.staticModules ++ rhs.staticModules; + }; + }; }; - }; - }; - # The type of a type! - optionType = mkOptionType { - name = "optionType"; - description = "optionType"; - descriptionClass = "noun"; - check = value: value._type or null == "option-type"; - merge = loc: defs: - if length defs == 1 - then (head defs).value - else let - # Prepares the type definitions for mergeOptionDecls, which - # annotates submodules types with file locations - optionModules = map ({ value, file }: - { - _file = file; - # There's no way to merge types directly from the module system, - # but we can cheat a bit by just declaring an option with the type - options = lib.mkOption { - type = value; - }; - } - ) defs; - # Merges all the types into a single one, including submodule merging. - # This also propagates file information to all submodules - mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules); - in mergedOption.type; - }; + # The type of a type! + optionType = mkOptionType { + name = "optionType"; + description = "optionType"; + descriptionClass = "noun"; + check = value: value._type or null == "option-type"; + merge = + loc: defs: + if length defs == 1 then + (head defs).value + else + let + # Prepares the type definitions for mergeOptionDecls, which + # annotates submodules types with file locations + optionModules = map ( + { value, file }: + { + _file = file; + # There's no way to merge types directly from the module system, + # but we can cheat a bit by just declaring an option with the type + options = lib.mkOption { + type = value; + }; + } + ) defs; + # Merges all the types into a single one, including submodule merging. + # This also propagates file information to all submodules + mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules); + in + mergedOption.type; + }; - submoduleWith = - { modules - , specialArgs ? {} - , shorthandOnlyDefinesConfig ? false - , description ? null - , class ? null - }@attrs: - let - inherit (lib.modules) evalModules; - - allModules = defs: map ({ value, file }: - if isAttrs value && shorthandOnlyDefinesConfig - then { _file = file; config = value; } - else { _file = file; imports = [ value ]; } - ) defs; - - base = evalModules { - inherit class specialArgs; - modules = [{ - # This is a work-around for the fact that some sub-modules, - # such as the one included in an attribute set, expects an "args" - # attribute to be given to the sub-module. As the option - # evaluation does not have any specific attribute name yet, we - # provide a default for the documentation and the freeform type. - # - # This is necessary as some option declaration might use the - # "name" attribute given as argument of the submodule and use it - # as the default of option declarations. - # - # We use lookalike unicode single angle quotation marks because - # of the docbook transformation the options receive. In all uses - # > and < wouldn't be encoded correctly so the encoded values - # would be used, and use of `<` and `>` would break the XML document. - # It shouldn't cause an issue since this is cosmetic for the manual. - _module.args.name = lib.mkOptionDefault "‹name›"; - }] ++ modules; - }; + submoduleWith = + { + modules, + specialArgs ? { }, + shorthandOnlyDefinesConfig ? false, + description ? null, + class ? null, + }@attrs: + let + inherit (lib.modules) evalModules; + + allModules = + defs: + map ( + { value, file }: + if isAttrs value && shorthandOnlyDefinesConfig then + { + _file = file; + config = value; + } + else + { + _file = file; + imports = [ value ]; + } + ) defs; + + base = evalModules { + inherit class specialArgs; + modules = [ + { + # This is a work-around for the fact that some sub-modules, + # such as the one included in an attribute set, expects an "args" + # attribute to be given to the sub-module. As the option + # evaluation does not have any specific attribute name yet, we + # provide a default for the documentation and the freeform type. + # + # This is necessary as some option declaration might use the + # "name" attribute given as argument of the submodule and use it + # as the default of option declarations. + # + # We use lookalike unicode single angle quotation marks because + # of the docbook transformation the options receive. In all uses + # > and < wouldn't be encoded correctly so the encoded values + # would be used, and use of `<` and `>` would break the XML document. + # It shouldn't cause an issue since this is cosmetic for the manual. + _module.args.name = lib.mkOptionDefault "‹name›"; + } + ] ++ modules; + }; - freeformType = base._module.freeformType; + freeformType = base._module.freeformType; - name = "submodule"; + name = "submodule"; - in - mkOptionType { - inherit name; - description = - if description != null then description - else freeformType.description or name; - check = x: isAttrs x || isFunction x || path.check x; - merge = loc: defs: - (base.extendModules { - modules = [ { _module.args.name = last loc; } ] ++ allModules defs; - prefix = loc; - }).config; - emptyValue = { value = {}; }; - getSubOptions = prefix: (base.extendModules - { inherit prefix; }).options // optionalAttrs (freeformType != null) { - # Expose the sub options of the freeform type. Note that the option - # discovery doesn't care about the attribute name used here, so this - # is just to avoid conflicts with potential options from the submodule - _freeformOptions = freeformType.getSubOptions prefix; + in + mkOptionType { + inherit name; + description = if description != null then description else freeformType.description or name; + check = x: isAttrs x || isFunction x || path.check x; + merge = + loc: defs: + (base.extendModules { + modules = [ { _module.args.name = last loc; } ] ++ allModules defs; + prefix = loc; + }).config; + emptyValue = { + value = { }; }; - getSubModules = modules; - substSubModules = m: submoduleWith (attrs // { - modules = m; - }); - nestedTypes = lib.optionalAttrs (freeformType != null) { - freeformType = freeformType; - }; - functor = defaultFunctor name // { - type = types.submoduleWith; - payload = { - inherit modules class specialArgs shorthandOnlyDefinesConfig description; + getSubOptions = + prefix: + (base.extendModules { inherit prefix; }).options + // optionalAttrs (freeformType != null) { + # Expose the sub options of the freeform type. Note that the option + # discovery doesn't care about the attribute name used here, so this + # is just to avoid conflicts with potential options from the submodule + _freeformOptions = freeformType.getSubOptions prefix; + }; + getSubModules = modules; + substSubModules = + m: + submoduleWith ( + attrs + // { + modules = m; + } + ); + nestedTypes = lib.optionalAttrs (freeformType != null) { + freeformType = freeformType; }; - binOp = lhs: rhs: { - class = - # `or null` was added for backwards compatibility only. `class` is - # always set in the current version of the module system. - if lhs.class or null == null then rhs.class or null - else if rhs.class or null == null then lhs.class or null - else if lhs.class or null == rhs.class then lhs.class or null - else throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\"."; - modules = lhs.modules ++ rhs.modules; - specialArgs = - let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs; - in if intersecting == {} - then lhs.specialArgs // rhs.specialArgs - else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; - shorthandOnlyDefinesConfig = - if lhs.shorthandOnlyDefinesConfig == null - then rhs.shorthandOnlyDefinesConfig - else if rhs.shorthandOnlyDefinesConfig == null - then lhs.shorthandOnlyDefinesConfig - else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig - then lhs.shorthandOnlyDefinesConfig - else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; - description = - if lhs.description == null - then rhs.description - else if rhs.description == null - then lhs.description - else if lhs.description == rhs.description - then lhs.description - else throw "A submoduleWith option is declared multiple times with conflicting descriptions"; + functor = defaultFunctor name // { + type = types.submoduleWith; + payload = { + inherit + modules + class + specialArgs + shorthandOnlyDefinesConfig + description + ; + }; + binOp = lhs: rhs: { + class = + # `or null` was added for backwards compatibility only. `class` is + # always set in the current version of the module system. + if lhs.class or null == null then + rhs.class or null + else if rhs.class or null == null then + lhs.class or null + else if lhs.class or null == rhs.class then + lhs.class or null + else + throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\"."; + modules = lhs.modules ++ rhs.modules; + specialArgs = + let + intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs; + in + if intersecting == { } then + lhs.specialArgs // rhs.specialArgs + else + throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\""; + shorthandOnlyDefinesConfig = + if lhs.shorthandOnlyDefinesConfig == null then + rhs.shorthandOnlyDefinesConfig + else if rhs.shorthandOnlyDefinesConfig == null then + lhs.shorthandOnlyDefinesConfig + else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig then + lhs.shorthandOnlyDefinesConfig + else + throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values"; + description = + if lhs.description == null then + rhs.description + else if rhs.description == null then + lhs.description + else if lhs.description == rhs.description then + lhs.description + else + throw "A submoduleWith option is declared multiple times with conflicting descriptions"; + }; }; }; - }; - # A value from a set of allowed ones. - enum = values: - let - inherit (lib.lists) unique; - show = v: - if builtins.isString v then ''"${v}"'' - else if builtins.isInt v then builtins.toString v - else if builtins.isBool v then boolToString v - else ''<${builtins.typeOf v}>''; - in - mkOptionType rec { - name = "enum"; - description = - # Length 0 or 1 enums may occur in a design pattern with type merging - # where an "interface" module declares an empty enum and other modules - # provide implementations, each extending the enum with their own - # identifier. - if values == [] then - "impossible (empty enum)" - else if builtins.length values == 1 then - "value ${show (builtins.head values)} (singular enum)" - else - "one of ${concatMapStringsSep ", " show values}"; - descriptionClass = - if builtins.length values < 2 - then "noun" - else "conjunction"; - check = flip elem values; - merge = mergeEqualOption; - functor = (defaultFunctor name) // { - payload = { inherit values; }; - type = payload: types.enum payload.values; - binOp = a: b: { values = unique (a.values ++ b.values); }; + # A value from a set of allowed ones. + enum = + values: + let + inherit (lib.lists) unique; + show = + v: + if builtins.isString v then + ''"${v}"'' + else if builtins.isInt v then + builtins.toString v + else if builtins.isBool v then + boolToString v + else + ''<${builtins.typeOf v}>''; + in + mkOptionType rec { + name = "enum"; + description = + # Length 0 or 1 enums may occur in a design pattern with type merging + # where an "interface" module declares an empty enum and other modules + # provide implementations, each extending the enum with their own + # identifier. + if values == [ ] then + "impossible (empty enum)" + else if builtins.length values == 1 then + "value ${show (builtins.head values)} (singular enum)" + else + "one of ${concatMapStringsSep ", " show values}"; + descriptionClass = if builtins.length values < 2 then "noun" else "conjunction"; + check = flip elem values; + merge = mergeEqualOption; + functor = (defaultFunctor name) // { + payload = { inherit values; }; + type = payload: types.enum payload.values; + binOp = a: b: { values = unique (a.values ++ b.values); }; + }; }; - }; - # Either value of type `t1` or `t2`. - either = t1: t2: mkOptionType rec { - name = "either"; - description = - if t1.descriptionClass or null == "nonRestrictiveClause" - then - # Plain, but add comma - "${t1.description}, or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2}" - else - "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${optionDescriptionPhrase (class: class == "noun" || class == "conjunction" || class == "composite") t2}"; - descriptionClass = "conjunction"; - check = x: t1.check x || t2.check x; - merge = loc: defs: + # Either value of type `t1` or `t2`. + either = + t1: t2: + mkOptionType rec { + name = "either"; + description = + if t1.descriptionClass or null == "nonRestrictiveClause" then + # Plain, but add comma + "${t1.description}, or ${ + optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2 + }" + else + "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${ + optionDescriptionPhrase ( + class: class == "noun" || class == "conjunction" || class == "composite" + ) t2 + }"; + descriptionClass = "conjunction"; + check = x: t1.check x || t2.check x; + merge = + loc: defs: + let + defList = map (d: d.value) defs; + in + if all (x: t1.check x) defList then + t1.merge loc defs + else if all (x: t2.check x) defList then + t2.merge loc defs + else + mergeOneOption loc defs; + typeMerge = + f': + let + mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor; + mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor; + in + if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null; + functor = elemTypeFunctor name { + elemType = [ + t1 + t2 + ]; + }; + nestedTypes.left = t1; + nestedTypes.right = t2; + }; + + # Any of the types in the given list + oneOf = + ts: let - defList = map (d: d.value) defs; + head' = + if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts; + tail' = tail ts; in - if all (x: t1.check x) defList - then t1.merge loc defs - else if all (x: t2.check x) defList - then t2.merge loc defs - else mergeOneOption loc defs; - typeMerge = f': - let mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor; - mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor; - in - if (name == f'.name) && (mt1 != null) && (mt2 != null) - then functor.type mt1 mt2 - else null; - functor = elemTypeFunctor name { elemType = [ t1 t2 ]; }; - nestedTypes.left = t1; - nestedTypes.right = t2; - }; - - # Any of the types in the given list - oneOf = ts: - let - head' = if ts == [] then throw "types.oneOf needs to get at least one type in its argument" else head ts; - tail' = tail ts; - in foldl' either head' tail'; - - # Either value of type `coercedType` or `finalType`, the former is - # converted to `finalType` using `coerceFunc`. - coercedTo = coercedType: coerceFunc: finalType: - assert lib.assertMsg (coercedType.getSubModules == null) - "coercedTo: coercedType must not have submodules (it’s a ${ - coercedType.description})"; - mkOptionType rec { - name = "coercedTo"; - description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${optionDescriptionPhrase (class: class == "noun") coercedType} convertible to it"; - check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x; - merge = loc: defs: - let - coerceVal = val: - if coercedType.check val then coerceFunc val - else val; - in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); - emptyValue = finalType.emptyValue; - getSubOptions = finalType.getSubOptions; - getSubModules = finalType.getSubModules; - substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); - typeMerge = t: null; - functor = (defaultFunctor name) // { - wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; }; + foldl' either head' tail'; + + # Either value of type `coercedType` or `finalType`, the former is + # converted to `finalType` using `coerceFunc`. + coercedTo = + coercedType: coerceFunc: finalType: + assert lib.assertMsg ( + coercedType.getSubModules == null + ) "coercedTo: coercedType must not have submodules (it’s a ${coercedType.description})"; + mkOptionType rec { + name = "coercedTo"; + description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${ + optionDescriptionPhrase (class: class == "noun") coercedType + } convertible to it"; + check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x; + merge = + loc: defs: + let + coerceVal = val: if coercedType.check val then coerceFunc val else val; + in + finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); + emptyValue = finalType.emptyValue; + getSubOptions = finalType.getSubOptions; + getSubModules = finalType.getSubModules; + substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); + typeMerge = t: null; + functor = (defaultFunctor name) // { + wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; }; + }; + nestedTypes.coercedType = coercedType; + nestedTypes.finalType = finalType; }; - nestedTypes.coercedType = coercedType; - nestedTypes.finalType = finalType; - }; - # Augment the given type with an additional type check function. - addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; + # Augment the given type with an additional type check function. + addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; - }; + }; - /** - Merges two option types together. + /** + Merges two option types together. - :::{.note} - Uses the type merge function of the first type, to merge it with the second type. + :::{.note} + Uses the type merge function of the first type, to merge it with the second type. - Usually types can only be merged if they are of the same type - ::: + Usually types can only be merged if they are of the same type + ::: - # Inputs + # Inputs - : `a` (option type): The first option type. - : `b` (option type): The second option type. + : `a` (option type): The first option type. + : `b` (option type): The second option type. - # Returns + # Returns - - The merged option type. - - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged. + - The merged option type. + - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged. - # Examples - :::{.example} - ## `lib.types.mergeTypes` usage example - ```nix - let - enumAB = lib.types.enum ["A" "B"]; - enumXY = lib.types.enum ["X" "Y"]; - # This operation could be notated as: [ A ] | [ B ] -> [ A B ] - merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ] - in - assert merged.check "A"; # true - assert merged.check "B"; # true - assert merged.check "X"; # true - assert merged.check "Y"; # true - merged.check "C" # false - ``` - ::: - */ - mergeTypes = a: b: - assert isOptionType a && isOptionType b; - let - merged = a.typeMerge b.functor; - in - if merged == null then - setType "merge-error" { error = "Cannot merge types"; } - else - merged; -}; + # Examples + :::{.example} + ## `lib.types.mergeTypes` usage example + ```nix + let + enumAB = lib.types.enum ["A" "B"]; + enumXY = lib.types.enum ["X" "Y"]; + # This operation could be notated as: [ A ] | [ B ] -> [ A B ] + merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ] + in + assert merged.check "A"; # true + assert merged.check "B"; # true + assert merged.check "X"; # true + assert merged.check "Y"; # true + merged.check "C" # false + ``` + ::: + */ + mergeTypes = + a: b: + assert isOptionType a && isOptionType b; + let + merged = a.typeMerge b.functor; + in + if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged; + }; -in outer_types // outer_types.types +in +outer_types // outer_types.types -- cgit v1.2.3