152c63c9ff
This does break the API of being able to import any lib file and get its libs, however I'm not sure people did this. I made this while exploring being able to swap out docFn with a stub in #2305, to avoid functor performance problems. I don't know if that is going to move forward (or if it is a problem or not,) but after doing all this work figured I'd put it up anyway :) Two notable advantages to this approach: 1. when a lib inherits another lib's functions, it doesn't automatically get put in to the scope of lib 2. when a lib implements a new obscure functions, it doesn't automatically get put in to the scope of lib Using the test script (later in this commit) I got the following diff on the API: + diff master fixed-lib 11764a11765,11766 > .types.defaultFunctor > .types.defaultTypeMerge 11774a11777,11778 > .types.isOptionType > .types.isType 11781a11786 > .types.mkOptionType 11788a11794 > .types.setType 11795a11802 > .types.types This means that this commit _adds_ to the API, however I can't find a way to fix these last remaining discrepancies. At least none are _removed_. Test script (run with nix-repl in the PATH): #!/bin/sh set -eux repl() { suff=${1:-} echo "(import ./lib)$suff" \ | nix-repl 2>&1 } attrs_to_check() { repl "${1:-}" \ | tr ';' $'\n' \ | grep "\.\.\." \ | cut -d' ' -f2 \ | sed -e "s/^/${1:-}./" \ | sort } summ() { repl "${1:-}" \ | tr ' ' $'\n' \ | sort \ | uniq } deep_summ() { suff="${1:-}" depth="${2:-4}" depth=$((depth - 1)) summ "$suff" for attr in $(attrs_to_check "$suff" | grep -v "types.types"); do if [ $depth -eq 0 ]; then summ "$attr" | sed -e "s/^/$attr./" else deep_summ "$attr" "$depth" | sed -e "s/^/$attr./" fi done } ( cd nixpkgs #git add . #git commit -m "Auto-commit, sorry" || true git checkout fixed-lib deep_summ > ../fixed-lib git checkout master deep_summ > ../master ) if diff master fixed-lib; then echo "SHALLOW MATCH!" fi ( cd nixpkgs git checkout fixed-lib repl .types )
469 lines
13 KiB
Nix
469 lines
13 KiB
Nix
{ lib }:
|
||
# Operations on attribute sets.
|
||
|
||
let
|
||
inherit (builtins) head tail length;
|
||
inherit (lib.trivial) and or;
|
||
inherit (lib.strings) concatStringsSep;
|
||
inherit (lib.lists) fold concatMap concatLists all deepSeqList;
|
||
in
|
||
|
||
rec {
|
||
inherit (builtins) attrNames listToAttrs hasAttr isAttrs getAttr;
|
||
|
||
|
||
/* Return an attribute from nested attribute sets.
|
||
|
||
Example:
|
||
x = { a = { b = 3; }; }
|
||
attrByPath ["a" "b"] 6 x
|
||
=> 3
|
||
attrByPath ["z" "z"] 6 x
|
||
=> 6
|
||
*/
|
||
attrByPath = attrPath: default: e:
|
||
let attr = head attrPath;
|
||
in
|
||
if attrPath == [] then e
|
||
else if e ? ${attr}
|
||
then attrByPath (tail attrPath) default e.${attr}
|
||
else default;
|
||
|
||
/* Return if an attribute from nested attribute set exists.
|
||
|
||
Example:
|
||
x = { a = { b = 3; }; }
|
||
hasAttrByPath ["a" "b"] x
|
||
=> true
|
||
hasAttrByPath ["z" "z"] x
|
||
=> false
|
||
|
||
*/
|
||
hasAttrByPath = attrPath: e:
|
||
let attr = head attrPath;
|
||
in
|
||
if attrPath == [] then true
|
||
else if e ? ${attr}
|
||
then hasAttrByPath (tail attrPath) e.${attr}
|
||
else false;
|
||
|
||
|
||
/* Return nested attribute set in which an attribute is set.
|
||
|
||
Example:
|
||
setAttrByPath ["a" "b"] 3
|
||
=> { a = { b = 3; }; }
|
||
*/
|
||
setAttrByPath = attrPath: value:
|
||
if attrPath == [] then value
|
||
else listToAttrs
|
||
[ { name = head attrPath; value = setAttrByPath (tail attrPath) value; } ];
|
||
|
||
|
||
/* Like `getAttrPath' without a default value. If it doesn't find the
|
||
path it will throw.
|
||
|
||
Example:
|
||
x = { a = { b = 3; }; }
|
||
getAttrFromPath ["a" "b"] x
|
||
=> 3
|
||
getAttrFromPath ["z" "z"] x
|
||
=> error: cannot find attribute `z.z'
|
||
*/
|
||
getAttrFromPath = attrPath: set:
|
||
let errorMsg = "cannot find attribute `" + concatStringsSep "." attrPath + "'";
|
||
in attrByPath attrPath (abort errorMsg) set;
|
||
|
||
|
||
/* Return the specified attributes from a set.
|
||
|
||
Example:
|
||
attrVals ["a" "b" "c"] as
|
||
=> [as.a as.b as.c]
|
||
*/
|
||
attrVals = nameList: set: map (x: set.${x}) nameList;
|
||
|
||
|
||
/* Return the values of all attributes in the given set, sorted by
|
||
attribute name.
|
||
|
||
Example:
|
||
attrValues {c = 3; a = 1; b = 2;}
|
||
=> [1 2 3]
|
||
*/
|
||
attrValues = builtins.attrValues or (attrs: attrVals (attrNames attrs) attrs);
|
||
|
||
|
||
/* Collect each attribute named `attr' from a list of attribute
|
||
sets. Sets that don't contain the named attribute are ignored.
|
||
|
||
Example:
|
||
catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}]
|
||
=> [1 2]
|
||
*/
|
||
catAttrs = builtins.catAttrs or
|
||
(attr: l: concatLists (map (s: if s ? ${attr} then [s.${attr}] else []) l));
|
||
|
||
|
||
/* Filter an attribute set by removing all attributes for which the
|
||
given predicate return false.
|
||
|
||
Example:
|
||
filterAttrs (n: v: n == "foo") { foo = 1; bar = 2; }
|
||
=> { foo = 1; }
|
||
*/
|
||
filterAttrs = pred: set:
|
||
listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (attrNames set));
|
||
|
||
|
||
/* Filter an attribute set recursively by removing all attributes for
|
||
which the given predicate return false.
|
||
|
||
Example:
|
||
filterAttrsRecursive (n: v: v != null) { foo = { bar = null; }; }
|
||
=> { foo = {}; }
|
||
*/
|
||
filterAttrsRecursive = pred: set:
|
||
listToAttrs (
|
||
concatMap (name:
|
||
let v = set.${name}; in
|
||
if pred name v then [
|
||
(nameValuePair name (
|
||
if isAttrs v then filterAttrsRecursive pred v
|
||
else v
|
||
))
|
||
] else []
|
||
) (attrNames set)
|
||
);
|
||
|
||
/* Apply fold functions to values grouped by key.
|
||
|
||
Example:
|
||
foldAttrs (n: a: [n] ++ a) [] [{ a = 2; } { a = 3; }]
|
||
=> { a = [ 2 3 ]; }
|
||
*/
|
||
foldAttrs = op: nul: list_of_attrs:
|
||
fold (n: a:
|
||
fold (name: o:
|
||
o // (listToAttrs [{inherit name; value = op n.${name} (a.${name} or nul); }])
|
||
) a (attrNames n)
|
||
) {} list_of_attrs;
|
||
|
||
|
||
/* Recursively collect sets that verify a given predicate named `pred'
|
||
from the set `attrs'. The recursion is stopped when the predicate is
|
||
verified.
|
||
|
||
Type:
|
||
collect ::
|
||
(AttrSet -> Bool) -> AttrSet -> [x]
|
||
|
||
Example:
|
||
collect isList { a = { b = ["b"]; }; c = [1]; }
|
||
=> [["b"] [1]]
|
||
|
||
collect (x: x ? outPath)
|
||
{ a = { outPath = "a/"; }; b = { outPath = "b/"; }; }
|
||
=> [{ outPath = "a/"; } { outPath = "b/"; }]
|
||
*/
|
||
collect = pred: attrs:
|
||
if pred attrs then
|
||
[ attrs ]
|
||
else if isAttrs attrs then
|
||
concatMap (collect pred) (attrValues attrs)
|
||
else
|
||
[];
|
||
|
||
|
||
/* Utility function that creates a {name, value} pair as expected by
|
||
builtins.listToAttrs.
|
||
|
||
Example:
|
||
nameValuePair "some" 6
|
||
=> { name = "some"; value = 6; }
|
||
*/
|
||
nameValuePair = name: value: { inherit name value; };
|
||
|
||
|
||
/* Apply a function to each element in an attribute set. The
|
||
function takes two arguments --- the attribute name and its value
|
||
--- and returns the new value for the attribute. The result is a
|
||
new attribute set.
|
||
|
||
Example:
|
||
mapAttrs (name: value: name + "-" + value)
|
||
{ x = "foo"; y = "bar"; }
|
||
=> { x = "x-foo"; y = "y-bar"; }
|
||
*/
|
||
mapAttrs = f: set:
|
||
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set));
|
||
|
||
|
||
/* Like `mapAttrs', but allows the name of each attribute to be
|
||
changed in addition to the value. The applied function should
|
||
return both the new name and value as a `nameValuePair'.
|
||
|
||
Example:
|
||
mapAttrs' (name: value: nameValuePair ("foo_" + name) ("bar-" + value))
|
||
{ x = "a"; y = "b"; }
|
||
=> { foo_x = "bar-a"; foo_y = "bar-b"; }
|
||
*/
|
||
mapAttrs' = f: set:
|
||
listToAttrs (map (attr: f attr set.${attr}) (attrNames set));
|
||
|
||
|
||
/* Call a function for each attribute in the given set and return
|
||
the result in a list.
|
||
|
||
Example:
|
||
mapAttrsToList (name: value: name + value)
|
||
{ x = "a"; y = "b"; }
|
||
=> [ "xa" "yb" ]
|
||
*/
|
||
mapAttrsToList = f: attrs:
|
||
map (name: f name attrs.${name}) (attrNames attrs);
|
||
|
||
|
||
/* Like `mapAttrs', except that it recursively applies itself to
|
||
attribute sets. Also, the first argument of the argument
|
||
function is a *list* of the names of the containing attributes.
|
||
|
||
Type:
|
||
mapAttrsRecursive ::
|
||
([String] -> a -> b) -> AttrSet -> AttrSet
|
||
|
||
Example:
|
||
mapAttrsRecursive (path: value: concatStringsSep "-" (path ++ [value]))
|
||
{ n = { a = "A"; m = { b = "B"; c = "C"; }; }; d = "D"; }
|
||
=> { n = { a = "n-a-A"; m = { b = "n-m-b-B"; c = "n-m-c-C"; }; }; d = "d-D"; }
|
||
*/
|
||
mapAttrsRecursive = mapAttrsRecursiveCond (as: true);
|
||
|
||
|
||
/* Like `mapAttrsRecursive', but it takes an additional predicate
|
||
function that tells it whether to recursive into an attribute
|
||
set. If it returns false, `mapAttrsRecursiveCond' does not
|
||
recurse, but does apply the map function. It is returns true, it
|
||
does recurse, and does not apply the map function.
|
||
|
||
Type:
|
||
mapAttrsRecursiveCond ::
|
||
(AttrSet -> Bool) -> ([String] -> a -> b) -> AttrSet -> AttrSet
|
||
|
||
Example:
|
||
# To prevent recursing into derivations (which are attribute
|
||
# sets with the attribute "type" equal to "derivation"):
|
||
mapAttrsRecursiveCond
|
||
(as: !(as ? "type" && as.type == "derivation"))
|
||
(x: ... do something ...)
|
||
attrs
|
||
*/
|
||
mapAttrsRecursiveCond = cond: f: set:
|
||
let
|
||
recurse = path: set:
|
||
let
|
||
g =
|
||
name: value:
|
||
if isAttrs value && cond value
|
||
then recurse (path ++ [name]) value
|
||
else f (path ++ [name]) value;
|
||
in mapAttrs g set;
|
||
in recurse [] set;
|
||
|
||
|
||
/* Generate an attribute set by mapping a function over a list of
|
||
attribute names.
|
||
|
||
Example:
|
||
genAttrs [ "foo" "bar" ] (name: "x_" + name)
|
||
=> { foo = "x_foo"; bar = "x_bar"; }
|
||
*/
|
||
genAttrs = names: f:
|
||
listToAttrs (map (n: nameValuePair n (f n)) names);
|
||
|
||
|
||
/* Check whether the argument is a derivation. Any set with
|
||
{ type = "derivation"; } counts as a derivation.
|
||
|
||
Example:
|
||
nixpkgs = import <nixpkgs> {}
|
||
isDerivation nixpkgs.ruby
|
||
=> true
|
||
isDerivation "foobar"
|
||
=> false
|
||
*/
|
||
isDerivation = x: isAttrs x && x ? type && x.type == "derivation";
|
||
|
||
/* Converts a store path to a fake derivation. */
|
||
toDerivation = path:
|
||
let
|
||
path' = builtins.storePath path;
|
||
res =
|
||
{ type = "derivation";
|
||
name = builtins.unsafeDiscardStringContext (builtins.substring 33 (-1) (baseNameOf path'));
|
||
outPath = path';
|
||
outputs = [ "out" ];
|
||
out = res;
|
||
outputName = "out";
|
||
};
|
||
in res;
|
||
|
||
|
||
/* If `cond' is true, return the attribute set `as',
|
||
otherwise an empty attribute set.
|
||
|
||
Example:
|
||
optionalAttrs (true) { my = "set"; }
|
||
=> { my = "set"; }
|
||
optionalAttrs (false) { my = "set"; }
|
||
=> { }
|
||
*/
|
||
optionalAttrs = cond: as: if cond then as else {};
|
||
|
||
|
||
/* Merge sets of attributes and use the function f to merge attributes
|
||
values.
|
||
|
||
Example:
|
||
zipAttrsWithNames ["a"] (name: vs: vs) [{a = "x";} {a = "y"; b = "z";}]
|
||
=> { a = ["x" "y"]; }
|
||
*/
|
||
zipAttrsWithNames = names: f: sets:
|
||
listToAttrs (map (name: {
|
||
inherit name;
|
||
value = f name (catAttrs name sets);
|
||
}) names);
|
||
|
||
/* Implementation note: Common names appear multiple times in the list of
|
||
names, hopefully this does not affect the system because the maximal
|
||
laziness avoid computing twice the same expression and listToAttrs does
|
||
not care about duplicated attribute names.
|
||
|
||
Example:
|
||
zipAttrsWith (name: values: values) [{a = "x";} {a = "y"; b = "z";}]
|
||
=> { a = ["x" "y"]; b = ["z"] }
|
||
*/
|
||
zipAttrsWith = f: sets: zipAttrsWithNames (concatMap attrNames sets) f sets;
|
||
/* Like `zipAttrsWith' with `(name: values: value)' as the function.
|
||
|
||
Example:
|
||
zipAttrs [{a = "x";} {a = "y"; b = "z";}]
|
||
=> { a = ["x" "y"]; b = ["z"] }
|
||
*/
|
||
zipAttrs = zipAttrsWith (name: values: values);
|
||
|
||
/* Does the same as the update operator '//' except that attributes are
|
||
merged until the given predicate is verified. The predicate should
|
||
accept 3 arguments which are the path to reach the attribute, a part of
|
||
the first attribute set and a part of the second attribute set. When
|
||
the predicate is verified, the value of the first attribute set is
|
||
replaced by the value of the second attribute set.
|
||
|
||
Example:
|
||
recursiveUpdateUntil (path: l: r: path == ["foo"]) {
|
||
# first attribute set
|
||
foo.bar = 1;
|
||
foo.baz = 2;
|
||
bar = 3;
|
||
} {
|
||
#second attribute set
|
||
foo.bar = 1;
|
||
foo.quz = 2;
|
||
baz = 4;
|
||
}
|
||
|
||
returns: {
|
||
foo.bar = 1; # 'foo.*' from the second set
|
||
foo.quz = 2; #
|
||
bar = 3; # 'bar' from the first set
|
||
baz = 4; # 'baz' from the second set
|
||
}
|
||
|
||
*/
|
||
recursiveUpdateUntil = pred: lhs: rhs:
|
||
let f = attrPath:
|
||
zipAttrsWith (n: values:
|
||
if tail values == []
|
||
|| pred attrPath (head (tail values)) (head values) then
|
||
head values
|
||
else
|
||
f (attrPath ++ [n]) values
|
||
);
|
||
in f [] [rhs lhs];
|
||
|
||
/* A recursive variant of the update operator ‘//’. The recursion
|
||
stops when one of the attribute values is not an attribute set,
|
||
in which case the right hand side value takes precedence over the
|
||
left hand side value.
|
||
|
||
Example:
|
||
recursiveUpdate {
|
||
boot.loader.grub.enable = true;
|
||
boot.loader.grub.device = "/dev/hda";
|
||
} {
|
||
boot.loader.grub.device = "";
|
||
}
|
||
|
||
returns: {
|
||
boot.loader.grub.enable = true;
|
||
boot.loader.grub.device = "";
|
||
}
|
||
|
||
*/
|
||
recursiveUpdate = lhs: rhs:
|
||
recursiveUpdateUntil (path: lhs: rhs:
|
||
!(isAttrs lhs && isAttrs rhs)
|
||
) lhs rhs;
|
||
|
||
/* Returns true if the pattern is contained in the set. False otherwise.
|
||
|
||
Example:
|
||
matchAttrs { cpu = {}; } { cpu = { bits = 64; }; }
|
||
=> true
|
||
*/
|
||
matchAttrs = pattern: attrs: assert isAttrs pattern;
|
||
fold and true (attrValues (zipAttrsWithNames (attrNames pattern) (n: values:
|
||
let pat = head values; val = head (tail values); in
|
||
if length values == 1 then false
|
||
else if isAttrs pat then isAttrs val && matchAttrs pat val
|
||
else pat == val
|
||
) [pattern attrs]));
|
||
|
||
/* Override only the attributes that are already present in the old set
|
||
useful for deep-overriding.
|
||
|
||
Example:
|
||
x = { a = { b = 4; c = 3; }; }
|
||
overrideExisting x { a = { b = 6; d = 2; }; }
|
||
=> { a = { b = 6; d = 2; }; }
|
||
*/
|
||
overrideExisting = old: new:
|
||
old // listToAttrs (map (attr: nameValuePair attr (attrByPath [attr] old.${attr} new)) (attrNames old));
|
||
|
||
/* Get a package output.
|
||
If no output is found, fallback to `.out` and then to the default.
|
||
|
||
Example:
|
||
getOutput "dev" pkgs.openssl
|
||
=> "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev"
|
||
*/
|
||
getOutput = output: pkg:
|
||
if pkg.outputUnspecified or false
|
||
then pkg.${output} or pkg.out or pkg
|
||
else pkg;
|
||
|
||
getBin = getOutput "bin";
|
||
getLib = getOutput "lib";
|
||
getDev = getOutput "dev";
|
||
|
||
/* Pick the outputs of packages to place in buildInputs */
|
||
chooseDevOutputs = drvs: builtins.map getDev drvs;
|
||
|
||
/*** deprecated stuff ***/
|
||
|
||
zipWithNames = zipAttrsWithNames;
|
||
zip = builtins.trace
|
||
"lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith;
|
||
|
||
}
|