diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ascii-table.nix | 5 | ||||
-rw-r--r-- | lib/attrsets.nix | 19 | ||||
-rw-r--r-- | lib/customisation.nix | 12 | ||||
-rw-r--r-- | lib/debug.nix | 66 | ||||
-rw-r--r-- | lib/default.nix | 6 | ||||
-rw-r--r-- | lib/fixed-points.nix | 4 | ||||
-rw-r--r-- | lib/licenses.nix | 40 | ||||
-rw-r--r-- | lib/lists.nix | 16 | ||||
-rw-r--r-- | lib/meta.nix | 11 | ||||
-rw-r--r-- | lib/options.nix | 82 | ||||
-rw-r--r-- | lib/path/default.nix | 70 | ||||
-rw-r--r-- | lib/path/tests/unit.nix | 40 | ||||
-rw-r--r-- | lib/strings.nix | 32 | ||||
-rw-r--r-- | lib/systems/architectures.nix | 19 | ||||
-rw-r--r-- | lib/systems/doubles.nix | 3 | ||||
-rw-r--r-- | lib/systems/inspect.nix | 23 | ||||
-rw-r--r-- | lib/tests/maintainer-module.nix | 3 | ||||
-rw-r--r-- | lib/tests/maintainers.nix | 9 | ||||
-rw-r--r-- | lib/tests/misc.nix | 19 | ||||
-rw-r--r-- | lib/tests/release.nix | 10 | ||||
-rw-r--r-- | lib/tests/systems.nix | 3 |
21 files changed, 407 insertions, 85 deletions
diff --git a/lib/ascii-table.nix b/lib/ascii-table.nix index c564e12bcc6ff..74989936ea402 100644 --- a/lib/ascii-table.nix +++ b/lib/ascii-table.nix @@ -1,4 +1,7 @@ -{ " " = 32; +{ "\t" = 9; + "\n" = 10; + "\r" = 13; + " " = 32; "!" = 33; "\"" = 34; "#" = 35; diff --git a/lib/attrsets.nix b/lib/attrsets.nix index 1a7b90593b1d7..30952651adf40 100644 --- a/lib/attrsets.nix +++ b/lib/attrsets.nix @@ -168,7 +168,7 @@ rec { ] { a.b.c = 0; } => { a = { b = { d = 1; }; }; x = { y = "xy"; }; } - Type: updateManyAttrsByPath :: [{ path :: [String], update :: (Any -> Any) }] -> AttrSet -> AttrSet + Type: updateManyAttrsByPath :: [{ path :: [String]; update :: (Any -> Any); }] -> AttrSet -> AttrSet */ updateManyAttrsByPath = let # When recursing into attributes, instead of updating the `path` of each @@ -414,7 +414,7 @@ rec { => { name = "some"; value = 6; } Type: - nameValuePair :: String -> Any -> { name :: String, value :: Any } + nameValuePair :: String -> Any -> { name :: String; value :: Any; } */ nameValuePair = # Attribute name @@ -449,7 +449,7 @@ rec { => { foo_x = "bar-a"; foo_y = "bar-b"; } Type: - mapAttrs' :: (String -> Any -> { name = String; value = Any }) -> AttrSet -> AttrSet + mapAttrs' :: (String -> Any -> { name :: String; value :: Any; }) -> AttrSet -> AttrSet */ mapAttrs' = # A function, given an attribute's name and value, returns a new `nameValuePair`. @@ -480,8 +480,13 @@ rec { /* Like `mapAttrs`, except that it recursively applies itself to - attribute sets. Also, the first argument of the argument - function is a *list* of the names of the containing attributes. + the *leaf* attributes of a potentially-nested attribute set: + the second argument of the function will never be an attrset. + Also, the first argument of the argument function is a *list* + of the attribute names that form the path to the leaf attribute. + + For a function that gives you control over what counts as a leaf, + see `mapAttrsRecursiveCond`. Example: mapAttrsRecursive (path: value: concatStringsSep "-" (path ++ [value])) @@ -644,7 +649,7 @@ rec { Example: zipAttrsWith (name: values: values) [{a = "x";} {a = "y"; b = "z";}] - => { a = ["x" "y"]; b = ["z"] } + => { a = ["x" "y"]; b = ["z"]; } Type: zipAttrsWith :: (String -> [ Any ] -> Any) -> [ AttrSet ] -> AttrSet @@ -659,7 +664,7 @@ rec { Example: zipAttrs [{a = "x";} {a = "y"; b = "z";}] - => { a = ["x" "y"]; b = ["z"] } + => { a = ["x" "y"]; b = ["z"]; } Type: zipAttrs :: [ AttrSet ] -> AttrSet diff --git a/lib/customisation.nix b/lib/customisation.nix index 101c9e62b9e61..cb3a4b561151f 100644 --- a/lib/customisation.nix +++ b/lib/customisation.nix @@ -213,7 +213,14 @@ rec { outputSpecified = true; drvPath = assert condition; drv.${outputName}.drvPath; outPath = assert condition; drv.${outputName}.outPath; - }; + } // + # TODO: give the derivation control over the outputs. + # `overrideAttrs` may not be the only attribute that needs + # updating when switching outputs. + lib.optionalAttrs (passthru?overrideAttrs) { + # TODO: also add overrideAttrs when overrideAttrs is not custom, e.g. when not splicing. + overrideAttrs = f: (passthru.overrideAttrs f).${outputName}; + }; }; outputsList = map outputToAttrListElement outputs; @@ -252,7 +259,8 @@ rec { outputsList = map makeOutput outputs; drv' = (lib.head outputsList).value; - in lib.deepSeq drv' drv'; + in if drv == null then null else + lib.deepSeq drv' drv'; /* Make a set of packages with a common scope. All packages called with the provided `callPackage` will be evaluated with the same diff --git a/lib/debug.nix b/lib/debug.nix index e3ca3352397ec..35ca4c7dfb202 100644 --- a/lib/debug.nix +++ b/lib/debug.nix @@ -109,6 +109,8 @@ rec { traceSeqN 2 { a.b.c = 3; } null trace: { a = { b = {…}; }; } => null + + Type: traceSeqN :: Int -> a -> b -> b */ traceSeqN = depth: x: y: let snip = v: if isList v then noQuotes "[…]" v @@ -173,17 +175,63 @@ rec { # -- TESTING -- - /* Evaluate a set of tests. A test is an attribute set `{expr, - expected}`, denoting an expression and its expected result. The - result is a list of failed tests, each represented as `{name, - expected, actual}`, denoting the attribute name of the failing - test and its expected and actual results. + /* Evaluates a set of tests. - Used for regression testing of the functions in lib; see - tests.nix for an example. Only tests having names starting with - "test" are run. + A test is an attribute set `{expr, expected}`, + denoting an expression and its expected result. + + The result is a `list` of __failed tests__, each represented as + `{name, expected, result}`, + + - expected + - What was passed as `expected` + - result + - The actual `result` of the test - Add attr { tests = ["testName"]; } to run these tests only. + Used for regression testing of the functions in lib; see + tests.nix for more examples. + + Important: Only attributes that start with `test` are executed. + + - If you want to run only a subset of the tests add the attribute `tests = ["testName"];` + + Example: + + runTests { + testAndOk = { + expr = lib.and true false; + expected = false; + }; + testAndFail = { + expr = lib.and true false; + expected = true; + }; + } + -> + [ + { + name = "testAndFail"; + expected = true; + result = false; + } + ] + + Type: + runTests :: { + tests = [ String ]; + ${testName} :: { + expr :: a; + expected :: a; + }; + } + -> + [ + { + name :: String; + expected :: a; + result :: a; + } + ] */ runTests = # Tests to run diff --git a/lib/default.nix b/lib/default.nix index 8ce1de33f5dce..7948dbd5a1ef4 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -88,19 +88,19 @@ let updateManyAttrsByPath; inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1 concatMap flatten remove findSingle findFirst any all count - optional optionals toList range partition zipListsWith zipLists + optional optionals toList range replicate partition zipListsWith zipLists reverseList listDfs toposort sort naturalSort compareLists take drop sublist last init crossLists unique intersectLists subtractLists mutuallyExclusive groupBy groupBy'; inherit (self.strings) concatStrings concatMapStrings concatImapStrings intersperse concatStringsSep concatMapStringsSep - concatImapStringsSep makeSearchPath makeSearchPathOutput + concatImapStringsSep concatLines makeSearchPath makeSearchPathOutput makeLibraryPath makeBinPath optionalString hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape escapeShellArg escapeShellArgs isStorePath isStringLike isValidPosixName toShellVar toShellVars - escapeRegex escapeXML replaceChars lowerChars + escapeRegex escapeURL escapeXML replaceChars lowerChars upperChars toLower toUpper addContextFrom splitString removePrefix removeSuffix versionOlder versionAtLeast getName getVersion diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index bf1567a22a664..926428293c1c8 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -107,7 +107,7 @@ rec { # Same as `makeExtensible` but the name of the extending attribute is # customized. makeExtensibleWithCustomName = extenderName: rattrs: - fix' rattrs // { + fix' (self: (rattrs self) // { ${extenderName} = f: makeExtensibleWithCustomName extenderName (extends f rattrs); - }; + }); } diff --git a/lib/licenses.nix b/lib/licenses.nix index 6919859d6cb45..00f469b61a8e6 100644 --- a/lib/licenses.nix +++ b/lib/licenses.nix @@ -108,11 +108,26 @@ in mkLicense lset) ({ fullName = "Apache License 2.0"; }; + asl20-llvm = { + spdxId = "Apache-2.0 WITH LLVM-exception"; + fullName = "Apache License 2.0 with LLVM Exceptions"; + }; + 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"; @@ -332,6 +347,13 @@ in mkLicense lset) ({ free = false; }; + ecl20 = { + fullName = "Educational Community License, Version 2.0"; + url = "https://opensource.org/licenses/ECL-2.0"; + shortName = "ECL 2.0"; + spdxId = "ECL-2.0"; + }; + efl10 = { spdxId = "EFL-1.0"; fullName = "Eiffel Forum License v1.0"; @@ -557,6 +579,12 @@ in mkLicense lset) ({ redistributable = false; }; + fair = { + fullName = "Fair License"; + spdxId = "Fair"; + free = true; + }; + issl = { fullName = "Intel Simplified Software License"; url = "https://software.intel.com/en-us/license/intel-simplified-software-license"; @@ -633,11 +661,6 @@ in mkLicense lset) ({ url = "https://opensource.franz.com/preamble.html"; }; - llvm-exception = { - spdxId = "LLVM-exception"; - fullName = "LLVM Exception"; # LLVM exceptions to the Apache 2.0 License - }; - lppl12 = { spdxId = "LPPL-1.2"; fullName = "LaTeX Project Public License v1.2"; @@ -708,7 +731,12 @@ in mkLicense lset) ({ ncsa = { spdxId = "NCSA"; - fullName = "University of Illinois/NCSA Open Source License"; + fullName = "University of Illinois/NCSA Open Source License"; + }; + + nlpl = { + spdxId = "NLPL"; + fullName = "No Limit Public License"; }; nposl3 = { diff --git a/lib/lists.nix b/lib/lists.nix index 8b2c2d12801bb..2186cd4a79f60 100644 --- a/lib/lists.nix +++ b/lib/lists.nix @@ -303,10 +303,22 @@ rec { else genList (n: first + n) (last - first + 1); + /* Return a list with `n` copies of an element. + + Type: replicate :: int -> a -> [a] + + Example: + replicate 3 "a" + => [ "a" "a" "a" ] + replicate 2 true + => [ true true ] + */ + replicate = n: elem: genList (_: elem) n; + /* Splits the elements of a list in two lists, `right` and `wrong`, depending on the evaluation of a predicate. - Type: (a -> bool) -> [a] -> { right :: [a], wrong :: [a] } + Type: (a -> bool) -> [a] -> { right :: [a]; wrong :: [a]; } Example: partition (x: x > 2) [ 5 1 2 3 4 ] @@ -374,7 +386,7 @@ rec { /* Merges two lists of the same size together. If the sizes aren't the same the merging stops at the shortest. - Type: zipLists :: [a] -> [b] -> [{ fst :: a, snd :: b}] + Type: zipLists :: [a] -> [b] -> [{ fst :: a; snd :: b; }] Example: zipLists [ 1 2 ] [ "a" "b" ] diff --git a/lib/meta.nix b/lib/meta.nix index 893c671b04fa8..5fd55c4e90d69 100644 --- a/lib/meta.nix +++ b/lib/meta.nix @@ -76,7 +76,9 @@ rec { 1. (legacy) a system string. - 2. (modern) a pattern for the platform `parsed` field. + 2. (modern) a pattern for the entire platform structure (see `lib.systems.inspect.platformPatterns`). + + 3. (modern) a pattern for the platform `parsed` field (see `lib.systems.inspect.patterns`). We can inject these into a pattern for the whole of a structured platform, and then match that. @@ -85,6 +87,8 @@ rec { pattern = if builtins.isString elem then { system = elem; } + else if elem?parsed + then elem else { parsed = elem; }; in lib.matchAttrs pattern platform; @@ -92,12 +96,13 @@ rec { A package is available on a platform if both - 1. One of `meta.platforms` pattern matches the given platform. + 1. One of `meta.platforms` pattern matches the given + platform, or `meta.platforms` is not present. 2. None of `meta.badPlatforms` pattern matches the given platform. */ availableOn = platform: pkg: - lib.any (platformMatch platform) pkg.meta.platforms && + ((!pkg?meta.platforms) || lib.any (platformMatch platform) pkg.meta.platforms) && lib.all (elem: !platformMatch platform elem) (pkg.meta.badPlatforms or []); /* Get the corresponding attribute in lib.licenses diff --git a/lib/options.nix b/lib/options.nix index ce66bfb9d5d9f..4780a56fc1c37 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -36,6 +36,12 @@ let inherit (lib.types) mkOptionType ; + inherit (lib.lists) + last + ; + prioritySuggestion = '' + Use `lib.mkForce value` or `lib.mkDefault value` to change the priority on any of these definitions. + ''; in rec { @@ -104,17 +110,26 @@ rec { /* Creates an Option attribute set for an option that specifies the package a module should use for some purpose. - The package is specified as a list of strings representing its attribute path in nixpkgs. + The package is specified in the third argument under `default` as a list of strings + representing its attribute path in nixpkgs (or another package set). + Because of this, you need to pass nixpkgs itself (or a subset) as the first argument. - Because of this, you need to pass nixpkgs itself as the first argument. + The second argument may be either a string or a list of strings. + It provides the display name of the package in the description of the generated option + (using only the last element if the passed value is a list) + and serves as the fallback value for the `default` argument. - The second argument is the name of the option, used in the description "The <name> package to use.". + To include extra information in the description, pass `extraDescription` to + append arbitrary text to the generated description. + You can also pass an `example` value, either a literal string or an attribute path. - You can also pass an example value, either a literal string or a package's attribute path. + The default argument can be omitted if the provided name is + an attribute of pkgs (if name is a string) or a + valid attribute path in pkgs (if name is a list). - You can omit the default path if the name of the option is also attribute path in nixpkgs. + If you wish to explicitly provide no default, pass `null` as `default`. - Type: mkPackageOption :: pkgs -> string -> { default :: [string], example :: null | string | [string] } -> option + Type: mkPackageOption :: pkgs -> (string|[string]) -> { default? :: [string], example? :: null|string|[string], extraDescription? :: string } -> option Example: mkPackageOption pkgs "hello" { } @@ -126,27 +141,46 @@ rec { example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])"; } => { _type = "option"; default = «derivation /nix/store/jxx55cxsjrf8kyh3fp2ya17q99w7541r-ghc-8.10.7.drv»; defaultText = { ... }; description = "The GHC package to use."; example = { ... }; type = { ... }; } + + Example: + mkPackageOption pkgs [ "python39Packages" "pytorch" ] { + extraDescription = "This is an example and doesn't actually do anything."; + } + => { _type = "option"; default = «derivation /nix/store/gvqgsnc4fif9whvwd9ppa568yxbkmvk8-python3.9-pytorch-1.10.2.drv»; defaultText = { ... }; description = "The pytorch package to use. This is an example and doesn't actually do anything."; type = { ... }; } + */ mkPackageOption = - # Package set (a specific version of nixpkgs) + # Package set (a specific version of nixpkgs or a subset) pkgs: # Name for the package, shown in option description name: - { default ? [ name ], example ? null }: - let default' = if !isList default then [ default ] else default; + { + # The attribute path where the default package is located (may be omitted) + default ? name, + # A string or an attribute path to use as an example (may be omitted) + example ? null, + # Additional text to include in the option description (may be omitted) + extraDescription ? "", + }: + let + name' = if isList name then last name else name; + default' = if isList default then default else [ default ]; + defaultPath = concatStringsSep "." default'; + defaultValue = attrByPath default' + (throw "${defaultPath} cannot be found in pkgs") pkgs; in mkOption { + defaultText = literalExpression ("pkgs." + defaultPath); type = lib.types.package; - description = "The ${name} package to use."; - default = attrByPath default' - (throw "${concatStringsSep "." default'} cannot be found in pkgs") pkgs; - defaultText = literalExpression ("pkgs." + concatStringsSep "." default'); + description = "The ${name'} package to use." + + (if extraDescription == "" then "" else " ") + extraDescription; + ${if default != null then "default" else null} = defaultValue; ${if example != null then "example" else null} = literalExpression (if isList example then "pkgs." + concatStringsSep "." example else example); }; /* Like mkPackageOption, but emit an mdDoc description instead of DocBook. */ - mkPackageOptionMD = args: name: extra: - let option = mkPackageOption args name extra; + mkPackageOptionMD = pkgs: name: extra: + let option = mkPackageOption pkgs name extra; in option // { description = lib.mdDoc option.description; }; /* This option accepts anything, but it does not produce any result. @@ -184,7 +218,7 @@ rec { if length defs == 1 then (head defs).value else assert length defs > 1; - throw "The option `${showOption loc}' is defined multiple times.\n${message}\nDefinition values:${showDefs defs}"; + 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. */ mergeEqualOption = loc: defs: @@ -195,13 +229,13 @@ rec { 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 ]}" + 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. - Type: getValues :: [ { value :: a } ] -> [a] + Type: getValues :: [ { value :: a; } ] -> [a] Example: getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] @@ -211,7 +245,7 @@ rec { /* Extracts values of all "file" keys of the given list - Type: getFiles :: [ { file :: a } ] -> [a] + Type: getFiles :: [ { file :: a; } ] -> [a] Example: getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] @@ -334,19 +368,17 @@ rec { # Helper functions. - /* Convert an option, described as a list of the option parts in to a - safe, human readable version. + /* Convert an option, described as a list of the option parts to a + human-readable version. Example: (showOption ["foo" "bar" "baz"]) == "foo.bar.baz" - (showOption ["foo" "bar.baz" "tux"]) == "foo.bar.baz.tux" + (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux" + (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.\"2bwm\".enable" Placeholders will not be quoted as they are not actual values: (showOption ["foo" "*" "bar"]) == "foo.*.bar" (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar" - - Unlike attributes, options can also start with numbers: - (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.2bwm.enable" */ showOption = parts: let escapeOptionPart = part: diff --git a/lib/path/default.nix b/lib/path/default.nix index 96a9244407bf5..075e2fc0d1377 100644 --- a/lib/path/default.nix +++ b/lib/path/default.nix @@ -4,6 +4,7 @@ let inherit (builtins) isString + isPath split match ; @@ -25,6 +26,10 @@ let assertMsg ; + inherit (lib.path.subpath) + isValid + ; + # Return the reason why a subpath is invalid, or `null` if it's valid subpathInvalidReason = value: if ! isString value then @@ -94,6 +99,52 @@ let in /* No rec! Add dependencies on this file at the top. */ { + /* Append a subpath string to a path. + + Like `path + ("/" + string)` but safer, because it errors instead of returning potentially surprising results. + More specifically, it checks that the first argument is a [path value type](https://nixos.org/manual/nix/stable/language/values.html#type-path"), + and that the second argument is a valid subpath string (see `lib.path.subpath.isValid`). + + Type: + append :: Path -> String -> Path + + Example: + append /foo "bar/baz" + => /foo/bar/baz + + # subpaths don't need to be normalised + append /foo "./bar//baz/./" + => /foo/bar/baz + + # can append to root directory + append /. "foo/bar" + => /foo/bar + + # first argument needs to be a path value type + append "/foo" "bar" + => <error> + + # second argument needs to be a valid subpath string + append /foo /bar + => <error> + append /foo "" + => <error> + append /foo "/bar" + => <error> + append /foo "../bar" + => <error> + */ + append = + # The absolute path to append to + path: + # The subpath string to append + subpath: + assert assertMsg (isPath path) '' + lib.path.append: The first argument is of type ${builtins.typeOf path}, but a path was expected''; + assert assertMsg (isValid subpath) '' + lib.path.append: Second argument is not a valid subpath string: + ${subpathInvalidReason subpath}''; + path + ("/" + subpath); /* Whether a value is a valid subpath string. @@ -133,7 +184,9 @@ in /* No rec! Add dependencies on this file at the top. */ { subpath.isValid "./foo//bar/" => true */ - subpath.isValid = value: + subpath.isValid = + # The value to check + value: subpathInvalidReason value == null; @@ -150,11 +203,11 @@ in /* No rec! Add dependencies on this file at the top. */ { Laws: - - (Idempotency) Normalising multiple times gives the same result: + - Idempotency - normalising multiple times gives the same result: subpath.normalise (subpath.normalise p) == subpath.normalise p - - (Uniqueness) There's only a single normalisation for the paths that lead to the same file system node: + - Uniqueness - there's only a single normalisation for the paths that lead to the same file system node: subpath.normalise p != subpath.normalise q -> $(realpath ${p}) != $(realpath ${q}) @@ -210,9 +263,12 @@ in /* No rec! Add dependencies on this file at the top. */ { subpath.normalise "/foo" => <error> */ - subpath.normalise = path: - assert assertMsg (subpathInvalidReason path == null) - "lib.path.subpath.normalise: Argument is not a valid subpath string: ${subpathInvalidReason path}"; - joinRelPath (splitRelPath path); + subpath.normalise = + # The subpath string to normalise + subpath: + assert assertMsg (isValid subpath) '' + lib.path.subpath.normalise: Argument is not a valid subpath string: + ${subpathInvalidReason subpath}''; + joinRelPath (splitRelPath subpath); } diff --git a/lib/path/tests/unit.nix b/lib/path/tests/unit.nix index eccf3b7b1c33b..a1a45173a9098 100644 --- a/lib/path/tests/unit.nix +++ b/lib/path/tests/unit.nix @@ -3,9 +3,44 @@ { libpath }: let lib = import libpath; - inherit (lib.path) subpath; + inherit (lib.path) append subpath; cases = lib.runTests { + # Test examples from the lib.path.append documentation + testAppendExample1 = { + expr = append /foo "bar/baz"; + expected = /foo/bar/baz; + }; + testAppendExample2 = { + expr = append /foo "./bar//baz/./"; + expected = /foo/bar/baz; + }; + testAppendExample3 = { + expr = append /. "foo/bar"; + expected = /foo/bar; + }; + testAppendExample4 = { + expr = (builtins.tryEval (append "/foo" "bar")).success; + expected = false; + }; + testAppendExample5 = { + expr = (builtins.tryEval (append /foo /bar)).success; + expected = false; + }; + testAppendExample6 = { + expr = (builtins.tryEval (append /foo "")).success; + expected = false; + }; + testAppendExample7 = { + expr = (builtins.tryEval (append /foo "/bar")).success; + expected = false; + }; + testAppendExample8 = { + expr = (builtins.tryEval (append /foo "../bar")).success; + expected = false; + }; + + # Test examples from the lib.path.subpath.isValid documentation testSubpathIsValidExample1 = { expr = subpath.isValid null; expected = false; @@ -30,6 +65,7 @@ let expr = subpath.isValid "./foo//bar/"; expected = true; }; + # Some extra tests testSubpathIsValidTwoDotsEnd = { expr = subpath.isValid "foo/.."; expected = false; @@ -71,6 +107,7 @@ let expected = true; }; + # Test examples from the lib.path.subpath.normalise documentation testSubpathNormaliseExample1 = { expr = subpath.normalise "foo//bar"; expected = "./foo/bar"; @@ -107,6 +144,7 @@ let expr = (builtins.tryEval (subpath.normalise "/foo")).success; expected = false; }; + # Some extra tests testSubpathNormaliseIsValidDots = { expr = subpath.normalise "./foo/.bar/.../baz...qux"; expected = "./foo/.bar/.../baz...qux"; diff --git a/lib/strings.nix b/lib/strings.nix index 2188fcb1dbfd1..3c3529c3285ee 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -4,6 +4,8 @@ let inherit (builtins) length; +asciiTable = import ./ascii-table.nix; + in rec { @@ -128,6 +130,17 @@ rec { # List of input strings list: concatStringsSep sep (lib.imap1 f list); + /* Concatenate a list of strings, adding a newline at the end of each one. + Defined as `concatMapStrings (s: s + "\n")`. + + Type: concatLines :: [string] -> string + + Example: + concatLines [ "foo" "bar" ] + => "foo\nbar\n" + */ + concatLines = concatMapStrings (s: s + "\n"); + /* Construct a Unix-style, colon-separated search path consisting of the given `subDir` appended to each of the given paths. @@ -316,9 +329,7 @@ rec { => 40 */ - charToInt = let - table = import ./ascii-table.nix; - in c: builtins.getAttr c table; + charToInt = c: builtins.getAttr c asciiTable; /* Escape occurrence of the elements of `list` in `string` by prefixing it with a backslash. @@ -344,6 +355,21 @@ rec { */ 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 + query. + + Type: escapeURL :: string -> string + + Example: + escapeURL "foo/bar baz" + => "foo%2Fbar%20baz" + */ + 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. Type: escapeShellArg :: string -> string diff --git a/lib/systems/architectures.nix b/lib/systems/architectures.nix index 94127fa90b351..57b9184ca60cd 100644 --- a/lib/systems/architectures.nix +++ b/lib/systems/architectures.nix @@ -40,14 +40,21 @@ rec { # a superior CPU has all the features of an inferior and is able to build and test code for it inferiors = { # x86_64 Intel + # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html default = [ ]; westmere = [ ]; - sandybridge = [ "westmere" ] ++ inferiors.westmere; - ivybridge = [ "sandybridge" ] ++ inferiors.sandybridge; - haswell = [ "ivybridge" ] ++ inferiors.ivybridge; - broadwell = [ "haswell" ] ++ inferiors.haswell; - skylake = [ "broadwell" ] ++ inferiors.broadwell; - skylake-avx512 = [ "skylake" ] ++ inferiors.skylake; + sandybridge = [ "westmere" ] ++ inferiors.westmere; + ivybridge = [ "sandybridge" ] ++ inferiors.sandybridge; + haswell = [ "ivybridge" ] ++ inferiors.ivybridge; + broadwell = [ "haswell" ] ++ inferiors.haswell; + skylake = [ "broadwell" ] ++ inferiors.broadwell; + skylake-avx512 = [ "skylake" ] ++ inferiors.skylake; + cannonlake = [ "skylake-avx512" ] ++ inferiors.skylake-avx512; + icelake-client = [ "cannonlake" ] ++ inferiors.cannonlake; + icelake-server = [ "icelake-client" ] ++ inferiors.icelake-client; + cascadelake = [ "skylake-avx512" ] ++ inferiors.cannonlake; + cooperlake = [ "cascadelake" ] ++ inferiors.cascadelake; + tigerlake = [ "icelake-server" ] ++ inferiors.icelake-server; # x86_64 AMD # TODO: fill this (need testing) diff --git a/lib/systems/doubles.nix b/lib/systems/doubles.nix index 23a44d02e85e4..383dd30bfdb2f 100644 --- a/lib/systems/doubles.nix +++ b/lib/systems/doubles.nix @@ -68,6 +68,7 @@ in { none = []; arm = filterDoubles predicates.isAarch32; + armv7 = filterDoubles predicates.isArmv7; aarch64 = filterDoubles predicates.isAarch64; x86 = filterDoubles predicates.isx86; i686 = filterDoubles predicates.isi686; @@ -75,6 +76,7 @@ in { 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; @@ -83,6 +85,7 @@ in { or1k = filterDoubles predicates.isOr1k; m68k = filterDoubles predicates.isM68k; s390 = filterDoubles predicates.isS390; + s390x = filterDoubles predicates.isS390x; js = filterDoubles predicates.isJavaScript; bigEndian = filterDoubles predicates.isBigEndian; diff --git a/lib/systems/inspect.nix b/lib/systems/inspect.nix index 53d84118bd30d..30615c9fde32c 100644 --- a/lib/systems/inspect.nix +++ b/lib/systems/inspect.nix @@ -7,6 +7,7 @@ let abis_ = abis; in let abis = lib.mapAttrs (_: abi: builtins.removeAttrs abi [ "assertions" ]) abis_; in rec { + # these patterns are to be matched against {host,build,target}Platform.parsed patterns = rec { isi686 = { cpu = cpuTypes.i686; }; isx86_32 = { cpu = { family = "x86"; bits = 32; }; }; @@ -22,6 +23,9 @@ rec { ]; isx86 = { cpu = { family = "x86"; }; }; isAarch32 = { cpu = { family = "arm"; bits = 32; }; }; + isArmv7 = map ({ arch, ... }: { cpu = { inherit arch; }; }) + (lib.filter (cpu: lib.hasPrefix "armv7" cpu.arch or "") + (lib.attrValues cpuTypes)); isAarch64 = { cpu = { family = "arm"; bits = 64; }; }; isAarch = { cpu = { family = "arm"; }; }; isMicroBlaze = { cpu = { family = "microblaze"; }; }; @@ -44,6 +48,7 @@ rec { isOr1k = { cpu = { family = "or1k"; }; }; isM68k = { cpu = { family = "m68k"; }; }; isS390 = { cpu = { family = "s390"; }; }; + isS390x = { cpu = { family = "s390"; bits = 64; }; }; isJavaScript = { cpu = cpuTypes.js; }; is32bit = { cpu = { bits = 32; }; }; @@ -77,8 +82,13 @@ rec { isMusl = with abis; map (a: { abi = a; }) [ musl musleabi musleabihf muslabin32 muslabi64 ]; isUClibc = with abis; map (a: { abi = a; }) [ uclibc uclibceabi uclibceabihf ]; - isEfi = map (family: { cpu.family = family; }) - [ "x86" "arm" "aarch64" ]; + isEfi = [ + { cpu = { family = "arm"; version = "6"; }; } + { cpu = { family = "arm"; version = "7"; }; } + { cpu = { family = "arm"; version = "8"; }; } + { cpu = { family = "riscv"; }; } + { cpu = { family = "x86"; }; } + ]; }; matchAnyAttrs = patterns: @@ -86,4 +96,13 @@ rec { else matchAttrs patterns; predicates = mapAttrs (_: matchAnyAttrs) patterns; + + # these patterns are to be matched against the entire + # {host,build,target}Platform structure; they include a `parsed={}` marker so + # 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; }; + }; } diff --git a/lib/tests/maintainer-module.nix b/lib/tests/maintainer-module.nix index 8cf8411b476a4..afa12587a98d7 100644 --- a/lib/tests/maintainer-module.nix +++ b/lib/tests/maintainer-module.nix @@ -7,7 +7,8 @@ in { type = types.str; }; email = lib.mkOption { - type = types.str; + type = types.nullOr types.str; + default = null; }; matrix = lib.mkOption { type = types.nullOr types.str; diff --git a/lib/tests/maintainers.nix b/lib/tests/maintainers.nix index 8a9a2b26efaf7..be1c8aaa85c52 100644 --- a/lib/tests/maintainers.nix +++ b/lib/tests/maintainers.nix @@ -1,5 +1,6 @@ # to run these tests (and the others) # nix-build nixpkgs/lib/tests/release.nix +# These tests should stay in sync with the comment in maintainers/maintainers-list.nix { # The pkgs used for dependencies for the testing itself pkgs ? import ../.. {} , lib ? pkgs.lib @@ -20,7 +21,7 @@ let ]; }).config; - checkGithubId = lib.optional (checkedAttrs.github != null && checkedAttrs.githubId == null) '' + checks = lib.optional (checkedAttrs.github != null && checkedAttrs.githubId == null) '' echo ${lib.escapeShellArg (lib.showOption prefix)}': If `github` is specified, `githubId` must be too.' # Calling this too often would hit non-authenticated API limits, but this # shouldn't happen since such errors will get fixed rather quickly @@ -28,8 +29,12 @@ let id=$(jq -r '.id' <<< "$info") echo "The GitHub ID for GitHub user ${checkedAttrs.github} is $id:" echo -e " githubId = $id;\n" + '' ++ lib.optional (checkedAttrs.email == null && checkedAttrs.github == null && checkedAttrs.matrix == null) '' + echo ${lib.escapeShellArg (lib.showOption prefix)}': At least one of `email`, `github` or `matrix` must be specified, so that users know how to reach you.' + '' ++ lib.optional (checkedAttrs.email != null && lib.hasSuffix "noreply.github.com" checkedAttrs.email) '' + echo ${lib.escapeShellArg (lib.showOption prefix)}': If an email address is given, it should allow people to reach you. If you do not want that, you can just provide `github` or `matrix` instead.' ''; - in lib.deepSeq checkedAttrs checkGithubId; + in lib.deepSeq checkedAttrs checks; missingGithubIds = lib.concatLists (lib.mapAttrsToList checkMaintainer lib.maintainers); diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index faf2b96530c1c..07d04f5356c7b 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -153,6 +153,11 @@ runTests { expected = "a,b,c"; }; + testConcatLines = { + expr = concatLines ["a" "b" "c"]; + expected = "a\nb\nc\n"; + }; + testSplitStringsSimple = { expr = strings.splitString "." "a.b.c.d"; expected = [ "a" "b" "c" "d" ]; @@ -342,6 +347,15 @@ runTests { expected = "Hello\\x20World"; }; + testEscapeURL = testAllTrue [ + ("" == strings.escapeURL "") + ("Hello" == strings.escapeURL "Hello") + ("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:/@$'()*,;") + ]; + testToInt = testAllTrue [ # Naive (123 == toInt "123") @@ -474,6 +488,11 @@ runTests { expected = [2 30 40 42]; }; + testReplicate = { + expr = replicate 3 "a"; + expected = ["a" "a" "a"]; + }; + testToIntShouldConvertStringToInt = { expr = toInt "27"; expected = 27; diff --git a/lib/tests/release.nix b/lib/tests/release.nix index f67892ab962f2..dbf6683d49a85 100644 --- a/lib/tests/release.nix +++ b/lib/tests/release.nix @@ -1,11 +1,11 @@ { # The pkgs used for dependencies for the testing itself # Don't test properties of pkgs.lib, but rather the lib in the parent directory - pkgs ? import ../.. {} // { lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; } + pkgs ? import ../.. {} // { lib = throw "pkgs.lib accessed, but the lib tests should use nixpkgs' lib path directly!"; }, + nix ? pkgs.nix, }: pkgs.runCommand "nixpkgs-lib-tests" { buildInputs = [ - pkgs.nix (import ./check-eval.nix) (import ./maintainers.nix { inherit pkgs; @@ -19,8 +19,12 @@ pkgs.runCommand "nixpkgs-lib-tests" { inherit pkgs; }) ]; + nativeBuildInputs = [ + nix + ]; + strictDeps = true; } '' - datadir="${pkgs.nix}/share" + datadir="${nix}/share" export TEST_ROOT=$(pwd)/test-tmp export NIX_BUILD_HOOK= export NIX_CONF_DIR=$TEST_ROOT/etc diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix index 27c5ff565ca04..88e2e4206d56a 100644 --- a/lib/tests/systems.nix +++ b/lib/tests/systems.nix @@ -16,12 +16,15 @@ with lib.systems.doubles; lib.runTests { testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox); testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ]; + testarmv7 = mseteq armv7 [ "armv7a-darwin" "armv7a-linux" "armv7l-linux" "armv7a-netbsd" "armv7l-netbsd" ]; testi686 = mseteq i686 [ "i686-linux" "i686-freebsd13" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ]; testmips = mseteq mips [ "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-freebsd13" "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" ]; |