diff options
author | Alyssa Ross <hi@alyssa.is> | 2021-01-10 07:13:44 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2021-01-12 14:07:16 +0000 |
commit | e2698550456abba83c6dcd5d5e5a9990a0b96f8a (patch) | |
tree | 79a56f0df3fa55e470d84b4dff6059fbf487ec18 /nixpkgs/lib | |
parent | 1cdc42df888dc98c347e03bd942ed9825a55bcb3 (diff) | |
parent | 84d74ae9c9cbed73274b8e4e00be14688ffc93fe (diff) | |
download | nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.gz nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.bz2 nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.lz nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.xz nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.tar.zst nixlib-e2698550456abba83c6dcd5d5e5a9990a0b96f8a.zip |
Merge commit '84d74ae9c9cbed73274b8e4e00be14688ffc93fe'
Diffstat (limited to 'nixpkgs/lib')
31 files changed, 837 insertions, 304 deletions
diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix index 82bea7af31fc..d91d7a0cd47e 100644 --- a/nixpkgs/lib/attrsets.nix +++ b/nixpkgs/lib/attrsets.nix @@ -469,6 +469,7 @@ rec { getBin = getOutput "bin"; getLib = getOutput "lib"; getDev = getOutput "dev"; + getMan = getOutput "man"; /* Pick the outputs of packages to place in buildInputs */ chooseDevOutputs = drvs: builtins.map getDev drvs; diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix index e7f59a67abbd..43b9ab5930c4 100644 --- a/nixpkgs/lib/default.nix +++ b/nixpkgs/lib/default.nix @@ -67,7 +67,7 @@ let inherit (trivial) id const pipe concat or and bitAnd bitOr bitXor bitNot boolToString mergeAttrs flip mapNullable inNixShell min max importJSON warn info showWarnings nixpkgsVersion version mod compare - splitByAndCompare functionArgs setFunctionArgs isFunction; + splitByAndCompare functionArgs setFunctionArgs isFunction toHexString toBaseDigits; inherit (fixedPoints) fix fix' converge extends composeExtensions makeExtensible makeExtensibleWithCustomName; inherit (attrsets) attrByPath hasAttrByPath setAttrByPath @@ -77,7 +77,7 @@ let genAttrs isDerivation toDerivation optionalAttrs zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil recursiveUpdate matchAttrs overrideExisting getOutput getBin - getLib getDev chooseDevOutputs zipWithNames zip + getLib getDev getMan chooseDevOutputs zipWithNames zip recurseIntoAttrs dontRecurseIntoAttrs; inherit (lists) singleton forEach foldr fold foldl foldl' imap0 imap1 concatMap flatten remove findSingle findFirst any all count diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix index efe6ea6031d3..501a23599f45 100644 --- a/nixpkgs/lib/generators.nix +++ b/nixpkgs/lib/generators.nix @@ -48,8 +48,10 @@ rec { else if isAttrs v then err "attrsets" v # functions can’t be printed of course else if isFunction v then err "functions" v - # let’s not talk about floats. There is no sensible `toString` for them. - else if isFloat v then err "floats" v + # Floats currently can't be converted to precise strings, + # condition warning on nix version once this isn't a problem anymore + # See https://github.com/NixOS/nix/pull/3480 + else if isFloat v then libStr.floatToString v else err "this value is" (toString v); @@ -201,40 +203,59 @@ rec { /* If this option is true, attrsets like { __pretty = fn; val = …; } will use fn to convert val to a pretty printed representation. (This means fn is type Val -> String.) */ - allowPrettyValues ? false - }@args: v: with builtins; + allowPrettyValues ? false, + /* If this option is true, the output is indented with newlines for attribute sets and lists */ + multiline ? true + }@args: let + go = indent: v: with builtins; let isPath = v: typeOf v == "path"; + introSpace = if multiline then "\n${indent} " else " "; + outroSpace = if multiline then "\n${indent}" else " "; in if isInt v then toString v else if isFloat v then "~${toString v}" - else if isString v then ''"${libStr.escape [''"''] v}"'' + else if isString v then + let + # Separate a string into its lines + newlineSplits = filter (v: ! isList v) (builtins.split "\n" v); + # For a '' string terminated by a \n, which happens when the closing '' is on a new line + multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''"; + # For a '' string not terminated by a \n, which happens when the closing '' is not on a new line + multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''"; + # For single lines, replace all newlines with their escaped representation + singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\""; + in if multiline && length newlineSplits > 1 then + if lib.last newlineSplits == "" then multilineResult else multilineResult' + else singlelineResult else if true == v then "true" else if false == v then "false" else if null == v then "null" else if isPath v then toString v - else if isList v then "[ " - + libStr.concatMapStringsSep " " (toPretty args) v - + " ]" + else if isList v then + if v == [] then "[ ]" + else "[" + introSpace + + libStr.concatMapStringsSep introSpace (go (indent + " ")) v + + outroSpace + "]" + else if isFunction v then + let fna = lib.functionArgs v; + showFnas = concatStringsSep ", " (libAttr.mapAttrsToList + (name: hasDefVal: if hasDefVal then name + "?" else name) + fna); + in if fna == {} then "<function>" + else "<function, args: {${showFnas}}>" else if isAttrs v then # apply pretty values if allowed if attrNames v == [ "__pretty" "val" ] && allowPrettyValues then v.__pretty v.val - # TODO: there is probably a better representation? + else if v == {} then "{ }" else if v ? type && v.type == "derivation" then - "<δ:${v.name}>" - # "<δ:${concatStringsSep "," (builtins.attrNames v)}>" - else "{ " - + libStr.concatStringsSep " " (libAttr.mapAttrsToList + "<derivation ${v.drvPath}>" + else "{" + introSpace + + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList (name: value: - "${toPretty args name} = ${toPretty args value};") v) - + " }" - else if isFunction v then - let fna = lib.functionArgs v; - showFnas = concatStringsSep "," (libAttr.mapAttrsToList - (name: hasDefVal: if hasDefVal then "(${name})" else name) - fna); - in if fna == {} then "<λ>" - else "<λ:{${showFnas}}>" + "${libStr.escapeNixIdentifier name} = ${go (indent + " ") value};") v) + + outroSpace + "}" else abort "generators.toPretty: should never happen (v = ${v})"; + in go ""; # PLIST handling toPlist = {}: v: let diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix index b799a6ae8a43..a704a6884c7d 100644 --- a/nixpkgs/lib/licenses.nix +++ b/nixpkgs/lib/licenses.nix @@ -28,7 +28,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "Academic Free License v3.0"; }; - agpl3 = spdx { + agpl3Only = spdx { spdxId = "AGPL-3.0-only"; fullName = "GNU Affero General Public License v3.0 only"; }; @@ -85,6 +85,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = ''Beerware License''; }; + blueOak100 = spdx { + spdxId = "BlueOak-1.0.0"; + fullName = "Blue Oak Model License 1.0.0"; + }; + bsd0 = spdx { spdxId = "0BSD"; fullName = "BSD Zero Clause License"; @@ -95,6 +100,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = ''BSD 2-clause "Simplified" License''; }; + bsd2Patent = spdx { + spdxId = "BSD-2-Clause-Patent"; + fullName = ''BSD-2-Clause Plus Patent License''; + }; + bsd3 = spdx { spdxId = "BSD-3-Clause"; fullName = ''BSD 3-clause "New" or "Revised" License''; @@ -105,6 +115,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = ''BSD 4-clause "Original" or "Old" License''; }; + bsdProtection = spdx { + spdxId = "BSD-Protection"; + fullName = "BSD Protection License"; + }; + bsl11 = { fullName = "Business Source License 1.1"; url = "https://mariadb.com/bsl11"; @@ -276,12 +291,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "European Union Public License 1.2"; }; - fdl11 = spdx { + fdl11Only = spdx { spdxId = "GFDL-1.1-only"; fullName = "GNU Free Documentation License v1.1 only"; }; - fdl12 = spdx { + fdl12Only = spdx { spdxId = "GFDL-1.2-only"; fullName = "GNU Free Documentation License v1.2 only"; }; @@ -291,7 +306,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU Free Documentation License v1.2 or later"; }; - fdl13 = spdx { + fdl13Only = spdx { spdxId = "GFDL-1.3-only"; fullName = "GNU Free Documentation License v1.3 only"; }; @@ -322,7 +337,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { free = false; }; - gpl1 = spdx { + gpl1Only = spdx { spdxId = "GPL-1.0-only"; fullName = "GNU General Public License v1.0 only"; }; @@ -332,7 +347,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU General Public License v1.0 or later"; }; - gpl2 = spdx { + gpl2Only = spdx { spdxId = "GPL-2.0-only"; fullName = "GNU General Public License v2.0 only"; }; @@ -357,7 +372,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU General Public License v2.0 or later"; }; - gpl3 = spdx { + gpl3Only = spdx { spdxId = "GPL-3.0-only"; fullName = "GNU General Public License v3.0 only"; }; @@ -421,18 +436,19 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { }; # Proprietary binaries; free to redistribute without modification. + databricks = { + fullName = "Databricks Proprietary License"; + url = "https://pypi.org/project/databricks-connect"; + free = false; + }; + issl = { fullName = "Intel Simplified Software License"; url = "https://software.intel.com/en-us/license/intel-simplified-software-license"; free = false; }; - jasper = spdx { - spdxId = "JasPer-2.0"; - fullName = "JasPer License"; - }; - - lgpl2 = spdx { + lgpl2Only = spdx { spdxId = "LGPL-2.0-only"; fullName = "GNU Library General Public License v2 only"; }; @@ -442,7 +458,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU Library General Public License v2 or later"; }; - lgpl21 = spdx { + lgpl21Only = spdx { spdxId = "LGPL-2.1-only"; fullName = "GNU Lesser General Public License v2.1 only"; }; @@ -452,7 +468,7 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU Lesser General Public License v2.1 or later"; }; - lgpl3 = spdx { + lgpl3Only = spdx { spdxId = "LGPL-3.0-only"; fullName = "GNU Lesser General Public License v3.0 only"; }; @@ -462,6 +478,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "GNU Lesser General Public License v3.0 or later"; }; + lgpllr = spdx { + spdxId = "LGPLLR"; + fullName = "Lesser General Public License For Linguistic Resources"; + }; + libpng = spdx { spdxId = "Libpng"; fullName = "libpng License"; @@ -482,6 +503,11 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { url = "https://opensource.franz.com/preamble.html"; }; + llvm-exception = spdx { + spdxId = "LLVM-exception"; + fullName = "LLVM Exception"; # LLVM exceptions to the Apache 2.0 License + }; + lppl12 = spdx { spdxId = "LPPL-1.2"; fullName = "LaTeX Project Public License v1.2"; @@ -545,6 +571,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { fullName = "Non-Profit Open Software License 3.0"; }; + obsidian = { + fullName = "Obsidian End User Agreement"; + url = "https://obsidian.md/eula"; + free = false; + }; + ocamlpro_nc = { fullName = "OCamlPro Non Commercial license version 1"; url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf"; @@ -612,6 +644,12 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { url = "https://enterprise.dejacode.com/licenses/public/purdue-bsd"; }; + prosperity30 = { + fullName = "Prosperity-3.0.0"; + free = false; + url = "https://prosperitylicense.com/versions/3.0.0.html"; + }; + qhull = spdx { spdxId = "Qhull"; fullName = "Qhull License"; @@ -761,4 +799,16 @@ lib.mapAttrs (n: v: v // { shortName = n; }) { spdxId = "ZPL-2.1"; fullName = "Zope Public License 2.1"; }; +} // { + # TODO: remove legacy aliases + agpl3 = lib.licenses.agpl3Only; + fdl11 = lib.licenses.fdl11Only; + fdl12 = lib.licenses.fdl12Only; + fdl13 = lib.licenses.fdl13Only; + gpl1 = lib.licenses.gpl1Only; + gpl2 = lib.licenses.gpl2Only; + gpl3 = lib.licenses.gpl3Only; + lgpl2 = lib.licenses.lgpl2Only; + lgpl21 = lib.licenses.lgpl21Only; + lgpl3 = lib.licenses.lgpl3Only; } diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix index c18fec66c705..02a669df6593 100644 --- a/nixpkgs/lib/modules.nix +++ b/nixpkgs/lib/modules.nix @@ -58,6 +58,23 @@ rec { default = check; description = "Whether to check whether all option definitions have matching declarations."; }; + + _module.freeformType = mkOption { + # Disallow merging for now, but could be implemented nicely with a `types.optionType` + type = types.nullOr (types.uniq types.attrs); + internal = true; + default = null; + description = '' + If set, merge all definitions that don't have an associated option + together using this type. The result then gets combined with the + values of all declared options to produce the final <literal> + config</literal> value. + + If this is <literal>null</literal>, definitions without an option + will throw an error unless <option>_module.check</option> is + turned off. + ''; + }; }; config = { @@ -65,35 +82,55 @@ rec { }; }; - collected = collectModules - (specialArgs.modulesPath or "") - (modules ++ [ internalModule ]) - ({ inherit config options lib; } // specialArgs); - - options = mergeModules prefix (reverseList collected); - - # Traverse options and extract the option values into the final - # config set. At the same time, check whether all option - # definitions have matching declarations. - # !!! _module.check's value can't depend on any other config values - # without an infinite recursion. One way around this is to make the - # 'config' passed around to the modules be unconditionally unchecked, - # and only do the check in 'result'. - config = yieldConfig prefix options; - yieldConfig = prefix: set: - let res = removeAttrs (mapAttrs (n: v: - if isOption v then v.value - else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"]; - in - if options._module.check.value && set ? _definedNames then - foldl' (res: m: - foldl' (res: name: - if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.") - res m.names) - res set._definedNames - else - res; - result = { + merged = + let collected = collectModules + (specialArgs.modulesPath or "") + (modules ++ [ internalModule ]) + ({ inherit lib options config; } // specialArgs); + in mergeModules prefix (reverseList collected); + + options = merged.matchedOptions; + + config = + let + + # For definitions that have an associated option + declaredConfig = mapAttrsRecursiveCond (v: ! isOption v) (_: v: v.value) options; + + # If freeformType is set, this is for definitions that don't have an associated option + freeformConfig = + let + defs = map (def: { + file = def.file; + value = setAttrByPath def.prefix def.value; + }) merged.unmatchedDefns; + in if defs == [] then {} + else declaredConfig._module.freeformType.merge prefix defs; + + in if declaredConfig._module.freeformType == null then declaredConfig + # Because all definitions that had an associated option ended in + # declaredConfig, freeformConfig can only contain the non-option + # paths, meaning recursiveUpdate will never override any value + else recursiveUpdate freeformConfig declaredConfig; + + checkUnmatched = + if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [] then + let + firstDef = head merged.unmatchedDefns; + baseMsg = "The option `${showOption (prefix ++ firstDef.prefix)}' does not exist. Definition values:${showDefs [ firstDef ]}"; + in + if attrNames options == [ "_module" ] + then throw '' + ${baseMsg} + + However there are no options defined in `${showOption prefix}'. Are you sure you've + declared your options properly? This can happen if you e.g. declared your options in `types.submodule' + under `config' rather than `options'. + '' + else throw baseMsg + else null; + + result = builtins.seq checkUnmatched { inherit options; config = removeAttrs config [ "_module" ]; inherit (config) _module; @@ -174,12 +211,16 @@ rec { /* Massage a module into canonical form, that is, a set consisting of ‘options’, ‘config’ and ‘imports’ attributes. */ unifyModuleSyntax = file: key: m: - let addMeta = config: if m ? meta - then mkMerge [ config { meta = m.meta; } ] - else config; + let + addMeta = config: if m ? meta + then mkMerge [ config { meta = m.meta; } ] + else config; + addFreeformType = config: if m ? freeformType + then mkMerge [ config { _module.freeformType = m.freeformType; } ] + else config; in if m ? config || m ? options then - let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in + let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta" "freeformType"]; in if badAttrs != {} then throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by introducing a top-level `config' or `options' attribute. Add configuration attributes immediately on the top level instead, or move all of them (namely: ${toString (attrNames badAttrs)}) into the explicit `config' attribute." else @@ -188,7 +229,7 @@ rec { disabledModules = m.disabledModules or []; imports = m.imports or []; options = m.options or {}; - config = addMeta (m.config or {}); + config = addFreeformType (addMeta (m.config or {})); } else { _file = m._file or file; @@ -196,7 +237,7 @@ rec { disabledModules = m.disabledModules or []; imports = m.require or [] ++ m.imports or []; options = {}; - config = addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]); + config = addFreeformType (addMeta (removeAttrs m ["_file" "key" "disabledModules" "require" "imports" "freeformType"])); }; applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then @@ -233,7 +274,23 @@ rec { declarations in all modules, combining them into a single set. At the same time, for each option declaration, it will merge the corresponding option definitions in all machines, returning them - in the ‘value’ attribute of each option. */ + in the ‘value’ attribute of each option. + + This returns a set like + { + # A recursive set of options along with their final values + matchedOptions = { + foo = { _type = "option"; value = "option value of foo"; ... }; + bar.baz = { _type = "option"; value = "option value of bar.baz"; ... }; + ... + }; + # A list of definitions that weren't matched by any option + unmatchedDefns = [ + { file = "file.nix"; prefix = [ "qux" ]; value = "qux"; } + ... + ]; + } + */ mergeModules = prefix: modules: mergeModules' prefix modules (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules); @@ -280,9 +337,9 @@ rec { defnsByName' = byName "config" (module: value: [{ inherit (module) file; inherit value; }] ) configs; - in - (flip mapAttrs declsByName (name: decls: - # We're descending into attribute ‘name’. + + resultsByName = flip mapAttrs declsByName (name: decls: + # We're descending into attribute ‘name’. let loc = prefix ++ [name]; defns = defnsByName.${name} or []; @@ -291,7 +348,10 @@ rec { in if nrOptions == length decls then let opt = fixupOptionType loc (mergeOptionDecls loc decls); - in evalOptionValue loc opt defns' + in { + matchedOptions = evalOptionValue loc opt defns'; + unmatchedDefns = []; + } else if nrOptions != 0 then let firstOption = findFirst (m: isOption m.options) "" decls; @@ -299,9 +359,27 @@ rec { in throw "The option `${showOption loc}' in `${firstOption._file}' is a prefix of options in `${firstNonOption._file}'." else - mergeModules' loc decls defns - )) - // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; + mergeModules' loc decls defns); + + matchedOptions = mapAttrs (n: v: v.matchedOptions) resultsByName; + + # an attrset 'name' => list of unmatched definitions for 'name' + unmatchedDefnsByName = + # Propagate all unmatched definitions from nested option sets + mapAttrs (n: v: v.unmatchedDefns) resultsByName + # Plus the definitions for the current prefix that don't have a matching option + // removeAttrs defnsByName' (attrNames matchedOptions); + in { + inherit matchedOptions; + + # Transforms unmatchedDefnsByName into a list of definitions + unmatchedDefns = concatLists (mapAttrsToList (name: defs: + map (def: def // { + # Set this so we know when the definition first left unmatched territory + prefix = [name] ++ (def.prefix or []); + }) defs + ) unmatchedDefnsByName); + }; /* Merge multiple option declarations into a single declaration. In general, there should be only one declaration of each option. @@ -371,7 +449,13 @@ rec { # Handle properties, check types, and merge everything together. res = if opt.readOnly or false && length defs' > 1 then - throw "The option `${showOption loc}' is read-only, but it's set multiple times." + let + # For a better error message, evaluate all readOnly definitions as + # if they were the only definition. + separateDefs = map (def: def // { + value = (mergeDefinitions loc opt.type [ def ]).mergedValue; + }) defs'; + in throw "The option `${showOption loc}' is read-only, but it's set multiple times. Definition values:${showDefs separateDefs}" else mergeDefinitions loc opt.type defs'; @@ -379,7 +463,11 @@ rec { # yield a value computed from the definitions value = if opt ? apply then opt.apply res.mergedValue else res.mergedValue; - in opt // + warnDeprecation = + if opt.type.deprecationMessage == null then id + else warn "The type `types.${opt.type.name}' of option `${showOption loc}' defined in ${showFiles opt.declarations} is deprecated. ${opt.type.deprecationMessage}"; + + in warnDeprecation opt // { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; inherit (res.defsFinal') highestPrio; definitions = map (def: def.value) res.defsFinal; @@ -415,8 +503,8 @@ rec { mergedValue = if isDefined then if all (def: type.check def.value) defsFinal then type.merge loc defsFinal - else let firstInvalid = findFirst (def: ! type.check def.value) null defsFinal; - in throw "The option value `${showOption loc}' in `${firstInvalid.file}' is not of type `${type.description}'." + else let allInvalid = filter (def: ! type.check def.value) defsFinal; + in throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}" else # (nixos-option detects this specific error message and gives it special # handling. If changed here, please change it there too.) @@ -535,7 +623,6 @@ rec { if tp.name == "option set" || tp.name == "submodule" then throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options) - else if optionSetIn "loaOf" then types.loaOf (types.submodule options) else if optionSetIn "listOf" then types.listOf (types.submodule options) else if optionSetIn "nullOr" then types.nullOr (types.submodule options) else tp; diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix index 38f4f1329f21..5b7482c80937 100644 --- a/nixpkgs/lib/options.nix +++ b/nixpkgs/lib/options.nix @@ -96,22 +96,26 @@ rec { else if all isBool list then foldl' lib.or false list else if all isString list then lib.concatStrings list else if all isInt list && all (x: x == head list) list then head list - else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}."; + else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; mergeOneOption = loc: defs: if defs == [] then abort "This case should never happen." else if length defs != 1 then - throw "The unique option `${showOption loc}' is defined multiple times, in:\n - ${concatStringsSep "\n - " (getFiles defs)}." + throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}" else (head defs).value; /* "Merge" option definitions by checking that they all have the same value. */ mergeEqualOption = loc: defs: if defs == [] then abort "This case should never happen." - else foldl' (val: def: - if def.value != val then - throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." + # Return early if we only have one element + # This also makes it work for functions, because the foldl' below would try + # to compare the first element with itself, which is false for functions + else if length defs == 1 then (elemAt defs 0).value + else (foldl' (first: def: + if def.value != first.value then + throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}" else - val) (head defs).value defs; + first) (head defs) defs).value; /* Extracts values of all "value" keys of the given list. @@ -209,6 +213,24 @@ rec { else escaped; in (concatStringsSep ".") (map escapeOptionPart parts); showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); + + showDefs = defs: concatMapStrings (def: + let + # Pretty print the value for display, if successful + prettyEval = builtins.tryEval (lib.generators.toPretty {} def.value); + # Split it into its lines + lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value); + # Only display the first 5 lines, and indent them for better visibility + value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "..."); + result = + # Don't print any value if evaluating the value strictly fails + if ! prettyEval.success then "" + # Put it on a new line if it consists of multiple + else if length lines > 1 then ":\n " + value + else ": " + value; + in "\n- In `${def.file}'${result}" + ) defs; + unknownModule = "<unknown-file>"; } diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix index ed9bce485300..776fcc32052b 100644 --- a/nixpkgs/lib/sources.nix +++ b/nixpkgs/lib/sources.nix @@ -145,10 +145,14 @@ rec { # packed-refs file, so we have to grep through it: then let fileContent = readFile packedRefsName; - matchRef = match (".*\n([^\n ]*) " + file + "\n.*") fileContent; - in if matchRef == null + matchRef = builtins.match "([a-z0-9]+) ${file}"; + isRef = s: builtins.isString s && (matchRef s) != null; + # there is a bug in libstdc++ leading to stackoverflow for long strings: + # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795 + refs = builtins.filter isRef (builtins.split "\n" fileContent); + in if refs == [] then throw ("Could not find " + file + " in " + packedRefsName) - else lib.head matchRef + else lib.head (matchRef (lib.head refs)) else throw ("Not a .git directory: " + path); in readCommitFromFile "HEAD"; diff --git a/nixpkgs/lib/strings.nix b/nixpkgs/lib/strings.nix index 74e3eaa0722d..9fa9f023561e 100644 --- a/nixpkgs/lib/strings.nix +++ b/nixpkgs/lib/strings.nix @@ -612,6 +612,22 @@ rec { */ fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); + /* Convert a float to a string, but emit a warning when precision is lost + during the conversion + + Example: + floatToString 0.000001 + => "0.000001" + floatToString 0.0000001 + => trace: warning: Imprecise conversion from float to string 0.000000 + "0.000000" + */ + floatToString = float: let + result = toString float; + precise = float == builtins.fromJSON result; + in if precise then result + else lib.warn "Imprecise conversion from float to string ${result}" result; + /* Check whether a value can be coerced to a string */ isCoercibleToString = x: builtins.elem (builtins.typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || @@ -673,14 +689,15 @@ rec { "/prefix/nix-profiles-library-paths.patch" "/prefix/compose-search-path.patch" ] */ - readPathsFromFile = rootPath: file: - let - lines = lib.splitString "\n" (builtins.readFile file); - removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); - relativePaths = removeComments lines; - absolutePaths = builtins.map (path: rootPath + "/${path}") relativePaths; - in - absolutePaths; + readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead" + (rootPath: file: + let + lines = lib.splitString "\n" (builtins.readFile file); + removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); + relativePaths = removeComments lines; + absolutePaths = builtins.map (path: rootPath + "/${path}") relativePaths; + in + absolutePaths); /* Read the contents of a file removing the trailing \n diff --git a/nixpkgs/lib/systems/architectures.nix b/nixpkgs/lib/systems/architectures.nix index 287f5be03c45..bfecaec1ae88 100644 --- a/nixpkgs/lib/systems/architectures.nix +++ b/nixpkgs/lib/systems/architectures.nix @@ -35,21 +35,21 @@ rec { # x86_64 Intel default = [ ]; westmere = [ ]; - sandybridge = [ "westmere" ]; - ivybridge = [ "westmere" "sandybridge" ]; - haswell = [ "westmere" "sandybridge" "ivybridge" ]; - broadwell = [ "westmere" "sandybridge" "ivybridge" "haswell" ]; - skylake = [ "westmere" "sandybridge" "ivybridge" "haswell" "broadwell" ]; - skylake-avx512 = [ "westmere" "sandybridge" "ivybridge" "haswell" "broadwell" "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; # x86_64 AMD btver1 = [ ]; - btver2 = [ ]; - bdver1 = [ ]; - bdver2 = [ ]; - bdver3 = [ ]; - bdver4 = [ ]; - znver1 = [ ]; - znver2 = [ ]; + btver2 = [ ]; # TODO: fill this (need testing) + bdver1 = [ ]; # TODO: fill this (need testing) + bdver2 = [ ]; # TODO: fill this (need testing) + bdver3 = [ ]; # TODO: fill this (need testing) + bdver4 = [ ]; # TODO: fill this (need testing) + znver1 = [ ]; # TODO: fill this (need testing) + znver2 = [ ]; # TODO: fill this (need testing) # other armv5te = [ ]; armv6 = [ ]; @@ -59,17 +59,19 @@ rec { loongson2f = [ ]; }; - predicates = { - sse3Support = x: builtins.elem "sse3" features.${x}; - ssse3Support = x: builtins.elem "ssse3" features.${x}; - sse4_1Support = x: builtins.elem "sse4_1" features.${x}; - sse4_2Support = x: builtins.elem "sse4_2" features.${x}; - sse4_aSupport = x: builtins.elem "sse4a" features.${x}; - avxSupport = x: builtins.elem "avx" features.${x}; - avx2Support = x: builtins.elem "avx2" features.${x}; - avx512Support = x: builtins.elem "avx512" features.${x}; - aesSupport = x: builtins.elem "aes" features.${x}; - fmaSupport = x: builtins.elem "fma" features.${x}; - fma4Support = x: builtins.elem "fma4" features.${x}; + predicates = let + featureSupport = feature: x: builtins.elem feature features.${x} or []; + in { + sse3Support = featureSupport "sse3"; + ssse3Support = featureSupport "ssse3"; + sse4_1Support = featureSupport "sse4_1"; + sse4_2Support = featureSupport "sse4_2"; + sse4_aSupport = featureSupport "sse4a"; + avxSupport = featureSupport "avx"; + avx2Support = featureSupport "avx2"; + avx512Support = featureSupport "avx512"; + aesSupport = featureSupport "aes"; + fmaSupport = featureSupport "fma"; + fma4Support = featureSupport "fma4"; }; } diff --git a/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix index 02d58592b325..9939743157e7 100644 --- a/nixpkgs/lib/systems/default.nix +++ b/nixpkgs/lib/systems/default.nix @@ -77,6 +77,7 @@ rec { # uname -r release = null; }; + isStatic = final.isWasm || final.isRedox; kernelArch = if final.isAarch32 then "arm" diff --git a/nixpkgs/lib/systems/doubles.nix b/nixpkgs/lib/systems/doubles.nix index c0e78595d85d..517a7296afd2 100644 --- a/nixpkgs/lib/systems/doubles.nix +++ b/nixpkgs/lib/systems/doubles.nix @@ -38,7 +38,7 @@ let "js-ghcjs" - "aarch64-genode" "x86_64-genode" + "aarch64-genode" "i686-genode" "x86_64-genode" ]; allParsed = map parse.mkSystemFromString all; @@ -50,32 +50,35 @@ in { none = []; - arm = filterDoubles predicates.isAarch32; - aarch64 = filterDoubles predicates.isAarch64; - x86 = filterDoubles predicates.isx86; - i686 = filterDoubles predicates.isi686; - x86_64 = filterDoubles predicates.isx86_64; - mips = filterDoubles predicates.isMips; - riscv = filterDoubles predicates.isRiscV; - vc4 = filterDoubles predicates.isVc4; - js = filterDoubles predicates.isJavaScript; - - cygwin = filterDoubles predicates.isCygwin; - darwin = filterDoubles predicates.isDarwin; - freebsd = filterDoubles predicates.isFreeBSD; + arm = filterDoubles predicates.isAarch32; + aarch64 = filterDoubles predicates.isAarch64; + x86 = filterDoubles predicates.isx86; + i686 = filterDoubles predicates.isi686; + x86_64 = filterDoubles predicates.isx86_64; + mips = filterDoubles predicates.isMips; + riscv = filterDoubles predicates.isRiscV; + vc4 = filterDoubles predicates.isVc4; + js = filterDoubles predicates.isJavaScript; + + bigEndian = filterDoubles predicates.isBigEndian; + littleEndian = filterDoubles predicates.isLittleEndian; + + cygwin = filterDoubles predicates.isCygwin; + darwin = filterDoubles predicates.isDarwin; + freebsd = filterDoubles predicates.isFreeBSD; # Should be better, but MinGW is unclear. - gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; }); - illumos = filterDoubles predicates.isSunOS; - linux = filterDoubles predicates.isLinux; - netbsd = filterDoubles predicates.isNetBSD; - openbsd = filterDoubles predicates.isOpenBSD; - unix = filterDoubles predicates.isUnix; - wasi = filterDoubles predicates.isWasi; - redox = filterDoubles predicates.isRedox; - windows = filterDoubles predicates.isWindows; - genode = filterDoubles predicates.isGenode; - - embedded = filterDoubles predicates.isNone; + gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; }); + illumos = filterDoubles predicates.isSunOS; + linux = filterDoubles predicates.isLinux; + netbsd = filterDoubles predicates.isNetBSD; + openbsd = filterDoubles predicates.isOpenBSD; + unix = filterDoubles predicates.isUnix; + wasi = filterDoubles predicates.isWasi; + redox = filterDoubles predicates.isRedox; + windows = filterDoubles predicates.isWindows; + genode = filterDoubles predicates.isGenode; + + embedded = filterDoubles predicates.isNone; mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "armv7a-linux" "aarch64-linux" "powerpc64le-linux"]; } diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix index ca562d2e4565..5403f73405c2 100644 --- a/nixpkgs/lib/systems/examples.nix +++ b/nixpkgs/lib/systems/examples.nix @@ -46,16 +46,16 @@ rec { armv7a-android-prebuilt = { config = "armv7a-unknown-linux-androideabi"; - sdkVer = "24"; - ndkVer = "18b"; + sdkVer = "29"; + ndkVer = "21"; platform = platforms.armv7a-android; useAndroidPrebuilt = true; }; aarch64-android-prebuilt = { config = "aarch64-unknown-linux-android"; - sdkVer = "24"; - ndkVer = "18b"; + sdkVer = "29"; + ndkVer = "21"; platform = platforms.aarch64-multiplatform; useAndroidPrebuilt = true; }; diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix index 36ddd186d7b7..3a6db53c276d 100644 --- a/nixpkgs/lib/tests/misc.nix +++ b/nixpkgs/lib/tests/misc.nix @@ -102,6 +102,16 @@ runTests { expected = 9; }; + testToHexString = { + expr = toHexString 250; + expected = "FA"; + }; + + testToBaseDigits = { + expr = toBaseDigits 2 6; + expected = [ 1 1 0 ]; + }; + # STRINGS testConcatMapStrings = { @@ -435,32 +445,90 @@ runTests { expected = builtins.toJSON val; }; - testToPretty = { - expr = mapAttrs (const (generators.toPretty {})) rec { + testToPretty = + let + deriv = derivation { name = "test"; builder = "/bin/sh"; system = builtins.currentSystem; }; + in { + expr = mapAttrs (const (generators.toPretty { multiline = false; })) rec { int = 42; float = 0.1337; bool = true; + emptystring = ""; string = ''fno"rd''; + newlinestring = "\n"; path = /. + "/foo"; null_ = null; function = x: x; functionArgs = { arg ? 4, foo }: arg; list = [ 3 4 function [ false ] ]; + emptylist = []; attrs = { foo = null; "foo bar" = "baz"; }; - drv = derivation { name = "test"; system = builtins.currentSystem; }; + emptyattrs = {}; + drv = deriv; }; expected = rec { int = "42"; float = "~0.133700"; bool = "true"; + emptystring = ''""''; string = ''"fno\"rd"''; + newlinestring = "\"\\n\""; path = "/foo"; null_ = "null"; - function = "<λ>"; - functionArgs = "<λ:{(arg),foo}>"; + function = "<function>"; + functionArgs = "<function, args: {arg?, foo}>"; list = "[ 3 4 ${function} [ false ] ]"; - attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }"; - drv = "<δ:test>"; + emptylist = "[ ]"; + attrs = "{ foo = null; \"foo bar\" = \"baz\"; }"; + emptyattrs = "{ }"; + drv = "<derivation ${deriv.drvPath}>"; + }; + }; + + testToPrettyMultiline = { + expr = mapAttrs (const (generators.toPretty { })) rec { + list = [ 3 4 [ false ] ]; + attrs = { foo = null; bar.foo = "baz"; }; + newlinestring = "\n"; + multilinestring = '' + hello + there + test + ''; + multilinestring' = '' + hello + there + test''; + }; + expected = rec { + list = '' + [ + 3 + 4 + [ + false + ] + ]''; + attrs = '' + { + bar = { + foo = "baz"; + }; + foo = null; + }''; + newlinestring = "''\n \n''"; + multilinestring = '' + ''' + hello + there + test + '''''; + multilinestring' = '' + ''' + hello + there + test'''''; + }; }; @@ -532,4 +600,30 @@ runTests { name = ""; expected = "unknown"; }; + + testFreeformOptions = { + expr = + let + submodule = { lib, ... }: { + freeformType = lib.types.attrsOf (lib.types.submodule { + options.bar = lib.mkOption {}; + }); + options.bar = lib.mkOption {}; + }; + + module = { lib, ... }: { + options.foo = lib.mkOption { + type = lib.types.submodule submodule; + }; + }; + + options = (evalModules { + modules = [ module ]; + }).options; + + locs = filter (o: ! o.internal) (optionAttrSetToDocList options); + in map (o: o.loc) locs; + expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ]; + }; + } diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh index 6258244457aa..309c5311361c 100755 --- a/nixpkgs/lib/tests/modules.sh +++ b/nixpkgs/lib/tests/modules.sh @@ -49,7 +49,7 @@ checkConfigError() { reportFailure "$@" return 1 else - if echo "$err" | grep --silent "$errorContains" ; then + if echo "$err" | grep -zP --silent "$errorContains" ; then pass=$((pass + 1)) return 0; else @@ -62,17 +62,17 @@ checkConfigError() { # Check boolean option. checkConfigOutput "false" config.enable ./declare-enable.nix -checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix +checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./define-enable.nix # Check integer types. # unsigned checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix -checkConfigError 'The option value .* in .* is not of type.*unsigned integer.*' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix +checkConfigError 'A definition for option .* is not of type.*unsigned integer.*. Definition values:\n- In .*: -23' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix # positive -checkConfigError 'The option value .* in .* is not of type.*positive integer.*' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix +checkConfigError 'A definition for option .* is not of type.*positive integer.*. Definition values:\n- In .*: 0' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix # between checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix -checkConfigError 'The option value .* in .* is not of type.*between.*-21 and 43.*inclusive.*' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix +checkConfigError 'A definition for option .* is not of type.*between.*-21 and 43.*inclusive.*. Definition values:\n- In .*: -23' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix # Check either types # types.either @@ -125,7 +125,7 @@ checkConfigOutput 'true' "$@" ./define-enable.nix ./define-attrsOfSub-foo-enable set -- config.enable ./define-enable.nix ./declare-enable.nix checkConfigOutput "true" "$@" checkConfigOutput "false" "$@" ./disable-define-enable.nix -checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix +checkConfigError "The option .*enable.* does not exist. Definition values:\n- In .*: true" "$@" ./disable-declare-enable.nix checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix @@ -142,17 +142,17 @@ checkConfigError 'infinite recursion encountered' "$@" # Check _module.check. set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-attrsOfSub-foo.nix -checkConfigError 'The option .* defined in .* does not exist.' "$@" +checkConfigError 'The option .* does not exist. Definition values:\n- In .*' "$@" checkConfigOutput "true" "$@" ./define-module-check.nix # Check coerced value. checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix -checkConfigError 'The option value .* in .* is not.*string or signed integer convertible to it' config.value ./declare-coerced-value.nix ./define-value-list.nix +checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix # Check coerced value with unsound coercion checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix -checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix +checkConfigError 'A definition for option .* is not of type .*. Definition values:\n- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix # Check mkAliasOptionModule. @@ -183,7 +183,7 @@ checkConfigOutput "true" config.submodule.enable ./declare-submoduleWith-path.ni checkConfigOutput "true" config.enable ./disable-recursive/main.nix checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-foo.nix} checkConfigOutput "true" config.enable ./disable-recursive/{main.nix,disable-bar.nix} -checkConfigError 'The option .* defined in .* does not exist' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix} +checkConfigError 'The option .* does not exist. Definition values:\n- In .*: true' config.enable ./disable-recursive/{main.nix,disable-foo.nix,disable-bar.nix} # Check that imports can depend on derivations checkConfigOutput "true" config.enable ./import-from-store.nix @@ -207,9 +207,61 @@ checkConfigOutput "empty" config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-c # Even with multiple assignments, a type error should be thrown if any of them aren't valid -checkConfigError 'The option value .* in .* is not of type .*' \ +checkConfigError 'A definition for option .* is not of type .*' \ config.value ./declare-int-unsigned-value.nix ./define-value-list.nix ./define-value-int-positive.nix +## Freeform modules +# Assigning without a declared option should work +checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.nix +# No freeform assigments shouldn't make it error +checkConfigOutput '{ }' config ./freeform-attrsOf.nix +# but only if the type matches +checkConfigError 'A definition for option .* is not of type .*' config.value ./freeform-attrsOf.nix ./define-value-list.nix +# and properties should be applied +checkConfigOutput yes config.value ./freeform-attrsOf.nix ./define-value-string-properties.nix +# Options should still be declarable, and be able to have a type that doesn't match the freeform type +checkConfigOutput false config.enable ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix +checkConfigOutput 24 config.value ./freeform-attrsOf.nix ./define-value-string.nix ./declare-enable.nix +# and this should work too with nested values +checkConfigOutput false config.nest.foo ./freeform-attrsOf.nix ./freeform-nested.nix +checkConfigOutput bar config.nest.bar ./freeform-attrsOf.nix ./freeform-nested.nix +# Check whether a declared option can depend on an freeform-typed one +checkConfigOutput null config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix +checkConfigOutput 24 config.foo ./freeform-attrsOf.nix ./freeform-str-dep-unstr.nix ./define-value-string.nix +# Check whether an freeform-typed value can depend on a declared option, this can only work with lazyAttrsOf +checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.nix ./freeform-unstr-dep-str.nix +checkConfigError 'The option .* is used but not defined' config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix +checkConfigOutput 24 config.foo ./freeform-lazyAttrsOf.nix ./freeform-unstr-dep-str.nix ./define-value-string.nix + +## types.anything +# Check that attribute sets are merged recursively +checkConfigOutput null config.value.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.l2.foo ./types-anything/nested-attrs.nix +checkConfigOutput null config.value.l1.l2.l3.foo ./types-anything/nested-attrs.nix +# Attribute sets that are coercible to strings shouldn't be recursed into +checkConfigOutput foo config.value.outPath ./types-anything/attrs-coercible.nix +# Multiple lists aren't concatenated together +checkConfigError 'The option .* has conflicting definitions' config.value ./types-anything/lists.nix +# Check that all equalizable atoms can be used as long as all definitions are equal +checkConfigOutput 0 config.value.int ./types-anything/equal-atoms.nix +checkConfigOutput false config.value.bool ./types-anything/equal-atoms.nix +checkConfigOutput '""' config.value.string ./types-anything/equal-atoms.nix +checkConfigOutput / config.value.path ./types-anything/equal-atoms.nix +checkConfigOutput null config.value.null ./types-anything/equal-atoms.nix +checkConfigOutput 0.1 config.value.float ./types-anything/equal-atoms.nix +# Functions can't be merged together +checkConfigError "The option .* has conflicting definition values" config.value.multiple-lambdas ./types-anything/functions.nix +checkConfigOutput '<LAMBDA>' config.value.single-lambda ./types-anything/functions.nix +# Check that all mk* modifiers are applied +checkConfigError 'attribute .* not found' config.value.mkiffalse ./types-anything/mk-mods.nix +checkConfigOutput '{ }' config.value.mkiftrue ./types-anything/mk-mods.nix +checkConfigOutput 1 config.value.mkdefault ./types-anything/mk-mods.nix +checkConfigOutput '{ }' config.value.mkmerge ./types-anything/mk-mods.nix +checkConfigOutput true config.value.mkbefore ./types-anything/mk-mods.nix +checkConfigOutput 1 config.value.nested.foo ./types-anything/mk-mods.nix +checkConfigOutput baz config.value.nested.bar.baz ./types-anything/mk-mods.nix + cat <<EOF ====== module tests ====== $pass Pass diff --git a/nixpkgs/lib/tests/modules/define-option-dependently.nix b/nixpkgs/lib/tests/modules/define-option-dependently.nix index 6abce29366ae..ad85f99a919f 100644 --- a/nixpkgs/lib/tests/modules/define-option-dependently.nix +++ b/nixpkgs/lib/tests/modules/define-option-dependently.nix @@ -7,7 +7,7 @@ # Always defined, but the value depends on the presence of an option. config = { value = if options ? enable then 360 else 7; - } + } # Only define if possible. // lib.optionalAttrs (options ? enable) { enable = true; diff --git a/nixpkgs/lib/tests/modules/define-value-string-properties.nix b/nixpkgs/lib/tests/modules/define-value-string-properties.nix new file mode 100644 index 000000000000..972304c01128 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-string-properties.nix @@ -0,0 +1,12 @@ +{ lib, ... }: { + + imports = [{ + value = lib.mkDefault "def"; + }]; + + value = lib.mkMerge [ + (lib.mkIf false "nope") + "yes" + ]; + +} diff --git a/nixpkgs/lib/tests/modules/freeform-attrsOf.nix b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix new file mode 100644 index 000000000000..8cc577f38a6c --- /dev/null +++ b/nixpkgs/lib/tests/modules/freeform-attrsOf.nix @@ -0,0 +1,3 @@ +{ lib, ... }: { + freeformType = with lib.types; attrsOf (either str (attrsOf str)); +} diff --git a/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix new file mode 100644 index 000000000000..36d6c0b13fca --- /dev/null +++ b/nixpkgs/lib/tests/modules/freeform-lazyAttrsOf.nix @@ -0,0 +1,3 @@ +{ lib, ... }: { + freeformType = with lib.types; lazyAttrsOf (either str (lazyAttrsOf str)); +} diff --git a/nixpkgs/lib/tests/modules/freeform-nested.nix b/nixpkgs/lib/tests/modules/freeform-nested.nix new file mode 100644 index 000000000000..5da27f5a8b4f --- /dev/null +++ b/nixpkgs/lib/tests/modules/freeform-nested.nix @@ -0,0 +1,7 @@ +{ lib, ... }: { + options.nest.foo = lib.mkOption { + type = lib.types.bool; + default = false; + }; + config.nest.bar = "bar"; +} diff --git a/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix b/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix new file mode 100644 index 000000000000..a2dfbc80cfa6 --- /dev/null +++ b/nixpkgs/lib/tests/modules/freeform-str-dep-unstr.nix @@ -0,0 +1,8 @@ +{ lib, config, ... }: { + options.foo = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + + config.foo = lib.mkIf (config ? value) config.value; +} diff --git a/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix b/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix new file mode 100644 index 000000000000..549d89afecac --- /dev/null +++ b/nixpkgs/lib/tests/modules/freeform-unstr-dep-str.nix @@ -0,0 +1,8 @@ +{ lib, config, ... }: { + options.value = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + + config.foo = lib.mkIf (config.value != null) config.value; +} diff --git a/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix b/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix new file mode 100644 index 000000000000..085cbd638f17 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/attrs-coercible.nix @@ -0,0 +1,12 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config.value = { + outPath = "foo"; + err = throw "err"; + }; + +} diff --git a/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix b/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix new file mode 100644 index 000000000000..972711201a09 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/equal-atoms.nix @@ -0,0 +1,26 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config = lib.mkMerge [ + { + value.int = 0; + value.bool = false; + value.string = ""; + value.path = /.; + value.null = null; + value.float = 0.1; + } + { + value.int = 0; + value.bool = false; + value.string = ""; + value.path = /.; + value.null = null; + value.float = 0.1; + } + ]; + +} diff --git a/nixpkgs/lib/tests/modules/types-anything/functions.nix b/nixpkgs/lib/tests/modules/types-anything/functions.nix new file mode 100644 index 000000000000..079518913918 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/functions.nix @@ -0,0 +1,17 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config = lib.mkMerge [ + { + value.single-lambda = x: x; + value.multiple-lambdas = x: x; + } + { + value.multiple-lambdas = x: x; + } + ]; + +} diff --git a/nixpkgs/lib/tests/modules/types-anything/lists.nix b/nixpkgs/lib/tests/modules/types-anything/lists.nix new file mode 100644 index 000000000000..bd846afd3d18 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/lists.nix @@ -0,0 +1,16 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config = lib.mkMerge [ + { + value = [ null ]; + } + { + value = [ null ]; + } + ]; + +} diff --git a/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix b/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix new file mode 100644 index 000000000000..f84ad01df017 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/mk-mods.nix @@ -0,0 +1,44 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config = lib.mkMerge [ + { + value.mkiffalse = lib.mkIf false {}; + } + { + value.mkiftrue = lib.mkIf true {}; + } + { + value.mkdefault = lib.mkDefault 0; + } + { + value.mkdefault = 1; + } + { + value.mkmerge = lib.mkMerge [ + {} + ]; + } + { + value.mkbefore = lib.mkBefore true; + } + { + value.nested = lib.mkMerge [ + { + foo = lib.mkDefault 0; + bar = lib.mkIf false 0; + } + (lib.mkIf true { + foo = lib.mkIf true (lib.mkForce 1); + bar = { + baz = lib.mkDefault "baz"; + }; + }) + ]; + } + ]; + +} diff --git a/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix b/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix new file mode 100644 index 000000000000..e57d33ef8717 --- /dev/null +++ b/nixpkgs/lib/tests/modules/types-anything/nested-attrs.nix @@ -0,0 +1,22 @@ +{ lib, ... }: { + + options.value = lib.mkOption { + type = lib.types.anything; + }; + + config = lib.mkMerge [ + { + value.foo = null; + } + { + value.l1.foo = null; + } + { + value.l1.l2.foo = null; + } + { + value.l1.l2.l3.foo = null; + } + ]; + +} diff --git a/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix index eebee1b49bc8..800d8a65c14f 100644 --- a/nixpkgs/lib/tests/release.nix +++ b/nixpkgs/lib/tests/release.nix @@ -17,7 +17,6 @@ pkgs.runCommandNoCC "nixpkgs-lib-tests" { export TEST_ROOT=$(pwd)/test-tmp export NIX_BUILD_HOOK= export NIX_CONF_DIR=$TEST_ROOT/etc - export NIX_DB_DIR=$TEST_ROOT/db export NIX_LOCALSTATE_DIR=$TEST_ROOT/var export NIX_LOG_DIR=$TEST_ROOT/var/log/nix export NIX_STATE_DIR=$TEST_ROOT/var/nix diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix index ea8ceedd43f7..f691b2da3165 100644 --- a/nixpkgs/lib/tests/systems.nix +++ b/nixpkgs/lib/tests/systems.nix @@ -15,14 +15,14 @@ in with lib.systems.doubles; lib.runTests { testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ js ++ genode ++ redox); testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-none" "armv7a-linux" "armv7l-linux" "arm-none" "armv7a-darwin" ]; - testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ]; + testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-genode" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" "i686-none" "i686-darwin" ]; testmips = mseteq mips [ "mipsel-linux" ]; testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-genode" "x86_64-redox" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" "x86_64-none" ]; testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ]; testdarwin = mseteq darwin [ "x86_64-darwin" "i686-darwin" "aarch64-darwin" "armv7a-darwin" ]; testfreebsd = mseteq freebsd [ "i686-freebsd" "x86_64-freebsd" ]; - testgenode = mseteq genode [ "aarch64-genode" "x86_64-genode" ]; + testgenode = mseteq genode [ "aarch64-genode" "i686-genode" "x86_64-genode" ]; testredox = mseteq redox [ "x86_64-redox" ]; testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */); testillumos = mseteq illumos [ "x86_64-solaris" ]; diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix index 5788dd435e59..9501a2906cae 100644 --- a/nixpkgs/lib/trivial.nix +++ b/nixpkgs/lib/trivial.nix @@ -171,7 +171,7 @@ rec { On each release the first letter is bumped and a new animal is chosen starting with that new letter. */ - codeName = "Nightingale"; + codeName = "Okapi"; /* Returns the current nixpkgs version suffix as string. */ versionSuffix = @@ -332,4 +332,55 @@ rec { */ isFunction = f: builtins.isFunction f || (f ? __functor && isFunction (f.__functor f)); + + /* Convert the given positive integer to a string of its hexadecimal + representation. For example: + + toHexString 0 => "0" + + toHexString 16 => "10" + + toHexString 250 => "FA" + */ + toHexString = i: + let + toHexDigit = d: + if d < 10 + then toString d + else + { + "10" = "A"; + "11" = "B"; + "12" = "C"; + "13" = "D"; + "14" = "E"; + "15" = "F"; + }.${toString d}; + in + lib.concatMapStrings toHexDigit (toBaseDigits 16 i); + + /* `toBaseDigits base i` converts the positive integer i to a list of its + digits in the given base. For example: + + toBaseDigits 10 123 => [ 1 2 3 ] + + toBaseDigits 2 6 => [ 1 1 0 ] + + toBaseDigits 16 250 => [ 15 10 ] + */ + toBaseDigits = base: i: + let + go = i: + if i < base + then [i] + else + let + r = i - ((i / base) * base); + q = (i - r) / base; + in + [r] ++ go q; + in + assert (base >= 2); + assert (i >= 0); + lib.reverseList (go i); } diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix index 6fd6de7e1fd9..77105740bc23 100644 --- a/nixpkgs/lib/types.nix +++ b/nixpkgs/lib/types.nix @@ -91,9 +91,12 @@ rec { # combinable with the binOp binary operation. # binOp: binary operation that merge two payloads of the same type. functor ? defaultFunctor name + , # The deprecation message to display when this type is used by an option + # If null, the type isn't deprecated + deprecationMessage ? null }: { _type = "option-type"; - inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor; + inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage; description = if description == null then name else description; }; @@ -101,6 +104,42 @@ rec { # When adding new types don't forget to document them in # nixos/doc/manual/development/option-types.xml! types = rec { + + anything = mkOptionType { + name = "anything"; + description = "anything"; + check = value: true; + merge = loc: defs: + let + getType = value: + if isAttrs value && isCoercibleToString value + then "stringCoercibleSet" + else builtins.typeOf value; + + # Returns the common type of all definitions, throws an error if they + # don't have the same type + commonType = foldl' (type: def: + if getType def.value == type + then type + else throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}" + ) (getType (head defs).value) defs; + + mergeFunction = { + # Recursively merge attribute sets + set = (attrsOf anything).merge; + # Safe and deterministic behavior for lists is to only accept one definition + # listOf only used to apply mkIf and co. + list = + if length defs > 1 + then throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." + else (listOf anything).merge; + # This is the type of packages, only accept a single definition + stringCoercibleSet = mergeOneOption; + # Otherwise fall back to only allowing all equal definitions + }.${commonType} or mergeEqualOption; + in mergeFunction loc defs; + }; + unspecified = mkOptionType { name = "unspecified"; }; @@ -222,8 +261,10 @@ rec { # Deprecated; should not be used because it quietly concatenates # strings, which is usually not what you want. - string = warn "types.string is deprecated because it quietly concatenates strings" - (separatedString ""); + string = separatedString "" // { + name = "string"; + deprecationMessage = "See https://github.com/NixOS/nixpkgs/pull/66346 for better alternative types."; + }; attrs = mkOptionType { name = "attrs"; @@ -252,25 +293,20 @@ rec { merge = mergeEqualOption; }; - # drop this in the future: - list = builtins.trace "`types.list` is deprecated; use `types.listOf` instead" types.listOf; - listOf = elemType: mkOptionType rec { name = "listOf"; description = "list of ${elemType.description}s"; check = isList; merge = loc: defs: map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: - if isList def.value then - imap1 (m: def': - (mergeDefinitions - (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) - elemType - [{ inherit (def) file; value = def'; }] - ).optionalValue - ) def.value - else - throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs))); + imap1 (m: def': + (mergeDefinitions + (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) + elemType + [{ inherit (def) file; value = def'; }] + ).optionalValue + ) def.value + ) defs))); emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); getSubModules = elemType.getSubModules; @@ -326,110 +362,13 @@ rec { functor = (defaultFunctor name) // { wrapped = elemType; }; }; - # List or attribute set of ... - loaOf = elemType: - let - convertAllLists = loc: defs: - let - padWidth = stringLength (toString (length defs)); - unnamedPrefix = i: "unnamed-" + fixedWidthNumber padWidth i + "."; - in - imap1 (i: convertIfList loc (unnamedPrefix i)) defs; - convertIfList = loc: unnamedPrefix: def: - if isList def.value then - let - padWidth = stringLength (toString (length def.value)); - unnamed = i: unnamedPrefix + fixedWidthNumber padWidth i; - anyString = placeholder "name"; - nameAttrs = [ - { path = [ "environment" "etc" ]; - name = "target"; - } - { path = [ "containers" anyString "bindMounts" ]; - name = "mountPoint"; - } - { path = [ "programs" "ssh" "knownHosts" ]; - # hostNames is actually a list so we would need to handle it only when singleton - name = "hostNames"; - } - { path = [ "fileSystems" ]; - name = "mountPoint"; - } - { path = [ "boot" "specialFileSystems" ]; - name = "mountPoint"; - } - { path = [ "services" "znapzend" "zetup" ]; - name = "dataset"; - } - { path = [ "services" "znapzend" "zetup" anyString "destinations" ]; - name = "label"; - } - { path = [ "services" "geoclue2" "appConfig" ]; - name = "desktopID"; - } - ]; - matched = let - equals = a: b: b == anyString || a == b; - fallback = { name = "name"; }; - in findFirst ({ path, ... }: all (v: v == true) (zipListsWith equals loc path)) fallback nameAttrs; - nameAttr = matched.name; - nameValueOld = value: - if isList value then - if length value > 0 then - "[ " + concatMapStringsSep " " escapeNixString value + " ]" - else - "[ ]" - else - escapeNixString value; - nameValueNew = value: unnamed: - if isList value then - if length value > 0 then - head value - else - unnamed - else - value; - res = - { inherit (def) file; - value = listToAttrs ( - imap1 (elemIdx: elem: - { name = nameValueNew (elem.${nameAttr} or (unnamed elemIdx)) (unnamed elemIdx); - value = elem; - }) def.value); - }; - option = concatStringsSep "." loc; - sample = take 3 def.value; - more = lib.optionalString (length def.value > 3) "... "; - list = concatMapStrings (x: ''{ ${nameAttr} = ${nameValueOld (x.${nameAttr} or "unnamed")}; ...} '') sample; - set = concatMapStrings (x: ''${nameValueNew (x.${nameAttr} or "unnamed") "unnamed"} = {...}; '') sample; - msg = '' - In file ${def.file} - a list is being assigned to the option config.${option}. - This will soon be an error as type loaOf is deprecated. - See https://github.com/NixOS/nixpkgs/pull/63103 for more information. - Do - ${option} = - { ${set}${more}} - instead of - ${option} = - [ ${list}${more}] - ''; - in - lib.warn msg res - else - def; - attrOnly = attrsOf elemType; - in mkOptionType rec { - name = "loaOf"; - description = "list or attribute set of ${elemType.description}s"; - check = x: isList x || isAttrs x; - merge = loc: defs: attrOnly.merge loc (convertAllLists loc defs); - emptyValue = { value = {}; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]); - getSubModules = elemType.getSubModules; - substSubModules = m: loaOf (elemType.substSubModules m); - functor = (defaultFunctor name) // { wrapped = elemType; }; - }; + # TODO: drop this in the future: + loaOf = elemType: types.attrsOf elemType // { + name = "loaOf"; + deprecationMessage = "Mixing lists with attribute values is no longer" + + " possible; please use `types.attrsOf` instead. See" + + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation."; + }; # Value of given type but with no merging (i.e. `uniq list`s are not concatenated). uniq = elemType: mkOptionType rec { @@ -486,9 +425,15 @@ rec { else value ) defs; + freeformType = (evalModules { + inherit modules specialArgs; + args.name = "‹name›"; + })._module.freeformType; + in mkOptionType rec { name = "submodule"; + description = freeformType.description or name; check = x: isAttrs x || isFunction x || path.check x; merge = loc: defs: (evalModules { @@ -516,7 +461,12 @@ rec { # would be used, and use of `<` and `>` would break the XML document. # It shouldn't cause an issue since this is cosmetic for the manual. args.name = "‹name›"; - }).options; + }).options // optionalAttrs (freeformType != null) { + # Expose the sub options of the freeform type. Note that the option + # discovery doesn't care about the attribute name used here, so this + # is just to avoid conflicts with potential options from the submodule + _freeformOptions = freeformType.getSubOptions prefix; + }; getSubModules = modules; substSubModules = m: submoduleWith (attrs // { modules = m; @@ -618,8 +568,9 @@ rec { # declarations from the ‘options’ attribute of containing option # declaration. optionSet = mkOptionType { - name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet"; + name = "optionSet"; description = "option set"; + deprecationMessage = "Use `types.submodule' instead"; }; # Augment the given type with an additional type check function. addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; |