diff options
author | Alyssa Ross <hi@alyssa.is> | 2022-12-06 19:57:55 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2023-02-08 13:48:30 +0000 |
commit | bf3aadfdd39aa197e18bade671fab6726349ffa4 (patch) | |
tree | 698567af766ed441d757b57a7b21e68d4a342a2b /nixpkgs/lib | |
parent | f4afc5a01d9539ce09e47494e679c51f80723d07 (diff) | |
parent | 99665eb45f58d959d2cb9e49ddb960c79d596f33 (diff) | |
download | nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar.gz nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar.bz2 nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar.lz nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar.xz nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.tar.zst nixlib-bf3aadfdd39aa197e18bade671fab6726349ffa4.zip |
Merge commit '99665eb45f58d959d2cb9e49ddb960c79d596f33'
Diffstat (limited to 'nixpkgs/lib')
35 files changed, 1259 insertions, 165 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix index 516fdd8d33fd..5575e9577029 100644 --- a/nixpkgs/lib/attrsets.nix +++ b/nixpkgs/lib/attrsets.nix @@ -248,7 +248,7 @@ rec { /* Apply fold functions to values grouped by key. Example: - foldAttrs (n: a: [n] ++ a) [] [{ a = 2; } { a = 3; }] + foldAttrs (item: acc: [item] ++ acc) [] [{ a = 2; } { a = 3; }] => { a = [ 2 3 ]; } */ foldAttrs = op: nul: diff --git a/nixpkgs/lib/customisation.nix b/nixpkgs/lib/customisation.nix index 234a528527d3..cc9a9b1c55d0 100644 --- a/nixpkgs/lib/customisation.nix +++ b/nixpkgs/lib/customisation.nix @@ -117,8 +117,55 @@ rec { callPackageWith = autoArgs: fn: args: let f = if lib.isFunction fn then fn else import fn; - auto = builtins.intersectAttrs (lib.functionArgs f) autoArgs; - in makeOverridable f (auto // args); + fargs = lib.functionArgs f; + + # All arguments that will be passed to the function + # This includes automatic ones and ones passed explicitly + allArgs = builtins.intersectAttrs fargs autoArgs // args; + + # A list of argument names that the function requires, but + # wouldn't be passed to it + missingArgs = lib.attrNames + # Filter out arguments that have a default value + (lib.filterAttrs (name: value: ! value) + # Filter out arguments that would be passed + (removeAttrs fargs (lib.attrNames allArgs))); + + # Get a list of suggested argument names for a given missing one + getSuggestions = arg: lib.pipe (autoArgs // args) [ + lib.attrNames + # Only use ones that are at most 2 edits away. While mork would work, + # levenshteinAtMost is only fast for 2 or less. + (lib.filter (lib.strings.levenshteinAtMost 2 arg)) + # Put strings with shorter distance first + (lib.sort (x: y: lib.strings.levenshtein x arg < lib.strings.levenshtein y arg)) + # Only take the first couple results + (lib.take 3) + # Quote all entries + (map (x: "\"" + x + "\"")) + ]; + + prettySuggestions = suggestions: + if suggestions == [] then "" + else if lib.length suggestions == 1 then ", did you mean ${lib.elemAt suggestions 0}?" + else ", did you mean ${lib.concatStringsSep ", " (lib.init suggestions)} or ${lib.last suggestions}?"; + + errorForArg = arg: + let + loc = builtins.unsafeGetAttrPos arg fargs; + # loc' can be removed once lib/minver.nix is >2.3.4, since that includes + # https://github.com/NixOS/nix/pull/3468 which makes loc be non-null + loc' = if loc != null then loc.file + ":" + toString loc.line + else if ! lib.isFunction fn then + toString fn + lib.optionalString (lib.sources.pathIsDirectory fn) "/default.nix" + else "<unknown location>"; + in "Function called without required argument \"${arg}\" at " + + "${loc'}${prettySuggestions (getSuggestions arg)}"; + + # Only show the error for the first missing argument + error = errorForArg (lib.head missingArgs); + + in if missingArgs == [] then makeOverridable f allArgs else throw error; /* Like callPackage, but for a function that returns an attribute diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix index 1f06283790a8..e2a93e63ac1f 100644 --- a/nixpkgs/lib/default.nix +++ b/nixpkgs/lib/default.nix @@ -36,6 +36,7 @@ let # constants licenses = callLibs ./licenses.nix; + sourceTypes = callLibs ./source-types.nix; systems = callLibs ./systems; # serialization @@ -70,7 +71,7 @@ let info showWarnings nixpkgsVersion version isInOldestRelease mod compare splitByAndCompare functionArgs setFunctionArgs isFunction toFunction - toHexString toBaseDigits; + toHexString toBaseDigits inPureEvalMode; inherit (self.fixedPoints) fix fix' converge extends composeExtensions composeManyExtensions makeExtensible makeExtensibleWithCustomName; inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath @@ -94,7 +95,8 @@ let concatImapStringsSep makeSearchPath makeSearchPathOutput makeLibraryPath makeBinPath optionalString hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape - escapeShellArg escapeShellArgs escapeRegex escapeXML replaceChars lowerChars + escapeShellArg escapeShellArgs isValidPosixName toShellVar toShellVars + escapeRegex escapeXML replaceChars lowerChars upperChars toLower toUpper addContextFrom splitString removePrefix removeSuffix versionOlder versionAtLeast getName getVersion @@ -108,7 +110,7 @@ let makeScope makeScopeWithSplicing; inherit (self.meta) addMetaAttrs dontDistribute setName updateName appendToName mapDerivationAttrset setPrio lowPrio lowPrioSet hiPrio - hiPrioSet getLicenseFromSpdxId; + hiPrioSet getLicenseFromSpdxId getExe; inherit (self.sources) pathType pathIsDirectory cleanSourceFilter cleanSource sourceByRegex sourceFilesBySuffices commitIdFromGitRepo cleanSourceWith pathHasContext @@ -129,7 +131,9 @@ let getValues getFiles optionAttrSetToDocList optionAttrSetToDocList' scrubOptionValue literalExpression literalExample literalDocBook - showOption showFiles unknownModule mkOption mkPackageOption; + showOption showOptionWithDefLocs showFiles + unknownModule mkOption mkPackageOption + mdDoc literalMD; inherit (self.types) isType setType defaultTypeMerge defaultFunctor isOptionType mkOptionType; inherit (self.asserts) diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix index 79ae9055ce3d..431b93c4ebbc 100644 --- a/nixpkgs/lib/generators.nix +++ b/nixpkgs/lib/generators.nix @@ -10,7 +10,7 @@ * are mostly generators themselves, called with * their respective default values; they can be reused. * - * Tests can be found in ./tests.nix + * Tests can be found in ./tests/misc.nix * Documentation in the manual, #sec-generators */ { lib }: @@ -108,7 +108,7 @@ rec { * The mk* configuration attributes can generically change * the way sections and key-value strings are generated. * - * For more examples see the test cases in ./tests.nix. + * For more examples see the test cases in ./tests/misc.nix. */ toINI = { # apply transformations (e.g. escapes) to section names @@ -130,6 +130,51 @@ rec { # map input to ini sections mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; + /* Generate an INI-style config file from an attrset + * specifying the global section (no header), and an + * attrset of sections to an attrset of key-value pairs. + * + * generators.toINIWithGlobalSection {} { + * globalSection = { + * someGlobalKey = "hi"; + * }; + * sections = { + * foo = { hi = "${pkgs.hello}"; ciao = "bar"; }; + * baz = { "also, integers" = 42; }; + * } + * + *> someGlobalKey=hi + *> + *> [baz] + *> also, integers=42 + *> + *> [foo] + *> ciao=bar + *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10 + * + * The mk* configuration attributes can generically change + * the way sections and key-value strings are generated. + * + * For more examples see the test cases in ./tests/misc.nix. + * + * If you don’t need a global section, you can also use + * `generators.toINI` directly, which only takes + * the part in `sections`. + */ + toINIWithGlobalSection = { + # apply transformations (e.g. escapes) to section names + mkSectionName ? (name: libStr.escape [ "[" "]" ] name), + # format a setting line from key and value + mkKeyValue ? mkKeyValueDefault {} "=", + # allow lists as values for duplicate keys + listsAsDuplicateKeys ? false + }: { globalSection, sections }: + ( if globalSection == {} + then "" + else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) + + "\n") + + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections); + /* Generate a git-config file from an attrset. * * It has two major differences from the regular INI format: @@ -206,6 +251,16 @@ rec { }: assert builtins.isInt depthLimit; let + specialAttrs = [ + "__functor" + "__functionArgs" + "__toString" + "__pretty" + ]; + stepIntoAttr = evalNext: name: + if builtins.elem name specialAttrs + then id + else evalNext; transform = depth: if depthLimit != null && depth > depthLimit then if throwOnDepthLimit @@ -216,7 +271,7 @@ rec { let evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); in - if isAttrs v then mapAttrs (const evalNext) v + if isAttrs v then mapAttrs (stepIntoAttr evalNext) v else if isList v then map evalNext v else transform (depth + 1) v; in diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix index 2928d11d8b22..56299612a0e6 100644 --- a/nixpkgs/lib/licenses.nix +++ b/nixpkgs/lib/licenses.nix @@ -55,6 +55,12 @@ in mkLicense lset) ({ 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/"; @@ -285,6 +291,11 @@ in mkLicense lset) ({ fullName = "DOC License"; }; + drl10 = { + spdxId = "DRL-1.0"; + fullName = "Detection Rule License 1.0"; + }; + eapl = { fullName = "EPSON AVASYS PUBLIC LICENSE"; url = "https://avasys.jp/hp/menu000000700/hpg000000603.htm"; @@ -509,6 +520,13 @@ in mkLicense lset) ({ free = false; }; + databricks-dbx = { + fullName = "DataBricks eXtensions aka dbx License"; + url = "https://github.com/databrickslabs/dbx/blob/743b579a4ac44531f764c6e522dbe5a81a7dc0e4/LICENSE"; + free = false; + redistributable = false; + }; + issl = { fullName = "Intel Simplified Software License"; url = "https://software.intel.com/en-us/license/intel-simplified-software-license"; diff --git a/nixpkgs/lib/lists.nix b/nixpkgs/lib/lists.nix index a030280c8dcc..36056e18065e 100644 --- a/nixpkgs/lib/lists.nix +++ b/nixpkgs/lib/lists.nix @@ -507,7 +507,7 @@ rec { compareLists compare [ "a" ] [] => 1 compareLists compare [ "a" "b" ] [ "a" "c" ] - => 1 + => -1 */ compareLists = cmp: a: b: if a == [] diff --git a/nixpkgs/lib/meta.nix b/nixpkgs/lib/meta.nix index 5b1f7ee5ff2d..74b94211552b 100644 --- a/nixpkgs/lib/meta.nix +++ b/nixpkgs/lib/meta.nix @@ -126,4 +126,18 @@ rec { lib.warn "getLicenseFromSpdxId: No license matches the given SPDX ID: ${licstr}" { shortName = licstr; } ); + + /* Get the path to the main program of a derivation with either + meta.mainProgram or pname or name + + Type: getExe :: derivation -> string + + Example: + getExe pkgs.hello + => "/nix/store/g124820p9hlv4lj8qplzxw1c44dxaw1k-hello-2.12/bin/hello" + getExe pkgs.mustache-go + => "/nix/store/am9ml4f4ywvivxnkiaqwr0hyxka1xjsf-mustache-go-1.3.0/bin/mustache" + */ + getExe = x: + "${lib.getBin x}/bin/${x.meta.mainProgram or (lib.getName x)}"; } diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix index 894104cc5790..7f1646e9b8bc 100644 --- a/nixpkgs/lib/modules.nix +++ b/nixpkgs/lib/modules.nix @@ -46,6 +46,7 @@ let showFiles showOption unknownModule + literalExpression ; showDeclPrefix = loc: decl: prefix: @@ -140,7 +141,7 @@ rec { # this module is used, to avoid conflicts and allow chaining of # extendModules. internalModule = rec { - _file = ./modules.nix; + _file = "lib/modules.nix"; key = _file; @@ -153,8 +154,94 @@ rec { # a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't # start a download when `pkgs` wasn't evaluated. type = types.lazyAttrsOf types.raw; - internal = true; - description = "Arguments passed to each module."; + # 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; + # TODO: Change the type of this option to a submodule with a + # freeformType, so that individual arguments can be documented + # separately + description = '' + Additional arguments passed to each module in addition to ones + like <literal>lib</literal>, <literal>config</literal>, + and <literal>pkgs</literal>, <literal>modulesPath</literal>. + </para> + <para> + This option is also available to all submodules. Submodules do not + inherit args from their parent module, nor do they provide args to + their parent module or sibling submodules. The sole exception to + this is the argument <literal>name</literal> which is provided by + parent modules to a submodule and contains the attribute name + the submodule is bound to, or a unique generated name if it is + not bound to an attribute. + </para> + <para> + Some arguments are already passed by default, of which the + following <emphasis>cannot</emphasis> be changed with this option: + <itemizedlist> + <listitem> + <para> + <varname>lib</varname>: The nixpkgs library. + </para> + </listitem> + <listitem> + <para> + <varname>config</varname>: The results of all options after merging the values from all modules together. + </para> + </listitem> + <listitem> + <para> + <varname>options</varname>: The options declared in all modules. + </para> + </listitem> + <listitem> + <para> + <varname>specialArgs</varname>: The <literal>specialArgs</literal> argument passed to <literal>evalModules</literal>. + </para> + </listitem> + <listitem> + <para> + All attributes of <varname>specialArgs</varname> + </para> + <para> + Whereas option values can generally depend on other option values + thanks to laziness, this does not apply to <literal>imports</literal>, which + must be computed statically before anything else. + </para> + <para> + For this reason, callers of the module system can provide <literal>specialArgs</literal> + which are available during import resolution. + </para> + <para> + For NixOS, <literal>specialArgs</literal> includes + <varname>modulesPath</varname>, which allows you to import + extra modules from the nixpkgs package tree without having to + somehow make the module aware of the location of the + <literal>nixpkgs</literal> or NixOS directories. + <programlisting> + { modulesPath, ... }: { + imports = [ + (modulesPath + "/profiles/minimal.nix") + ]; + } + </programlisting> + </para> + </listitem> + </itemizedlist> + </para> + <para> + For NixOS, the default value for this option includes at least this argument: + <itemizedlist> + <listitem> + <para> + <varname>pkgs</varname>: The nixpkgs package set according to + the <option>nixpkgs.pkgs</option> option. + </para> + </listitem> + </itemizedlist> + ''; }; _module.check = mkOption { @@ -179,6 +266,15 @@ rec { turned off. ''; }; + + _module.specialArgs = mkOption { + readOnly = true; + internal = true; + description = '' + Externally provided module arguments that can't be modified from + within a configuration, but can be used in module imports. + ''; + }; }; config = { @@ -186,6 +282,7 @@ rec { inherit extendModules; moduleType = type; }; + _module.specialArgs = specialArgs; }; }; @@ -258,7 +355,7 @@ rec { evalModules (evalModulesArgs // { modules = regularModules ++ modules; specialArgs = evalModulesArgs.specialArgs or {} // specialArgs; - prefix = extendArgs.prefix or evalModulesArgs.prefix; + prefix = extendArgs.prefix or evalModulesArgs.prefix or []; }); type = lib.types.submoduleWith { @@ -375,6 +472,7 @@ rec { config = addFreeformType (addMeta (m.config or {})); } else + lib.throwIfNot (isAttrs m) "module ${file} (${key}) does not look like a module." { _file = toString m._file or file; key = toString m.key or key; disabledModules = m.disabledModules or []; diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix index 8d0801775c46..84d9fd5e15de 100644 --- a/nixpkgs/lib/options.nix +++ b/nixpkgs/lib/options.nix @@ -7,6 +7,7 @@ let collect concatLists concatMap + concatMapStringsSep elemAt filter foldl' @@ -120,7 +121,7 @@ rec { Example: mkPackageOption pkgs "GHC" { default = [ "ghc" ]; - example = "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])"; + example = "pkgs.haskell.packages.ghc924.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 = { ... }; } */ @@ -241,6 +242,8 @@ rec { 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); @@ -280,6 +283,21 @@ rec { if ! isString text then throw "literalDocBook expects a string." else { _type = "literalDocBook"; inherit text; }; + /* Transition marker for documentation that's already migrated to markdown + syntax. + */ + mdDoc = text: + if ! isString text then throw "mdDoc expects a string." + else { _type = "mdDoc"; inherit text; }; + + /* For use in the `defaultText` and `example` option attributes. Causes the + given MD text to be inserted verbatim in the documentation, for when + a `literalExpression` would be too hard to read. + */ + literalMD = text: + if ! isString text then throw "literalMD expects a string." + else { _type = "literalMD"; inherit text; }; + # Helper functions. /* Convert an option, described as a list of the option parts in to a @@ -325,6 +343,11 @@ rec { in "\n- In `${def.file}'${result}" ) defs; + showOptionWithDefLocs = opt: '' + ${showOption opt.loc}, with values defined in: + ${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files} + ''; + unknownModule = "<unknown-file>"; } diff --git a/nixpkgs/lib/source-types.nix b/nixpkgs/lib/source-types.nix new file mode 100644 index 000000000000..c4f263dcf464 --- /dev/null +++ b/nixpkgs/lib/source-types.nix @@ -0,0 +1,19 @@ +{ lib }: + +let + defaultSourceType = tname: { + shortName = tname; + isSource = false; + }; +in lib.mapAttrs (tname: tset: defaultSourceType tname // tset) { + + fromSource = { + isSource = true; + }; + + binaryNativeCode = {}; + + binaryBytecode = {}; + + binaryFirmware = {}; +} diff --git a/nixpkgs/lib/strings.nix b/nixpkgs/lib/strings.nix index b2fd495e4c84..295d98900e99 100644 --- a/nixpkgs/lib/strings.nix +++ b/nixpkgs/lib/strings.nix @@ -17,6 +17,7 @@ rec { head isInt isList + isAttrs isString match parseDrvName @@ -253,10 +254,7 @@ rec { => false */ hasInfix = infix: content: - let - drop = x: substring 1 (stringLength x) x; - in hasPrefix infix content - || content != "" && hasInfix infix (drop content); + builtins.match ".*${escapeRegex infix}.*" "${content}" != null; /* Convert a string to a list of characters (i.e. singleton strings). This allows you to, e.g., map a function over each character. However, @@ -327,6 +325,66 @@ rec { */ escapeShellArgs = concatMapStringsSep " " escapeShellArg; + /* Test whether the given name is a valid POSIX shell variable name. + + Type: string -> bool + + Example: + isValidPosixName "foo_bar000" + => true + isValidPosixName "0-bad.jpg" + => false + */ + isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null; + + /* Translate a Nix value into a shell variable declaration, with proper escaping. + + The value can be a string (mapped to a regular variable), a list of strings + (mapped to a Bash-style array) or an attribute set of strings (mapped to a + Bash-style associative array). Note that "string" includes string-coercible + values like paths or derivations. + + Strings are translated into POSIX sh-compatible code; lists and attribute sets + assume a shell that understands Bash syntax (e.g. Bash or ZSH). + + Type: string -> (string | listOf string | attrsOf string) -> string + + Example: + '' + ${toShellVar "foo" "some string"} + [[ "$foo" == "some string" ]] + '' + */ + toShellVar = name: value: + lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( + if isAttrs value && ! isCoercibleToString 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 into corresponding shell variable declarations + using `toShellVar`. + + Type: attrsOf (string | listOf string | attrsOf string) -> string + + Example: + let + foo = "value"; + bar = foo; + in '' + ${toShellVars { inherit foo bar; }} + [[ "$foo" == "$bar" ]] + '' + */ + toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars); + /* Turn a string into a Nix expression representing that string Type: string -> string @@ -756,7 +814,14 @@ rec { sanitizeDerivationName pkgs.hello => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10" */ - sanitizeDerivationName = string: lib.pipe string [ + 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 @@ -774,4 +839,131 @@ rec { (x: if stringLength x == 0 then "unknown" else x) ]; + /* Computes the Levenshtein distance between two strings. + Complexity O(n*m) where n and m are the lengths of the strings. + Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742 + + Type: levenshtein :: string -> string -> int + + Example: + levenshtein "foo" "foo" + => 0 + levenshtein "book" "hook" + => 1 + levenshtein "hello" "Heyo" + => 3 + */ + 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 common to both strings. + */ + 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; + + /* Returns the length of the suffix common to both strings. + */ + 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; + + /* Returns whether the levenshtein distance between two strings is at most some value + Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise + + Type: levenshteinAtMost :: int -> string -> string -> bool + + Example: + levenshteinAtMost 0 "foo" "foo" + => true + levenshteinAtMost 1 "foo" "boa" + => false + levenshteinAtMost 2 "foo" "boa" + => true + levenshteinAtMost 2 "This is a sentence" "this is a sentense." + => false + levenshteinAtMost 3 "This is a sentence" "this is a sentense." + => true + + */ + 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 + # 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 + # 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; + + 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/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix index 7ddd5b8a5812..2990afde3e9a 100644 --- a/nixpkgs/lib/systems/default.nix +++ b/nixpkgs/lib/systems/default.nix @@ -8,7 +8,13 @@ rec { platforms = import ./platforms.nix { inherit lib; }; examples = import ./examples.nix { inherit lib; }; architectures = import ./architectures.nix { inherit lib; }; - supported = import ./supported.nix { inherit lib; }; + + /* List of all Nix system doubles the nixpkgs flake will expose the package set + for. All systems listed here must be supported by nixpkgs as `localSystem`. + + **Warning**: This attribute is considered experimental and is subject to change. + */ + flakeExposed = import ./flake-systems.nix { }; # Elaborate a `localSystem` or `crossSystem` so that it contains everything # necessary. @@ -25,8 +31,12 @@ rec { # Either of these can be losslessly-extracted from `parsed` iff parsing succeeds. system = parse.doubleFromSystem final.parsed; config = parse.tripleFromSystem final.parsed; - # Determine whether we are compatible with the provided CPU - isCompatible = platform: parse.isCompatible final.parsed.cpu platform.parsed.cpu; + # 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 libc = /**/ if final.isDarwin then "libSystem" @@ -159,7 +169,7 @@ rec { wine = (pkgs.winePackagesFor wine-name).minimal; in if final.parsed.kernel.name == pkgs.stdenv.hostPlatform.parsed.kernel.name && - pkgs.stdenv.hostPlatform.isCompatible final + pkgs.stdenv.hostPlatform.canExecute final then "${pkgs.runtimeShell} -c '\"$@\"' --" else if final.isWindows then "${wine}/bin/${wine-name}" diff --git a/nixpkgs/lib/systems/doubles.nix b/nixpkgs/lib/systems/doubles.nix index 27cdaf6a7233..90a6eb9f35c9 100644 --- a/nixpkgs/lib/systems/doubles.nix +++ b/nixpkgs/lib/systems/doubles.nix @@ -41,7 +41,7 @@ let # none "aarch64_be-none" "aarch64-none" "arm-none" "armv6l-none" "avr-none" "i686-none" "msp430-none" "or1k-none" "m68k-none" "powerpc-none" "powerpcle-none" - "riscv32-none" "riscv64-none" "s390-none" "s390x-none" "vc4-none" + "riscv32-none" "riscv64-none" "rx-none" "s390-none" "s390x-none" "vc4-none" "x86_64-none" # OpenBSD @@ -74,6 +74,9 @@ in { mips = filterDoubles predicates.isMips; mmix = filterDoubles predicates.isMmix; 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; diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix index 997a7a8c273a..65dc9c07e346 100644 --- a/nixpkgs/lib/systems/examples.nix +++ b/nixpkgs/lib/systems/examples.nix @@ -57,30 +57,28 @@ rec { armv7a-android-prebuilt = { config = "armv7a-unknown-linux-androideabi"; rustc.config = "armv7-linux-androideabi"; - sdkVer = "29"; - ndkVer = "21"; + sdkVer = "28"; + ndkVer = "24"; useAndroidPrebuilt = true; } // platforms.armv7a-android; aarch64-android-prebuilt = { config = "aarch64-unknown-linux-android"; rustc.config = "aarch64-linux-android"; - sdkVer = "29"; - ndkVer = "21"; + sdkVer = "28"; + ndkVer = "24"; useAndroidPrebuilt = true; }; aarch64-android = { config = "aarch64-unknown-linux-android"; sdkVer = "30"; - ndkVer = "21"; + ndkVer = "24"; libc = "bionic"; useAndroidPrebuilt = false; useLLVM = true; }; - scaleway-c1 = armv7l-hf-multiplatform // platforms.scaleway-c1; - pogoplug4 = { config = "armv5tel-unknown-linux-gnueabi"; } // platforms.pogoplug4; @@ -93,25 +91,23 @@ rec { config = "mipsel-unknown-linux-gnu"; } // platforms.fuloong2f_n32; - # MIPS ABI table transcribed from here: https://wiki.debian.org/Multiarch/Tuples - # can execute on 32bit chip - mips-linux-gnu = { config = "mips-linux-gnu"; } // platforms.gcc_mips32r2_o32; - mipsel-linux-gnu = { config = "mipsel-linux-gnu"; } // platforms.gcc_mips32r2_o32; - mipsisa32r6-linux-gnu = { config = "mipsisa32r6-linux-gnu"; } // platforms.gcc_mips32r6_o32; - mipsisa32r6el-linux-gnu = { config = "mipsisa32r6el-linux-gnu"; } // platforms.gcc_mips32r6_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; + mipsisa32r6-linux-gnu = { config = "mipsisa32r6-unknown-linux-gnu"; } // platforms.gcc_mips32r6_o32; + mipsisa32r6el-linux-gnu = { config = "mipsisa32r6el-unknown-linux-gnu"; } // platforms.gcc_mips32r6_o32; # require 64bit chip (for more registers, 64-bit floating point, 64-bit "long long") but use 32bit pointers - mips64-linux-gnuabin32 = { config = "mips64-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; - mips64el-linux-gnuabin32 = { config = "mips64el-linux-gnuabin32"; } // platforms.gcc_mips64r2_n32; - mipsisa64r6-linux-gnuabin32 = { config = "mipsisa64r6-linux-gnuabin32"; } // platforms.gcc_mips64r6_n32; - mipsisa64r6el-linux-gnuabin32 = { config = "mipsisa64r6el-linux-gnuabin32"; } // platforms.gcc_mips64r6_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; + mipsisa64r6-linux-gnuabin32 = { config = "mipsisa64r6-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r6_n32; + mipsisa64r6el-linux-gnuabin32 = { config = "mipsisa64r6el-unknown-linux-gnuabin32"; } // platforms.gcc_mips64r6_n32; # 64bit pointers - mips64-linux-gnuabi64 = { config = "mips64-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; - mips64el-linux-gnuabi64 = { config = "mips64el-linux-gnuabi64"; } // platforms.gcc_mips64r2_64; - mipsisa64r6-linux-gnuabi64 = { config = "mipsisa64r6-linux-gnuabi64"; } // platforms.gcc_mips64r6_64; - mipsisa64r6el-linux-gnuabi64 = { config = "mipsisa64r6el-linux-gnuabi64"; } // platforms.gcc_mips64r6_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; + mipsisa64r6-linux-gnuabi64 = { config = "mipsisa64r6-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r6_64; + mipsisa64r6el-linux-gnuabi64 = { config = "mipsisa64r6el-unknown-linux-gnuabi64"; } // platforms.gcc_mips64r6_64; muslpi = raspberryPi // { config = "armv6l-unknown-linux-musleabihf"; @@ -145,6 +141,11 @@ rec { libc = "newlib"; }; + rx-embedded = { + config = "rx-none-elf"; + libc = "newlib"; + }; + msp430 = { config = "msp430-elf"; libc = "newlib"; @@ -303,8 +304,6 @@ rec { # BSDs - amd64-netbsd = lib.warn "The amd64-netbsd system example is deprecated. Use x86_64-netbsd instead." x86_64-netbsd; - x86_64-netbsd = { config = "x86_64-unknown-netbsd"; libc = "nblibc"; diff --git a/nixpkgs/lib/systems/flake-systems.nix b/nixpkgs/lib/systems/flake-systems.nix new file mode 100644 index 000000000000..74124c32e836 --- /dev/null +++ b/nixpkgs/lib/systems/flake-systems.nix @@ -0,0 +1,29 @@ +# See [RFC 46] for mandated platform support and ../../pkgs/stdenv for +# implemented platform support. This list is mainly descriptive, i.e. all +# system doubles for platforms where nixpkgs can do native compiliation +# reasonably well are included. +# +# [RFC 46]: https://github.com/NixOS/rfcs/blob/master/rfcs/0046-platform-support-tiers.md +{ }: + +[ + # Tier 1 + "x86_64-linux" + # Tier 2 + "aarch64-linux" + "x86_64-darwin" + # Tier 3 + "armv6l-linux" + "armv7l-linux" + "i686-linux" + "mipsel-linux" + + # Other platforms with sufficient support in stdenv which is not formally + # mandated by their platform tier. + "aarch64-darwin" + "armv5tel-linux" + "powerpc64le-linux" + "riscv64-linux" + + # "x86_64-freebsd" is excluded because it is mostly broken +] diff --git a/nixpkgs/lib/systems/inspect.nix b/nixpkgs/lib/systems/inspect.nix index 89cac575c67d..dbffca0300b5 100644 --- a/nixpkgs/lib/systems/inspect.nix +++ b/nixpkgs/lib/systems/inspect.nix @@ -11,11 +11,12 @@ rec { isi686 = { cpu = cpuTypes.i686; }; isx86_32 = { cpu = { family = "x86"; bits = 32; }; }; isx86_64 = { cpu = { family = "x86"; bits = 64; }; }; - isPowerPC = { cpu = cpuTypes.powerpc; }; isPower = { cpu = { family = "power"; }; }; + isPower64 = { cpu = { family = "power"; bits = 64; }; }; isx86 = { cpu = { family = "x86"; }; }; isAarch32 = { cpu = { family = "arm"; bits = 32; }; }; isAarch64 = { cpu = { family = "arm"; bits = 64; }; }; + isAarch = { cpu = { family = "arm"; }; }; isMips = { cpu = { family = "mips"; }; }; isMips32 = { cpu = { family = "mips"; bits = 32; }; }; isMips64 = { cpu = { family = "mips"; bits = 64; }; }; @@ -23,6 +24,9 @@ rec { 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"; }; }; isWasm = { cpu = { family = "wasm"; }; }; isMsp430 = { cpu = { family = "msp430"; }; }; diff --git a/nixpkgs/lib/systems/parse.nix b/nixpkgs/lib/systems/parse.nix index 3ceddbb599b9..9d2571c993a9 100644 --- a/nixpkgs/lib/systems/parse.nix +++ b/nixpkgs/lib/systems/parse.nix @@ -116,6 +116,7 @@ rec { 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"; }; @@ -147,8 +148,10 @@ rec { # Every CPU is compatible with itself. # - (transitivity) # If A is compatible with B and B is compatible with C then A is compatible with C. - # - (compatible under multiple endianness) - # CPUs with multiple modes of endianness are pairwise compatible. + # + # 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 = a: b: with cpuTypes; lib.any lib.id [ # x86 (b == i386 && isCompatible a i486) @@ -190,22 +193,13 @@ rec { (b == aarch64 && a == armv8a) (b == armv8a && isCompatible a aarch64) - (b == aarch64 && a == aarch64_be) - (b == aarch64_be && isCompatible a aarch64) - # PowerPC (b == powerpc && isCompatible a powerpc64) - (b == powerpcle && isCompatible a powerpc) - (b == powerpc && a == powerpcle) - (b == powerpc64le && isCompatible a powerpc64) - (b == powerpc64 && a == powerpc64le) + (b == powerpcle && isCompatible a powerpc64le) # MIPS (b == mips && isCompatible a mips64) - (b == mips && a == mipsel) - (b == mipsel && isCompatible a mips) - (b == mips64 && a == mips64el) - (b == mips64el && isCompatible a mips64) + (b == mipsel && isCompatible a mips64el) # RISCV (b == riscv32 && isCompatible a riscv64) diff --git a/nixpkgs/lib/systems/platforms.nix b/nixpkgs/lib/systems/platforms.nix index 04d55416242e..41c25484cea0 100644 --- a/nixpkgs/lib/systems/platforms.nix +++ b/nixpkgs/lib/systems/platforms.nix @@ -3,7 +3,7 @@ # targetPlatform, etc) containing at least the minimal set of attrs # required (see types.parsedPlatform in lib/systems/parse.nix). This # file takes an already-valid platform and further elaborates it with -# optional fields such as linux-kernel, gcc, etc. +# optional fields; currently these are: linux-kernel, gcc, and rustc. { lib }: rec { @@ -242,13 +242,6 @@ rec { }; }; - scaleway-c1 = armv7l-hf-multiplatform // { - gcc = { - cpu = "cortex-a9"; - fpu = "vfpv3"; - }; - }; - utilite = { linux-kernel = { name = "utilite"; @@ -490,8 +483,8 @@ rec { }; # can execute on 32bit chip - gcc_mips32r2_o32 = { gcc = { arch = "mips32r2"; abi = "o32"; }; }; - gcc_mips32r6_o32 = { gcc = { arch = "mips32r6"; abi = "o32"; }; }; + gcc_mips32r2_o32 = { gcc = { arch = "mips32r2"; abi = "32"; }; }; + gcc_mips32r6_o32 = { gcc = { arch = "mips32r6"; abi = "32"; }; }; gcc_mips64r2_n32 = { gcc = { arch = "mips64r2"; abi = "n32"; }; }; gcc_mips64r6_n32 = { gcc = { arch = "mips64r6"; abi = "n32"; }; }; gcc_mips64r2_64 = { gcc = { arch = "mips64r2"; abi = "64"; }; }; @@ -500,7 +493,7 @@ rec { # based on: # https://www.mail-archive.com/qemu-discuss@nongnu.org/msg05179.html # https://gmplib.org/~tege/qemu.html#mips64-debian - mips64el-qemu-linux-gnuabi64 = (import ./examples).mips64el-linux-gnuabi64 // { + mips64el-qemu-linux-gnuabi64 = { linux-kernel = { name = "mips64el"; baseConfig = "64r2el_defconfig"; @@ -568,5 +561,5 @@ rec { else if platform.parsed.cpu == lib.systems.parse.cpuTypes.powerpc64le then powernv - else pc; + else { }; } diff --git a/nixpkgs/lib/systems/supported.nix b/nixpkgs/lib/systems/supported.nix deleted file mode 100644 index a1c038a5c8bc..000000000000 --- a/nixpkgs/lib/systems/supported.nix +++ /dev/null @@ -1,26 +0,0 @@ -# Supported systems according to RFC0046's definition. -# -# https://github.com/NixOS/rfcs/blob/master/rfcs/0046-platform-support-tiers.md -{ lib }: -rec { - # List of systems that are built by Hydra. - hydra = tier1 ++ tier2 ++ tier3 ++ [ - "aarch64-darwin" - ]; - - tier1 = [ - "x86_64-linux" - ]; - - tier2 = [ - "aarch64-linux" - "x86_64-darwin" - ]; - - tier3 = [ - "armv6l-linux" - "armv7l-linux" - "i686-linux" - "mipsel-linux" - ]; -} diff --git a/nixpkgs/lib/tests/maintainer-module.nix b/nixpkgs/lib/tests/maintainer-module.nix new file mode 100644 index 000000000000..8cf8411b476a --- /dev/null +++ b/nixpkgs/lib/tests/maintainer-module.nix @@ -0,0 +1,31 @@ +{ lib, ... }: +let + inherit (lib) types; +in { + options = { + name = lib.mkOption { + type = types.str; + }; + email = lib.mkOption { + type = types.str; + }; + matrix = lib.mkOption { + type = types.nullOr types.str; + default = null; + }; + github = lib.mkOption { + type = types.nullOr types.str; + default = null; + }; + githubId = lib.mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + }; + keys = lib.mkOption { + type = types.listOf (types.submodule { + options.fingerprint = lib.mkOption { type = types.str; }; + }); + default = []; + }; + }; +} diff --git a/nixpkgs/lib/tests/maintainers.nix b/nixpkgs/lib/tests/maintainers.nix index 3cbfba569481..935d256d218d 100644 --- a/nixpkgs/lib/tests/maintainers.nix +++ b/nixpkgs/lib/tests/maintainers.nix @@ -1,50 +1,19 @@ # to run these tests (and the others) # nix-build nixpkgs/lib/tests/release.nix { # The pkgs used for dependencies for the testing itself - pkgs -, lib + pkgs ? import ../.. {} +, lib ? pkgs.lib }: let inherit (lib) types; - - maintainerModule = { config, ... }: { - options = { - name = lib.mkOption { - type = types.str; - }; - email = lib.mkOption { - type = types.str; - }; - matrix = lib.mkOption { - type = types.nullOr types.str; - default = null; - }; - github = lib.mkOption { - type = types.nullOr types.str; - default = null; - }; - githubId = lib.mkOption { - type = types.nullOr types.ints.unsigned; - default = null; - }; - keys = lib.mkOption { - type = types.listOf (types.submodule { - options.longkeyid = lib.mkOption { type = types.str; }; - options.fingerprint = lib.mkOption { type = types.str; }; - }); - default = []; - }; - }; - }; - checkMaintainer = handle: uncheckedAttrs: let prefix = [ "lib" "maintainers" handle ]; checkedAttrs = (lib.modules.evalModules { inherit prefix; modules = [ - maintainerModule + ./maintainer-module.nix { _file = toString ../../maintainers/maintainer-list.nix; config = uncheckedAttrs; diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix index 271119031395..584a946e92cc 100644 --- a/nixpkgs/lib/tests/misc.nix +++ b/nixpkgs/lib/tests/misc.nix @@ -22,7 +22,6 @@ in runTests { - # TRIVIAL testId = { @@ -251,6 +250,68 @@ runTests { expected = ""test" 'test' < & >"; }; + testToShellVars = { + expr = '' + ${toShellVars { + STRing01 = "just a 'string'"; + _array_ = [ "with" "more strings" ]; + assoc."with some" = '' + strings + possibly newlines + ''; + drv = { + outPath = "/drv"; + foo = "ignored attribute"; + }; + path = /path; + stringable = { + __toString = _: "hello toString"; + bar = "ignored attribute"; + }; + }} + ''; + expected = '' + STRing01='just a '\'''string'\'''' + declare -a _array_=('with' 'more strings') + declare -A assoc=(['with some']='strings + possibly newlines + ') + drv='/drv' + path='/path' + stringable='hello toString' + ''; + }; + + testHasInfixFalse = { + expr = hasInfix "c" "abde"; + expected = false; + }; + + testHasInfixTrue = { + expr = hasInfix "c" "abcde"; + expected = true; + }; + + testHasInfixDerivation = { + expr = hasInfix "hello" (import ../.. { system = "x86_64-linux"; }).hello; + expected = true; + }; + + testHasInfixPath = { + expr = hasInfix "tests" ./.; + expected = true; + }; + + testHasInfixPathStoreDir = { + expr = hasInfix builtins.storeDir ./.; + expected = true; + }; + + testHasInfixToString = { + expr = hasInfix "a" { __toString = _: "a"; }; + expected = true; + }; + # LISTS testFilter = { @@ -471,6 +532,66 @@ runTests { ''; }; + testToINIWithGlobalSectionEmpty = { + expr = generators.toINIWithGlobalSection {} { + globalSection = { + }; + sections = { + }; + }; + expected = '' + ''; + }; + + testToINIWithGlobalSectionGlobalEmptyIsTheSameAsToINI = + let + sections = { + "section 1" = { + attribute1 = 5; + x = "Me-se JarJar Binx"; + }; + "foo" = { + "he\\h=he" = "this is okay"; + }; + }; + in { + expr = + generators.toINIWithGlobalSection {} { + globalSection = {}; + sections = sections; + }; + expected = generators.toINI {} sections; + }; + + testToINIWithGlobalSectionFull = { + expr = generators.toINIWithGlobalSection {} { + globalSection = { + foo = "bar"; + test = false; + }; + sections = { + "section 1" = { + attribute1 = 5; + x = "Me-se JarJar Binx"; + }; + "foo" = { + "he\\h=he" = "this is okay"; + }; + }; + }; + expected = '' + foo=bar + test=false + + [foo] + he\h\=he=this is okay + + [section 1] + attribute1=5 + x=Me-se JarJar Binx + ''; + }; + /* right now only invocation check */ testToJSONSimple = let val = { @@ -553,6 +674,21 @@ runTests { expected = false; }; + testWithRecursionDealsWithFunctors = + let + functor = { + __functor = self: { a, b, }: null; + }; + a = { + value = "1234"; + b = functor; + c.d = functor; + }; + in { + expr = generators.toPretty { } (generators.withRecursion { depthLimit = 1; throwOnDepthLimit = false; } a); + expected = "{\n b = <function, args: {a, b}>;\n c = {\n d = \"<unevaluated>\";\n };\n value = \"<unevaluated>\";\n}"; + }; + testToPrettyMultiline = { expr = mapAttrs (const (generators.toPretty { })) rec { list = [ 3 4 [ false ] ]; @@ -649,6 +785,11 @@ runTests { expected = "foo"; }; + testSanitizeDerivationNameUnicode = testSanitizeDerivationName { + name = "fö"; + expected = "f-"; + }; + testSanitizeDerivationNameAscii = testSanitizeDerivationName { name = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; expected = "-+--.-0123456789-=-?-ABCDEFGHIJKLMNOPQRSTUVWXYZ-_-abcdefghijklmnopqrstuvwxyz-"; @@ -691,7 +832,7 @@ runTests { locs = filter (o: ! o.internal) (optionAttrSetToDocList options); in map (o: o.loc) locs; - expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ]; + expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ]; }; testCartesianProductOfEmptySet = { @@ -913,4 +1054,156 @@ runTests { }; }; + ## Levenshtein distance functions and co. + testCommonPrefixLengthEmpty = { + expr = strings.commonPrefixLength "" "hello"; + expected = 0; + }; + + testCommonPrefixLengthSame = { + expr = strings.commonPrefixLength "hello" "hello"; + expected = 5; + }; + + testCommonPrefixLengthDiffering = { + expr = strings.commonPrefixLength "hello" "hey"; + expected = 2; + }; + + testCommonSuffixLengthEmpty = { + expr = strings.commonSuffixLength "" "hello"; + expected = 0; + }; + + testCommonSuffixLengthSame = { + expr = strings.commonSuffixLength "hello" "hello"; + expected = 5; + }; + + testCommonSuffixLengthDiffering = { + expr = strings.commonSuffixLength "test" "rest"; + expected = 3; + }; + + testLevenshteinEmpty = { + expr = strings.levenshtein "" ""; + expected = 0; + }; + + testLevenshteinOnlyAdd = { + expr = strings.levenshtein "" "hello there"; + expected = 11; + }; + + testLevenshteinOnlyRemove = { + expr = strings.levenshtein "hello there" ""; + expected = 11; + }; + + testLevenshteinOnlyTransform = { + expr = strings.levenshtein "abcdef" "ghijkl"; + expected = 6; + }; + + testLevenshteinMixed = { + expr = strings.levenshtein "kitchen" "sitting"; + expected = 5; + }; + + testLevenshteinAtMostZeroFalse = { + expr = strings.levenshteinAtMost 0 "foo" "boo"; + expected = false; + }; + + testLevenshteinAtMostZeroTrue = { + expr = strings.levenshteinAtMost 0 "foo" "foo"; + expected = true; + }; + + testLevenshteinAtMostOneFalse = { + expr = strings.levenshteinAtMost 1 "car" "ct"; + expected = false; + }; + + testLevenshteinAtMostOneTrue = { + expr = strings.levenshteinAtMost 1 "car" "cr"; + expected = true; + }; + + # We test levenshteinAtMost 2 particularly well because it uses a complicated + # implementation + testLevenshteinAtMostTwoIsEmpty = { + expr = strings.levenshteinAtMost 2 "" ""; + expected = true; + }; + + testLevenshteinAtMostTwoIsZero = { + expr = strings.levenshteinAtMost 2 "abcdef" "abcdef"; + expected = true; + }; + + testLevenshteinAtMostTwoIsOne = { + expr = strings.levenshteinAtMost 2 "abcdef" "abddef"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff0False = { + expr = strings.levenshteinAtMost 2 "abcdef" "aczyef"; + expected = false; + }; + + testLevenshteinAtMostTwoDiff0Outer = { + expr = strings.levenshteinAtMost 2 "abcdef" "zbcdez"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff0DelLeft = { + expr = strings.levenshteinAtMost 2 "abcdef" "bcdefz"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff0DelRight = { + expr = strings.levenshteinAtMost 2 "abcdef" "zabcde"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff1False = { + expr = strings.levenshteinAtMost 2 "abcdef" "bddez"; + expected = false; + }; + + testLevenshteinAtMostTwoDiff1DelLeft = { + expr = strings.levenshteinAtMost 2 "abcdef" "bcdez"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff1DelRight = { + expr = strings.levenshteinAtMost 2 "abcdef" "zbcde"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff2False = { + expr = strings.levenshteinAtMost 2 "hello" "hxo"; + expected = false; + }; + + testLevenshteinAtMostTwoDiff2True = { + expr = strings.levenshteinAtMost 2 "hello" "heo"; + expected = true; + }; + + testLevenshteinAtMostTwoDiff3 = { + expr = strings.levenshteinAtMost 2 "hello" "ho"; + expected = false; + }; + + testLevenshteinAtMostThreeFalse = { + expr = strings.levenshteinAtMost 3 "hello" "Holla!"; + expected = false; + }; + + testLevenshteinAtMostThreeTrue = { + expr = strings.levenshteinAtMost 3 "hello" "Holla"; + expected = true; + }; } diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh index 8050c6539fc2..c92cc62023b5 100755 --- a/nixpkgs/lib/tests/modules.sh +++ b/nixpkgs/lib/tests/modules.sh @@ -194,6 +194,17 @@ checkConfigOutput '^"submodule"$' options.submodule.type.description ./declare-s ## Paths should be allowed as values and work as expected checkConfigOutput '^true$' config.submodule.enable ./declare-submoduleWith-path.nix +## deferredModule +# default module is merged into nodes.foo +checkConfigOutput '"beta"' config.nodes.foo.settingsDict.c ./deferred-module.nix +# errors from the default module are reported with accurate location +checkConfigError 'In `the-file-that-contains-the-bad-config.nix, via option default'\'': "bogus"' config.nodes.foo.bottom ./deferred-module.nix +checkConfigError '.*lib/tests/modules/deferred-module-error.nix, via option deferred [(]:anon-1:anon-1:anon-1[)] does not look like a module.' config.result ./deferred-module-error.nix + +# Check the file location information is propagated into submodules +checkConfigOutput the-file.nix config.submodule.internalFiles.0 ./submoduleFiles.nix + + # Check that disabledModules works recursively and correctly checkConfigOutput '^true$' config.enable ./disable-recursive/main.nix checkConfigOutput '^true$' config.enable ./disable-recursive/{main.nix,disable-foo.nix} @@ -290,10 +301,12 @@ checkConfigOutput '^"a b"$' config.result ./functionTo/merging-list.nix checkConfigError 'A definition for option .fun.\[function body\]. is not of type .string.. Definition values:\n\s*- In .*wrong-type.nix' config.result ./functionTo/wrong-type.nix checkConfigOutput '^"b a"$' config.result ./functionTo/list-order.nix checkConfigOutput '^"a c"$' config.result ./functionTo/merging-attrs.nix +checkConfigOutput '^"a bee"$' config.result ./functionTo/submodule-options.nix +checkConfigOutput '^"fun.\[function body\].a fun.\[function body\].b"$' config.optionsResult ./functionTo/submodule-options.nix # moduleType checkConfigOutput '^"a b"$' config.resultFoo ./declare-variants.nix ./define-variant.nix -checkConfigOutput '^"a y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix +checkConfigOutput '^"a b y z"$' config.resultFooBar ./declare-variants.nix ./define-variant.nix checkConfigOutput '^"a b c"$' config.resultFooFoo ./declare-variants.nix ./define-variant.nix ## emptyValue's @@ -313,7 +326,7 @@ checkConfigOutput "bar" config.priorities ./raw.nix ## Option collision checkConfigError \ - 'The option .set. in module .*/declare-set.nix. would be a parent of the following options, but its type .attribute set of signed integers. does not support nested options.\n\s*- option[(]s[)] with prefix .set.enable. in module .*/declare-enable-nested.nix.' \ + 'The option .set. in module .*/declare-set.nix. would be a parent of the following options, but its type .attribute set of signed integer. does not support nested options.\n\s*- option[(]s[)] with prefix .set.enable. in module .*/declare-enable-nested.nix.' \ config.set \ ./declare-set.nix ./declare-enable-nested.nix @@ -327,6 +340,10 @@ checkConfigError 'The option .theOption.nested. in .other.nix. is already declar # Test that types.optionType leaves types untouched as long as they don't need to be merged checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survives-type-merge.nix +# Anonymous submodules don't get nixed by import resolution/deduplication +# because of an `extendModules` bug, issue 168767. +checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix + cat <<EOF ====== module tests ====== $pass Pass diff --git a/nixpkgs/lib/tests/modules/deferred-module-error.nix b/nixpkgs/lib/tests/modules/deferred-module-error.nix new file mode 100644 index 000000000000..d48ae092e8fe --- /dev/null +++ b/nixpkgs/lib/tests/modules/deferred-module-error.nix @@ -0,0 +1,20 @@ +{ config, lib, ... }: +let + inherit (lib) types mkOption setDefaultModuleLocation evalModules; + inherit (types) deferredModule lazyAttrsOf submodule str raw enum; +in +{ + options = { + deferred = mkOption { + type = deferredModule; + }; + result = mkOption { + default = (evalModules { modules = [ config.deferred ]; }).config.result; + }; + }; + config = { + deferred = { ... }: + # this should be an attrset, so this fails + true; + }; +} diff --git a/nixpkgs/lib/tests/modules/deferred-module.nix b/nixpkgs/lib/tests/modules/deferred-module.nix new file mode 100644 index 000000000000..d03c60b029bf --- /dev/null +++ b/nixpkgs/lib/tests/modules/deferred-module.nix @@ -0,0 +1,58 @@ +{ lib, ... }: +let + inherit (lib) types mkOption setDefaultModuleLocation; + inherit (types) deferredModule lazyAttrsOf submodule str raw enum; +in +{ + imports = [ + # generic module, declaring submodules: + # - nodes.<name> + # - default + # where all nodes include the default + ({ config, ... }: { + _file = "generic.nix"; + options.nodes = mkOption { + type = lazyAttrsOf (submodule { imports = [ config.default ]; }); + default = {}; + }; + options.default = mkOption { + type = deferredModule; + default = { }; + description = '' + Module that is included in all nodes. + ''; + }; + }) + + { + _file = "default-1.nix"; + default = { config, ... }: { + options.settingsDict = lib.mkOption { type = lazyAttrsOf str; default = {}; }; + options.bottom = lib.mkOption { type = enum []; }; + }; + } + + { + _file = "default-a-is-b.nix"; + default = ./define-settingsDict-a-is-b.nix; + } + + { + _file = "nodes-foo.nix"; + nodes.foo.settingsDict.b = "beta"; + } + + { + _file = "the-file-that-contains-the-bad-config.nix"; + default.bottom = "bogus"; + } + + { + _file = "nodes-foo-c-is-a.nix"; + nodes.foo = { config, ... }: { + settingsDict.c = config.settingsDict.a; + }; + } + + ]; +} diff --git a/nixpkgs/lib/tests/modules/define-settingsDict-a-is-b.nix b/nixpkgs/lib/tests/modules/define-settingsDict-a-is-b.nix new file mode 100644 index 000000000000..42363f45f78d --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-settingsDict-a-is-b.nix @@ -0,0 +1,3 @@ +{ config, ... }: { + settingsDict.a = config.settingsDict.b; +} diff --git a/nixpkgs/lib/tests/modules/extendModules-168767-imports.nix b/nixpkgs/lib/tests/modules/extendModules-168767-imports.nix new file mode 100644 index 000000000000..489e6b5a5d83 --- /dev/null +++ b/nixpkgs/lib/tests/modules/extendModules-168767-imports.nix @@ -0,0 +1,41 @@ +{ lib +, extendModules +, ... +}: +with lib; +{ + imports = [ + + { + options.sub = mkOption { + default = { }; + type = types.submodule ( + { config + , extendModules + , ... + }: + { + options.value = mkOption { + type = types.int; + }; + + options.specialisation = mkOption { + default = { }; + inherit + (extendModules { + modules = [{ + specialisation = mkOverride 0 { }; + }]; + }) + type; + }; + } + ); + }; + } + + { config.sub.value = 1; } + + + ]; +} diff --git a/nixpkgs/lib/tests/modules/functionTo/submodule-options.nix b/nixpkgs/lib/tests/modules/functionTo/submodule-options.nix new file mode 100644 index 000000000000..b884892efd6a --- /dev/null +++ b/nixpkgs/lib/tests/modules/functionTo/submodule-options.nix @@ -0,0 +1,61 @@ +{ lib, config, options, ... }: +let + inherit (lib) types; +in +{ + imports = [ + + # fun.<function-body>.a + ({ ... }: { + options = { + fun = lib.mkOption { + type = types.functionTo (types.submodule { + options.a = lib.mkOption { default = "a"; }; + }); + }; + }; + }) + + # fun.<function-body>.b + ({ ... }: { + options = { + fun = lib.mkOption { + type = types.functionTo (types.submodule { + options.b = lib.mkOption { default = "b"; }; + }); + }; + }; + }) + ]; + + options = { + result = lib.mkOption + { + type = types.str; + default = lib.concatStringsSep " " (lib.attrValues (config.fun (throw "shouldn't use input param"))); + }; + + optionsResult = lib.mkOption + { + type = types.str; + default = lib.concatStringsSep " " + (lib.concatLists + (lib.mapAttrsToList + (k: v: + if k == "_module" + then [ ] + else [ (lib.showOption v.loc) ] + ) + ( + (options.fun.type.getSubOptions [ "fun" ]) + ) + ) + ); + }; + }; + + config.fun = lib.mkMerge + [ + (input: { b = "bee"; }) + ]; +} diff --git a/nixpkgs/lib/tests/modules/submoduleFiles.nix b/nixpkgs/lib/tests/modules/submoduleFiles.nix new file mode 100644 index 000000000000..c0d9b2cef3e8 --- /dev/null +++ b/nixpkgs/lib/tests/modules/submoduleFiles.nix @@ -0,0 +1,21 @@ +{ lib, ... }: { + options.submodule = lib.mkOption { + default = {}; + type = lib.types.submoduleWith { + modules = [ ({ options, ... }: { + options.value = lib.mkOption {}; + + options.internalFiles = lib.mkOption { + default = options.value.files; + }; + })]; + }; + }; + + imports = [ + { + _file = "the-file.nix"; + submodule.value = 10; + } + ]; +} diff --git a/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix index 815841e0a8f3..b93a4236f91e 100644 --- a/nixpkgs/lib/tests/release.nix +++ b/nixpkgs/lib/tests/release.nix @@ -11,6 +11,10 @@ pkgs.runCommand "nixpkgs-lib-tests" { inherit pkgs; lib = import ../.; }) + (import ./teams.nix { + inherit pkgs; + lib = import ../.; + }) ]; } '' datadir="${pkgs.nix}/share" diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix index c88adbf4651a..46e7bd992f1e 100644 --- a/nixpkgs/lib/tests/systems.nix +++ b/nixpkgs/lib/tests/systems.nix @@ -19,6 +19,9 @@ with lib.systems.doubles; lib.runTests { testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "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" ]; + 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" ]; 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" ]; diff --git a/nixpkgs/lib/tests/teams.nix b/nixpkgs/lib/tests/teams.nix new file mode 100644 index 000000000000..8a0a5d272634 --- /dev/null +++ b/nixpkgs/lib/tests/teams.nix @@ -0,0 +1,50 @@ +# to run these tests: +# nix-build nixpkgs/lib/tests/teams.nix +# If it builds, all tests passed +{ pkgs ? import ../.. {}, lib ? pkgs.lib }: + +let + inherit (lib) types; + + teamModule = { config, ... }: { + options = { + shortName = lib.mkOption { + type = types.str; + }; + scope = lib.mkOption { + type = types.str; + }; + enableFeatureFreezePing = lib.mkOption { + type = types.bool; + default = false; + }; + members = lib.mkOption { + type = types.listOf (types.submodule + (import ./maintainer-module.nix { inherit lib; }) + ); + default = []; + }; + githubTeams = lib.mkOption { + type = types.listOf types.str; + default = []; + }; + }; + }; + + checkTeam = team: uncheckedAttrs: + let + prefix = [ "lib" "maintainer-team" team ]; + checkedAttrs = (lib.modules.evalModules { + inherit prefix; + modules = [ + teamModule + { + _file = toString ../../maintainers/team-list.nix; + config = uncheckedAttrs; + } + ]; + }).config; + in checkedAttrs; + + checkedTeams = lib.mapAttrs checkTeam lib.teams; +in pkgs.writeTextDir "maintainer-teams.json" (builtins.toJSON checkedTeams) diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix index 18616a189c26..5d4fad8266bc 100644 --- a/nixpkgs/lib/trivial.nix +++ b/nixpkgs/lib/trivial.nix @@ -179,7 +179,7 @@ rec { they take effect as soon as the oldest release reaches end of life. */ oldestSupportedRelease = # Update on master only. Do not backport. - 2111; + 2205; /* Whether a feature is supported in all supported releases (at the time of release branch-off, if applicable). See `oldestSupportedRelease`. */ @@ -195,7 +195,7 @@ rec { On each release the first letter is bumped and a new animal is chosen starting with that new letter. */ - codeName = "Quokka"; + codeName = "Raccoon"; /* Returns the current nixpkgs version suffix as string. */ versionSuffix = @@ -229,6 +229,13 @@ rec { */ inNixShell = builtins.getEnv "IN_NIX_SHELL" != ""; + /* Determine whether the function is being called from inside pure-eval mode + by seeing whether `builtins` contains `currentSystem`. If not, we must be in + pure-eval mode. + + Type: inPureEvalMode :: bool + */ + inPureEvalMode = ! builtins ? currentSystem; ## Integer operations diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix index 5c4b96310617..d7655bc1a6a2 100644 --- a/nixpkgs/lib/types.nix +++ b/nixpkgs/lib/types.nix @@ -55,6 +55,7 @@ let concatMapStringsSep concatStringsSep escapeNixString + hasInfix isCoercibleToString ; inherit (lib.trivial) @@ -360,6 +361,11 @@ rec { deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types."; }; + passwdEntry = entryType: addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str)) // { + name = "passwdEntry ${entryType.name}"; + description = "${entryType.description}, not containing newlines or colons"; + }; + attrs = mkOptionType { name = "attrs"; description = "attribute set"; @@ -397,7 +403,7 @@ rec { listOf = elemType: mkOptionType rec { name = "listOf"; - description = "list of ${elemType.description}s"; + description = "list of ${elemType.description}"; check = isList; merge = loc: defs: map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: @@ -426,7 +432,7 @@ rec { attrsOf = elemType: mkOptionType rec { name = "attrsOf"; - description = "attribute set of ${elemType.description}s"; + description = "attribute set of ${elemType.description}"; check = isAttrs; merge = loc: defs: mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: @@ -449,7 +455,7 @@ rec { # error that it's not defined. Use only if conditional definitions don't make sense. lazyAttrsOf = elemType: mkOptionType rec { name = "lazyAttrsOf"; - description = "lazy attribute set of ${elemType.description}s"; + description = "lazy attribute set of ${elemType.description}"; check = isAttrs; merge = loc: defs: zipAttrsWith (name: defs: @@ -526,9 +532,11 @@ rec { check = isFunction; merge = loc: defs: fnArgs: (mergeDefinitions (loc ++ [ "[function body]" ]) elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs)).mergedValue; - getSubOptions = elemType.getSubOptions; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "[function body]" ]); getSubModules = elemType.getSubModules; substSubModules = m: functionTo (elemType.substSubModules m); + functor = (defaultFunctor "functionTo") // { wrapped = elemType; }; + nestedTypes.elemType = elemType; }; # A submodule (like typed attribute set). See NixOS manual. @@ -537,6 +545,36 @@ rec { modules = toList modules; }; + # 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"; + 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"; @@ -568,23 +606,15 @@ rec { { modules , specialArgs ? {} , shorthandOnlyDefinesConfig ? false + , description ? null }@attrs: let inherit (lib.modules) evalModules; - shorthandToModule = if shorthandOnlyDefinesConfig == false - then value: value - else value: { config = value; }; - - allModules = defs: imap1 (n: { value, file }: - if isFunction value - then setFunctionArgs - (args: lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (value args)) - (functionArgs value) - else if isAttrs value - then - lib.modules.unifyModuleSyntax file "${toString file}-${toString n}" (shorthandToModule value) - else value + allModules = defs: map ({ value, file }: + if isAttrs value && shorthandOnlyDefinesConfig + then { _file = file; config = value; } + else { _file = file; imports = [ value ]; } ) defs; base = evalModules { @@ -611,10 +641,14 @@ rec { freeformType = base._module.freeformType; - in - mkOptionType rec { name = "submodule"; - description = freeformType.description or name; + + 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 { @@ -639,9 +673,7 @@ rec { functor = defaultFunctor name // { type = types.submoduleWith; payload = { - modules = modules; - specialArgs = specialArgs; - shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig; + inherit modules specialArgs shorthandOnlyDefinesConfig description; }; binOp = lhs: rhs: { modules = lhs.modules ++ rhs.modules; @@ -658,6 +690,14 @@ rec { 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"; }; }; }; diff --git a/nixpkgs/lib/zip-int-bits.nix b/nixpkgs/lib/zip-int-bits.nix index edbcdfe1e682..53efd2bb0a04 100644 --- a/nixpkgs/lib/zip-int-bits.nix +++ b/nixpkgs/lib/zip-int-bits.nix @@ -1,5 +1,5 @@ /* Helper function to implement a fallback for the bit operators - `bitAnd`, `bitOr` and `bitXOr` on older nix version. + `bitAnd`, `bitOr` and `bitXor` on older nix version. See ./trivial.nix */ f: x: y: |