Merge staging-next into staging

This commit is contained in:
Frederik Rietdijk
2020-09-22 18:31:56 +02:00
142 changed files with 3478 additions and 882 deletions
+39 -20
View File
@@ -203,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
+4
View File
@@ -107,6 +107,10 @@ rec {
/* "Merge" option definitions by checking that they all have the same value. */
mergeEqualOption = loc: defs:
if defs == [] then abort "This case should never happen."
# Return early if we only have one element
# This also makes it work for functions, because the foldl' below would try
# to compare the first element with itself, which is false for functions
else if length defs == 1 then (elemAt defs 0).value
else foldl' (val: def:
if def.value != val then
throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
+65 -7
View File
@@ -445,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''''';
};
};
+29
View File
@@ -233,6 +233,35 @@ checkConfigError 'infinite recursion encountered' config.foo ./freeform-attrsOf.
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 definitions" 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
@@ -0,0 +1,12 @@
{ lib, ... }: {
options.value = lib.mkOption {
type = lib.types.anything;
};
config.value = {
outPath = "foo";
err = throw "err";
};
}
@@ -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;
}
];
}
@@ -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;
}
];
}
@@ -0,0 +1,16 @@
{ lib, ... }: {
options.value = lib.mkOption {
type = lib.types.anything;
};
config = lib.mkMerge [
{
value = [ null ];
}
{
value = [ null ];
}
];
}
@@ -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";
};
})
];
}
];
}
@@ -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;
}
];
}
+36
View File
@@ -104,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";
};