diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/attrsets.nix | 329 | ||||
-rw-r--r-- | lib/composable-derivation.nix | 54 | ||||
-rw-r--r-- | lib/customisation.nix | 116 | ||||
-rw-r--r-- | lib/debug.nix | 119 | ||||
-rw-r--r-- | lib/default.nix | 31 | ||||
-rw-r--r-- | lib/licenses.nix | 235 | ||||
-rw-r--r-- | lib/lists.nix | 235 | ||||
-rw-r--r-- | lib/maintainers.nix | 65 | ||||
-rw-r--r-- | lib/meta.nix | 48 | ||||
-rw-r--r-- | lib/misc.nix | 431 | ||||
-rw-r--r-- | lib/modules.nix | 380 | ||||
-rw-r--r-- | lib/options.nix | 315 | ||||
-rw-r--r-- | lib/platforms.nix | 16 | ||||
-rw-r--r-- | lib/properties.nix | 464 | ||||
-rw-r--r-- | lib/sources.nix | 29 | ||||
-rw-r--r-- | lib/strings-with-deps.nix | 78 | ||||
-rw-r--r-- | lib/strings.nix | 190 | ||||
-rw-r--r-- | lib/systems.nix | 126 | ||||
-rw-r--r-- | lib/tests.nix | 113 | ||||
-rw-r--r-- | lib/trivial.nix | 38 | ||||
-rw-r--r-- | lib/types.nix | 226 |
21 files changed, 3638 insertions, 0 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix new file mode 100644 index 000000000000..01d51779c809 --- /dev/null +++ b/lib/attrsets.nix @@ -0,0 +1,329 @@ +# Operations on attribute sets. + +with { + inherit (builtins) head tail isString; + inherit (import ./trivial.nix) or; + inherit (import ./default.nix) fold; + inherit (import ./strings.nix) concatStringsSep; + inherit (import ./lists.nix) concatMap concatLists all deepSeqList; + inherit (import ./misc.nix) maybeAttr; +}; + +rec { + inherit (builtins) attrNames listToAttrs hasAttr isAttrs getAttr; + + + /* Return an attribute from nested attribute sets. For instance + ["x" "y"] applied to some set e returns e.x.y, if it exists. The + default value is returned otherwise. */ + attrByPath = attrPath: default: e: + let attr = head attrPath; + in + if attrPath == [] then e + else if builtins ? hasAttr && hasAttr attr e + then attrByPath (tail attrPath) default (getAttr attr e) + else default; + + + /* Return nested attribute set in which an attribute is set. For instance + ["x" "y"] applied with some value v returns `x.y = v;' */ + setAttrByPath = attrPath: value: + if attrPath == [] then value + else listToAttrs [( + nameValuePair (head attrPath) (setAttrByPath (tail attrPath) value) + )]; + + + 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: getAttr x set) 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 = 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 = attr: l: concatLists (map (s: if hasAttr attr s then [(getAttr attr s)] 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 (fold (n: ys: let v = getAttr n set; in if pred n v then [(nameValuePair n v)] ++ ys else ys) [] (attrNames set)); + + + /* foldAttrs: apply fold functions to values grouped by key. Eg accumulate values as list: + 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 (getAttr name n) (maybeAttr name nul a); }]) + ) 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 -> AttrSet + + Example: + collect builtins.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 builtins.isAttrs attrs then + concatMap (collect pred) (attrValues attrs) + else + []; + + + /* Utility function that creates a {name, value} pair as expected by + builtins.listToAttrs. */ + 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: nameValuePair attr (f attr (getAttr attr set))) (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 (getAttr attr set)) (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 (getAttr name attrs)) (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. */ + isDerivation = x: isAttrs x && x ? type && x.type == "derivation"; + + + /* If the Boolean `cond' is true, return the attribute set `as', + otherwise an empty attribute set. */ + optionalAttrs = cond: as: if cond then as else {}; + + + /* Merge sets of attributes and use the function f to merge attributes + values. */ + zipAttrsWithNames = names: f: sets: + listToAttrs (map (name: { + inherit name; + value = f name (catAttrs name sets); + }) names); + + # implentation 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. + zipAttrsWith = f: sets: zipWithNames (concatMap attrNames sets) f sets; + + zipAttrs = zipAttrsWith (name: values: values); + + /* backward compatibility */ + zipWithNames = zipAttrsWithNames; + zip = builtins.trace "lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith; + + + /* Does the same as the update operator '//' except that attributes are + merged until the given pedicate 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 recusion + 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; + + matchAttrs = pattern: attrs: + fold or false (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 head values + else pat == val + ) [pattern attrs])); + + # override only the attributes that are already present in the old set + # useful for deep-overriding + overrideExisting = old: new: + old // listToAttrs (map (attr: nameValuePair attr (attrByPath [attr] (getAttr attr old) new)) (attrNames old)); + + deepSeqAttrs = x: y: deepSeqList (attrValues x) y; +} diff --git a/lib/composable-derivation.nix b/lib/composable-derivation.nix new file mode 100644 index 000000000000..1099bd152bf6 --- /dev/null +++ b/lib/composable-derivation.nix @@ -0,0 +1,54 @@ +{lib, pkgs} : +let inherit (lib) nv nvs; in +{ + # see for example: + # - development/interpreters/php_configurable/default.nix + # - .. search composableDerivation in all-packages.nix .. + # + # You should be able to override anything you like easily + # grep the mailinglist by title "python proposal" (dec 08) + # -> http://mail.cs.uu.nl/pipermail/nix-dev/2008-December/001571.html + # to see why this got complicated when using all its features + # TODO add newer example using new syntax (kernel derivation proposal -> mailinglist) + composableDerivation = { + mkDerivation ? pkgs.stdenv.mkDerivation, + + # list of functions to be applied before defaultOverridableDelayableArgs removes removeAttrs names + # prepareDerivationArgs handles derivation configurations + applyPreTidy ? [ lib.prepareDerivationArgs ], + + # consider adding addtional elements by derivation.merge { removeAttrs = ["elem"]; }; + removeAttrs ? ["cfg" "flags"] + + }: (lib.defaultOverridableDelayableArgs ( a: mkDerivation a) + { + inherit applyPreTidy removeAttrs; + }).merge; + + # some utility functions + # use this function to generate flag attrs for prepareDerivationArgs + # E nable D isable F eature + edf = {name, feat ? name, enable ? {}, disable ? {} , value ? ""}: + nvs name { + set = { + configureFlags = ["--enable-${feat}${if value == "" then "" else "="}${value}"]; + } // enable; + unset = { + configureFlags = ["--disable-${feat}"]; + } // disable; + }; + + # same for --with and --without- + # W ith or W ithout F eature + wwf = {name, feat ? name, enable ? {}, disable ? {}, value ? ""}: + nvs name { + set = enable // { + configureFlags = ["--with-${feat}${if value == "" then "" else "="}${value}"] + ++ lib.maybeAttr "configureFlags" [] enable; + }; + unset = disable // { + configureFlags = ["--without-${feat}"] + ++ lib.maybeAttr "configureFlags" [] disable; + }; + }; +} diff --git a/lib/customisation.nix b/lib/customisation.nix new file mode 100644 index 000000000000..bfa61169efb1 --- /dev/null +++ b/lib/customisation.nix @@ -0,0 +1,116 @@ +let lib = import ./default.nix; + inherit (builtins) getAttr attrNames isFunction; + +in + +rec { + + + /* `overrideDerivation drv f' takes a derivation (i.e., the result + of a call to the builtin function `derivation') and returns a new + derivation in which the attributes of the original are overriden + according to the function `f'. The function `f' is called with + the original derivation attributes. + + `overrideDerivation' allows certain "ad-hoc" customisation + scenarios (e.g. in ~/.nixpkgs/config.nix). For instance, if you + want to "patch" the derivation returned by a package function in + Nixpkgs to build another version than what the function itself + provides, you can do something like this: + + mySed = overrideDerivation pkgs.gnused (oldAttrs: { + name = "sed-4.2.2-pre"; + src = fetchurl { + url = ftp://alpha.gnu.org/gnu/sed/sed-4.2.2-pre.tar.bz2; + sha256 = "11nq06d131y4wmf3drm0yk502d2xc6n5qy82cg88rb9nqd2lj41k"; + }; + patches = []; + }); + + For another application, see build-support/vm, where this + function is used to build arbitrary derivations inside a QEMU + virtual machine. */ + + overrideDerivation = drv: f: + let + newDrv = derivation (drv.drvAttrs // (f drv)); + in addPassthru newDrv ( + { meta = drv.meta or {}; + passthru = if drv ? passthru then drv.passthru else {}; + } + // + (drv.passthru or {}) + // + (if (drv ? crossDrv && drv ? nativeDrv) + then { + crossDrv = overrideDerivation drv.crossDrv f; + nativeDrv = overrideDerivation drv.nativeDrv f; + } + else { })); + + + # usage: (you can use override multiple times) + # let d = makeOverridable stdenv.mkDerivation { name = ..; buildInputs; } + # noBuildInputs = d.override { buildInputs = []; } + # additionalBuildInputs = d.override ( args : args // { buildInputs = args.buildInputs ++ [ additional ]; } ) + makeOverridable = f: origArgs: + let + ff = f origArgs; + in + if builtins.isAttrs ff then (ff // + { override = newArgs: + makeOverridable f (origArgs // (if builtins.isFunction newArgs then newArgs origArgs else newArgs)); + deepOverride = newArgs: + makeOverridable f (lib.overrideExisting (lib.mapAttrs (deepOverrider newArgs) origArgs) newArgs); + }) + else ff; + + deepOverrider = newArgs: name: x: if builtins.isAttrs x then ( + if x ? deepOverride then (x.deepOverride newArgs) else + if x ? override then (x.override newArgs) else + x) else x; + + + /* Call the package function in the file `fn' with the required + arguments automatically. The function is called with the + arguments `args', but any missing arguments are obtained from + `autoArgs'. This function is intended to be partially + parameterised, e.g., + + callPackage = callPackageWith pkgs; + pkgs = { + libfoo = callPackage ./foo.nix { }; + libbar = callPackage ./bar.nix { }; + }; + + If the `libbar' function expects an argument named `libfoo', it is + automatically passed as an argument. Overrides or missing + arguments can be supplied in `args', e.g. + + libbar = callPackage ./bar.nix { + libfoo = null; + enableX11 = true; + }; + */ + callPackageWith = autoArgs: fn: args: + let f = if builtins.isFunction fn then fn else import fn; in + makeOverridable f ((builtins.intersectAttrs (builtins.functionArgs f) autoArgs) // args); + + /* Add attributes to each output of a derivation without changing the derivation itself */ + addPassthru = drv: passthru: + let + outputs = drv.outputs or [ "out" ]; + + commonAttrs = drv // (builtins.listToAttrs outputsList) // + ({ all = map (x: x.value) outputsList; }) // passthru; + + outputToAttrListElement = outputName: + { name = outputName; + value = commonAttrs // { + inherit (builtins.getAttr outputName drv) outPath drvPath type outputName; + }; + }; + + outputsList = map outputToAttrListElement outputs; + in builtins.getAttr drv.outputName commonAttrs; +} diff --git a/lib/debug.nix b/lib/debug.nix new file mode 100644 index 000000000000..d627bc861abb --- /dev/null +++ b/lib/debug.nix @@ -0,0 +1,119 @@ +let lib = import ./default.nix; + +inherit (builtins) trace attrNamesToStr isAttrs isFunction isList isInt + isString isBool head substring attrNames; + +inherit (lib) all id mapAttrsFlatten elem; + +in + +rec { + + + # Wrapper aroung the primop `addErrorContext', which shouldn't used + # directly. It evaluates and returns `val', but if an evaluation + # error occurs, the text in `msg' is added to the error context + # (stack trace) printed by Nix. + addErrorContext = + if builtins ? addErrorContext + then builtins.addErrorContext + else msg: val: val; + + addErrorContextToAttrs = lib.mapAttrs (a : v : lib.addErrorContext "while evaluating ${a}" v); + + + traceVal = if builtins ? trace then x: (builtins.trace x x) else x: x; + traceXMLVal = if builtins ? trace then x: (builtins.trace (builtins.toXML x) x) else x: x; + traceXMLValMarked = str: if builtins ? trace then x: (builtins.trace ( str + builtins.toXML x) x) else x: x; + + # this can help debug your code as well - designed to not produce thousands of lines + traceShowVal = x : trace (showVal x) x; + traceShowValMarked = str: x: trace (str + showVal x) x; + attrNamesToStr = a : lib.concatStringsSep "; " (map (x : "${x}=") (attrNames a)); + showVal = x : + if isAttrs x then + if x ? outPath then "x is a derivation, name ${if x ? name then x.name else "<no name>"}, { ${attrNamesToStr x} }" + else "x is attr set { ${attrNamesToStr x} }" + else if isFunction x then "x is a function" + else if x == [] then "x is an empty list" + else if isList x then "x is a list, first element is: ${showVal (head x)}" + else if x == true then "x is boolean true" + else if x == false then "x is boolean false" + else if x == null then "x is null" + else if isInt x then "x is an integer `${toString x}'" + else if isString x then "x is a string `${substring 0 50 x}...'" + else "x is probably a path `${substring 0 50 (toString x)}...'"; + + # trace the arguments passed to function and its result + # maybe rewrite these functions in a traceCallXml like style. Then one function is enough + traceCall = n : f : a : let t = n2 : x : traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a)); + traceCall2 = n : f : a : b : let t = n2 : x : traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b)); + traceCall3 = n : f : a : b : c : let t = n2 : x : traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b) (t "arg 3" c)); + + traceValIfNot = c: x: + if c x then true else trace (showVal x) false; + + /* Evaluate a set of tests. A test is an attribute set {expr, + expected}, denoting an expression and its expected result. The + result is a list of failed tests, each represented as {name, + expected, actual}, denoting the attribute name of the failing + test and its expected and actual results. Used for regression + testing of the functions in lib; see tests.nix for an example. + Only tests having names starting with "test" are run. + Add attr { tests = ["testName"]; } to run these test only + */ + runTests = tests: lib.concatLists (lib.attrValues (lib.mapAttrs (name: test: + let testsToRun = if tests ? tests then tests.tests else []; + in if (substring 0 4 name == "test" || elem name testsToRun) + && ((testsToRun == []) || elem name tests.tests) + && (test.expr != test.expected) + + then [ { inherit name; expected = test.expected; result = test.expr; } ] + else [] ) tests)); + + # create a test assuming that list elements are true + # usage: { testX = allTrue [ true ]; } + testAllTrue = expr : { inherit expr; expected = map (x: true) expr; }; + + # evaluate everything once so that errors will occur earlier + # hacky: traverse attrs by adding a dummy + # ignores functions (should this behavior change?) See strictf + # + # Note: This should be a primop! Something like seq of haskell would be nice to + # have as well. It's used fore debugging only anyway + strict = x : + let + traverse = x : + if isString x then true + else if isAttrs x then + if x ? outPath then true + else all id (mapAttrsFlatten (n: traverse) x) + else if isList x then + all id (map traverse x) + else if isBool x then true + else if isFunction x then true + else if isInt x then true + else if x == null then true + else true; # a (store) path? + in if traverse x then x else throw "else never reached"; + + # example: (traceCallXml "myfun" id 3) will output something like + # calling myfun arg 1: 3 result: 3 + # this forces deep evaluation of all arguments and the result! + # note: if result doesn't evaluate you'll get no trace at all (FIXME) + # args should be printed in any case + traceCallXml = a: + if !isInt a then + traceCallXml 1 "calling ${a}\n" + else + let nr = a; + in (str: expr: + if isFunction expr then + (arg: + traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (strict arg)}" (expr arg) + ) + else + let r = strict expr; + in builtins.trace "${str}\n result:\n${builtins.toXML r}" r + ); +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 000000000000..dea82ee077eb --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,31 @@ +let + + trivial = import ./trivial.nix; + lists = import ./lists.nix; + strings = import ./strings.nix; + stringsWithDeps = import ./strings-with-deps.nix; + attrsets = import ./attrsets.nix; + sources = import ./sources.nix; + modules = import ./modules.nix; + options = import ./options.nix; + properties = import ./properties.nix; + types = import ./types.nix; + meta = import ./meta.nix; + debug = import ./debug.nix; + misc = import ./misc.nix; + maintainers = import ./maintainers.nix; + platforms = import ./platforms.nix; + systems = import ./systems.nix; + customisation = import ./customisation.nix; + licenses = import ./licenses.nix; + +in + { inherit trivial lists strings stringsWithDeps attrsets sources options + properties modules types meta debug maintainers licenses platforms systems; + } + # !!! don't include everything at top-level; perhaps only the most + # commonly used functions. + // trivial // lists // strings // stringsWithDeps // attrsets // sources + // properties // options // types // meta // debug // misc // modules + // systems + // customisation diff --git a/lib/licenses.nix b/lib/licenses.nix new file mode 100644 index 000000000000..55517c5e1e5e --- /dev/null +++ b/lib/licenses.nix @@ -0,0 +1,235 @@ +{ + /* License identifiers loosely based on: http://fedoraproject.org/wiki/Licensing + * If you cannot find your license here, then look for a similar license or + * add it to this list. The URL mentioned above is a good source for inspiration. + */ + + artistic2 = { + shortName = "Artistic 2.0"; + fullName = "Artistic 2.0"; + url = "http://opensource.org/licenses/artistic-license-2.0.php"; + }; + + agpl3 = { + shortName = "AGPLv3"; + fullName = "GNU Affero General Public License version 3 only"; + url = https://www.gnu.org/licenses/agpl.html; + }; + + agpl3Plus = { + shortName = "AGPLv3+"; + fullName = "GNU Affero General Public License version 3 or later"; + url = https://www.gnu.org/licenses/agpl.html; + }; + + amd = { + shortName = "amd"; + fullName = "AMD License Agreement"; + url = "http://developer.amd.com/amd-license-agreement/"; + }; + + amdadl = { + shortName = "amd-adl"; + fullName = "amd-adl license"; + url = "http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/AMD-ADL?revision=1.1"; + }; + + # Apple Public Source License 2.0; + # http://opensource.org/licenses/APSL-2.0 + apsl20 = "APSL 2.0"; + + asl20 = { + shortName = "ASL2.0"; + fullName = "Apache Software License 2.0"; + url = http://www.apache.org/licenses/LICENSE-2.0; + }; + + boost = { + shortName = "boost"; + fullName = "Boost Software License"; + url = http://www.boost.org/LICENSE_1_0.txt; + }; + + bsd2 = { + shortName = "BSD-2"; + fullName = "BSD license (2 clause)"; + url = http://opensource.org/licenses/BSD-2-Clause; + }; + + bsd3 = { + shortName = "BSD-3"; + fullName = "BSD license (3 clause)"; + url = http://opensource.org/licenses/BSD-3-Clause; + }; + + bsdOriginal = { + shortName = "BSD-original"; + fullName = "Original BSD license with advertising clause"; + url = https://fedoraproject.org/wiki/Licensing/BSD; + }; + + cddl = { + shortName = "CDDL"; + fullName = "Common Development Distribution License "; + url = http://www.opensolaris.org/os/licensing/cddllicense.txt; + }; + + cpl10 = { + shortName = "CPL 1.0"; + fullName = "Common Public License version 1.0"; + url = http://www.eclipse.org/legal/cpl-v10.html; + }; + + epl10 = { + shortName = "EPL 1.0"; + fullName = "Eclipse Public License version 1.0"; + url = http://www.eclipse.org/legal/epl-v10.html; + }; + + gpl2 = "GPLv2"; + + gpl2Oss = { + shortName = "GPLv2+OSS"; + fullName = "GNU General Public License version 2 only (with OSI approved licenses linking exception)"; + url = http://www.mysql.com/about/legal/licensing/foss-exception; + }; + + # GNU General Public License version 2 or later; + # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + gpl2Plus = "GPLv2+"; + + gpl3 = { + shortName = "GPLv3"; + fullName = "GNU General Public License version 3 only"; + url = http://www.fsf.org/licensing/licenses/gpl.html; + }; + + gpl3Plus = { + shortName = "GPLv3+"; + fullName = "GNU General Public License version 3 or later"; + url = http://www.fsf.org/licensing/licenses/gpl.html; + }; + + gpl3ClasspathPlus = { + shortName = "GPLv3+classpath+"; + fullName = "GNU General Public License version 3 or later (with Classpath exception)"; + url = https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception; + }; + + isc = { + shortName = "ISC"; + fullName = "Internet Systems Consortium License"; + url = http://www.opensource.org/licenses/ISC; + }; + + ipl10 = { + shortName = "IPL 1.0"; + fullName = "IBM Public License Version 1.0"; + url = http://www.ibm.com/developerworks/opensource/library/os-i18n2/os-ipl.html; + }; + + ijg = { + shortName = "IJG"; + fullName = "Independent JPEG Group License"; + url = https://fedoraproject.org/wiki/Licensing/IJG; + }; + + libtiff = { + shortName = "libtiff"; + fullName = "libtiff license"; + url = https://fedoraproject.org/wiki/Licensing/libtiff; + }; + + lgpl2 = "LGPLv2"; + + lgpl2Plus = { + shortName = "LGPLv2+"; + fullName = "GNU Library General Public License version 2 or later"; + url = http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html; + }; + + lgpl21 = "LGPLv2.1"; + + lgpl21Plus = { + shortName = "LGPLv2.1+"; + fullName = "GNU Lesser General Public License version 2.1 or later"; + url = http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html; + }; + + llgpl21 = { + shortName = "LLGPLv2.1"; + fullName = "Lisp LGPL; GNU Lesser General Public License version 2.1 with Franz Inc. preamble for clarification of LGPL terms in context of Lisp"; + url = http://opensource.franz.com/preamble.html; + }; + + lgpl3 = { + shortName = "LGPLv3"; + fullName = "GNU Lesser General Public License version 3 only"; + url = http://www.fsf.org/licensing/licenses/lgpl.html; + }; + + lgpl3Plus = { + shortName = "LGPLv3+"; + fullName = "GNU Lesser General Public License version 3 or later"; + url = http://www.fsf.org/licensing/licenses/lgpl.html; + }; + + mit = { + shortName = "MIT"; + fullName = "MIT/X11 license"; + url = http://www.opensource.org/licenses/mit-license.php; + }; + + mpl11 = { + shortName = "MPL1.1"; + fullName = "Mozilla Public License version 1.1"; + url = http://www.mozilla.org/MPL/MPL-1.1.html; + }; + + openssl = { + shortName = "openssl"; + fullName = "OpenSSL license"; + url = http://www.openssl.org/source/license.html; + }; + + publicDomain = { + shortName = "Public Domain"; + fullname = "Public Domain"; + }; + + psfl = { + shortName = "PSFL"; + fullName = "Python Software Foundation License"; + url = http://docs.python.org/license.html; + }; + + tcltk = { + shortName = "Tcl/Tk"; + fullName = "Tcl/Tk license"; + url = http://www.tcl.tk/software/tcltk/license.html; + }; + + unfree = "unfree"; + + unfreeRedistributable = "unfree-redistributable"; + + unfreeRedistributableFirmware = "unfree-redistributable-firmware"; + + zlib = { + shortName = "zlib"; + fullName = "zlib license"; + url = http://www.gzip.org/zlib/zlib_license.html; + }; + + zpt20 = { + shortName = "ZPT2.0"; + fullName = "Zope Public License 2.0"; + url = "http://old.zope.org/Resources/License/ZPL-2.0"; + }; + + zpt21 = { + shortName = "ZPT2.1"; + fullName = "Zope Public License 2.1"; + url = "http://old.zope.org/Resources/License/ZPL-2.1"; + }; +} diff --git a/lib/lists.nix b/lib/lists.nix new file mode 100644 index 000000000000..578686ae3668 --- /dev/null +++ b/lib/lists.nix @@ -0,0 +1,235 @@ +# General list operations. +let + inherit (import ./trivial.nix) deepSeq; + + inc = builtins.add 1; + + dec = n: builtins.sub n 1; + + inherit (builtins) elemAt; +in rec { + inherit (builtins) head tail length isList add sub lessThan; + + + # Create a list consisting of a single element. `singleton x' is + # sometimes more convenient with respect to indentation than `[x]' + # when x spans multiple lines. + singleton = x: [x]; + + + # "Fold" a binary function `op' between successive elements of + # `list' with `nul' as the starting value, i.e., `fold op nul [x_1 + # x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))'. (This is + # Haskell's foldr). + fold = op: nul: list: + let + len = length list; + fold' = n: + if n == len + then nul + else op (elemAt list n) (fold' (inc n)); + in fold' 0; + + # Left fold: `fold op nul [x_1 x_2 ... x_n] == op (... (op (op nul + # x_1) x_2) ... x_n)'. + foldl = op: nul: list: + let + len = length list; + foldl' = n: + if n == minus1 + then nul + else op (foldl' (dec n)) (elemAt list n); + in foldl' (dec (length list)); + + minus1 = dec 0; + + + # map with index: `imap (i: v: "${v}-${toString i}") ["a" "b"] == + # ["a-1" "b-2"]' + imap = f: list: + let + len = length list; + imap' = n: + if n == len + then [] + else [ (f (inc n) (elemAt list n)) ] ++ imap' (inc n); + in imap' 0; + + + # Concatenate a list of lists. + concatLists = builtins.concatLists or (fold (x: y: x ++ y) []); + + + # Map and concatenate the result. + concatMap = f: list: concatLists (map f list); + + + # Flatten the argument into a single list; that is, nested lists are + # spliced into the top-level lists. E.g., `flatten [1 [2 [3] 4] 5] + # == [1 2 3 4 5]' and `flatten 1 == [1]'. + flatten = x: + if isList x + then fold (x: y: (flatten x) ++ y) [] x + else [x]; + + + # Filter a list using a predicate; that is, return a list containing + # every element from `list' for which `pred' returns true. + filter = + builtins.filter or + (pred: list: + fold (x: y: if pred x then [x] ++ y else y) [] list); + + + # Remove elements equal to 'e' from a list. Useful for buildInputs. + remove = e: filter (x: x != e); + + + # Return true if `list' has an element `x'. + elem = + builtins.elem or + (x: list: fold (a: bs: x == a || bs) false list); + + + # Find the sole element in the list matching the specified + # predicate, returns `default' if no such element exists, or + # `multiple' if there are multiple matching elements. + findSingle = pred: default: multiple: list: + let found = filter pred list; len = length found; + in if len == 0 then default + else if len != 1 then multiple + else head found; + + + # Find the first element in the list matching the specified + # predicate or returns `default' if no such element exists. + findFirst = pred: default: list: + let found = filter pred list; + in if found == [] then default else head found; + + + # Return true iff function `pred' returns true for at least element + # of `list'. + any = pred: fold (x: y: if pred x then true else y) false; + + + # Return true iff function `pred' returns true for all elements of + # `list'. + all = pred: fold (x: y: if pred x then y else false) true; + + + # Return a singleton list or an empty list, depending on a boolean + # value. Useful when building lists with optional elements + # (e.g. `++ optional (system == "i686-linux") flashplayer'). + optional = cond: elem: if cond then [elem] else []; + + + # Return a list or an empty list, dependening on a boolean value. + optionals = cond: elems: if cond then elems else []; + + + # If argument is a list, return it; else, wrap it in a singleton + # list. If you're using this, you should almost certainly + # reconsider if there isn't a more "well-typed" approach. + toList = x: if builtins.isList x then x else [x]; + + + # Return a list of integers from `first' up to and including `last'. + range = first: last: + if builtins.lessThan last first + then [] + else [first] ++ range (builtins.add first 1) last; + + + # Partition the elements of a list in two lists, `right' and + # `wrong', depending on the evaluation of a predicate. + partition = pred: + fold (h: t: + if pred h + then { right = [h] ++ t.right; wrong = t.wrong; } + else { right = t.right; wrong = [h] ++ t.wrong; } + ) { right = []; wrong = []; }; + + + zipListsWith = f: fst: snd: + let + len1 = length fst; + len2 = length snd; + len = if builtins.lessThan len1 len2 then len1 else len2; + zipListsWith' = n: + if n != len then + [ (f (elemAt fst n) (elemAt snd n)) ] + ++ zipListsWith' (inc n) + else []; + in zipListsWith' 0; + + zipLists = zipListsWith (fst: snd: { inherit fst snd; }); + + + # Reverse the order of the elements of a list. + reverseList = fold (e: acc: acc ++ [ e ]) []; + + # Sort a list based on a comparator function which compares two + # elements and returns true if the first argument is strictly below + # the second argument. The returned list is sorted in an increasing + # order. The implementation does a quick-sort. + sort = strictLess: list: + let + len = length list; + first = head list; + pivot' = n: acc@{ left, right }: let el = elemAt list n; next = pivot' (inc n); in + if n == len + then acc + else if strictLess first el + then next { inherit left; right = [ el ] ++ right; } + else + next { left = [ el ] ++ left; inherit right; }; + pivot = pivot' 1 { left = []; right = []; }; + in + if lessThan len 2 then list + else (sort strictLess pivot.left) ++ [ first ] ++ (sort strictLess pivot.right); + + + # Return the first (at most) N elements of a list. + take = count: list: + let + len = length list; + take' = n: + if n == len || n == count + then [] + else + [ (elemAt list n) ] ++ take' (inc n); + in take' 0; + + + # Remove the first (at most) N elements of a list. + drop = count: list: + let + len = length list; + drop' = n: + if n == minus1 || lessThan n count + then [] + else + drop' (dec n) ++ [ (elemAt list n) ]; + in drop' (dec len); + + + last = list: + assert list != []; elemAt list (dec (length list)); + + + # Zip two lists together. + zipTwoLists = xs: ys: + let + len1 = length xs; + len2 = length ys; + len = if lessThan len1 len2 then len1 else len2; + zipTwoLists' = n: + if n != len then + [ { first = elemAt xs n; second = elemAt ys n; } ] + ++ zipTwoLists' (inc n) + else []; + in zipTwoLists' 0; + + deepSeqList = xs: y: if any (x: deepSeq x false) xs then y else y; +} diff --git a/lib/maintainers.nix b/lib/maintainers.nix new file mode 100644 index 000000000000..06c71b2b7ac8 --- /dev/null +++ b/lib/maintainers.nix @@ -0,0 +1,65 @@ +/* -*- coding: utf-8; -*- */ + +{ + /* Add your name and email address here. Keep the list + alphabetically sorted. */ + + aforemny = "Alexander Foremny <alexanderforemny@googlemail.com>"; + algorith = "Dries Van Daele <dries_van_daele@telenet.be>"; + all = "Nix Committers <nix-commits@lists.science.uu.nl>"; + amiddelk = "Arie Middelkoop <amiddelk@gmail.com>"; + amorsillo = "Andrew Morsillo <andrew.morsillo@gmail.com>"; + andres = "Andres Loeh <ksnixos@andres-loeh.de>"; + antono = "Antono Vasiljev <self@antono.info>"; + astsmtl = "Alexander Tsamutali <astsmtl@yandex.ru>"; + aszlig = "aszlig <aszlig@redmoonstudios.org>"; + bbenoist = "Baptist BENOIST <return_0@live.com>"; + bjg = "Brian Gough <bjg@gnu.org>"; + bjornfor = "Bjørn Forsman <bjorn.forsman@gmail.com>"; + bluescreen303 = "Mathijs Kwik <mathijs@bluescreen303.nl>"; + bodil = "Bodil Stokke <nix@bodil.org>"; + chaoflow = "Florian Friesdorf <flo@chaoflow.net>"; + coconnor = "Corey O'Connor <coreyoconnor@gmail.com>"; + coroa = "Jonas Hörsch <jonas@chaoflow.net>"; + edwtjo = "Edward Tjörnhammar <ed@cflags.cc>"; + eelco = "Eelco Dolstra <eelco.dolstra@logicblox.com>"; + ertes = "Ertugrul Söylemez <es@ertes.de>"; + garbas = "Rok Garbas <rok@garbas.si>"; + goibhniu = "Cillian de Róiste <cillian.deroiste@gmail.com>"; + guibert = "David Guibert <david.guibert@gmail.com>"; + iElectric = "Domen Kozar <domen@dev.si>"; + iyzsong = "Song Wenwu <iyzsong@gmail.com>"; + jcumming = "Jack Cummings <jack@mudshark.org>"; + kkallio = "Karn Kallio <tierpluspluslists@gmail.com>"; + lovek323 = "Jason O'Conal <jason@oconal.id.au>"; + ludo = "Ludovic Courtès <ludo@gnu.org>"; + marcweber = "Marc Weber <marco-oweber@gmx.de>"; + modulistic = "Pablo Costa <modulistic@gmail.com>"; + mornfall = "Petr Ročkai <me@mornfall.net>"; + ocharles = "Oliver Charles <ollie@ocharles.org.uk>"; + offline = "Jaka Hudoklin <jakahudoklin@gmail.com>"; + orbitz = "Malcolm Matalka <mmatalka@gmail.com>"; + page = "Carles Pagès <page@cubata.homelinux.net>"; + phreedom = "Evgeny Egorochkin <phreedom@yandex.ru>"; + pierron = "Nicolas B. Pierron <nixos@nbp.name>"; + piotr = "Piotr Pietraszkiewicz <ppietrasa@gmail.com>"; + pSub = "Pascal Wittmann <mail@pascal-wittmann.de>"; + qknight = "Joachim Schiele <js@lastlog.de>"; + raskin = "Michael Raskin <7c6f434c@mail.ru>"; + rickynils = "Rickard Nilsson <rickynils@gmail.com>"; + rob = "Rob Vermaas <rob.vermaas@gmail.com>"; + roconnor = "Russell O'Connor <roconnor@theorem.ca>"; + sander = "Sander van der Burg <s.vanderburg@tudelft.nl>"; + shlevy = "Shea Levy <shea@shealevy.com>"; + simons = "Peter Simons <simons@cryp.to>"; + smironov = "Sergey Mironov <ierton@gmail.com>"; + thammers = "Tobias Hammerschmidt <jawr@gmx.de>"; + the-kenny = "Moritz Ulrich <moritz@tarn-vedra.de>"; + urkud = "Yury G. Kudryashov <urkud+nix@ya.ru>"; + vcunat = "Vladimír Čunát <vcunat@gmail.com>"; + viric = "Lluís Batlle i Rossell <viric@viric.name>"; + vizanto = "Danny Wilson <danny@prime.vc>"; + winden = "Antonio Vargas Gonzalez <windenntw@gmail.com>"; + z77z = "Marco Maggesi <maggesi@math.unifi.it>"; + zef = "Zef Hemel <zef@zef.me>"; +} diff --git a/lib/meta.nix b/lib/meta.nix new file mode 100644 index 000000000000..a5afce9e0cb1 --- /dev/null +++ b/lib/meta.nix @@ -0,0 +1,48 @@ +/* Some functions for manipulating meta attributes, as well as the + name attribute. */ + +rec { + + + /* Add to or override the meta attributes of the given + derivation. + + Example: + addMetaAttrs {description = "Bla blah";} somePkg + */ + addMetaAttrs = newAttrs: drv: + drv // { meta = (if drv ? meta then drv.meta else {}) // newAttrs; }; + + + /* Change the symbolic name of a package for presentation purposes + (i.e., so that nix-env users can tell them apart). + */ + setName = name: drv: drv // {inherit name;}; + + + /* Like `setName', but takes the previous name as an argument. + + Example: + updateName (oldName: oldName + "-experimental") somePkg + */ + updateName = updater: drv: drv // {name = updater (drv.name);}; + + + /* Append a suffix to the name of a package. !!! the suffix should + really be appended *before* the version, at least most of the + time. + */ + appendToName = suffix: updateName (name: "${name}-${suffix}"); + + + /* Decrease the nix-env priority of the package, i.e., other + versions/variants of the package will be preferred. + */ + lowPrio = drv: addMetaAttrs { priority = "10"; } drv; + + /* Increase the nix-env priority of the package, i.e., this + version/variant of the package will be preferred. + */ + hiPrio = drv: addMetaAttrs { priority = "-10"; } drv; + +} diff --git a/lib/misc.nix b/lib/misc.nix new file mode 100644 index 000000000000..19e5081009de --- /dev/null +++ b/lib/misc.nix @@ -0,0 +1,431 @@ +let lib = import ./default.nix; + inherit (builtins) isFunction hasAttr getAttr head tail isList isAttrs isInt attrNames; + +in + +with import ./lists.nix; +with import ./attrsets.nix; +with import ./strings.nix; + +rec { + + # returns default if env var is not set + maybeEnv = name: default: + let value = builtins.getEnv name; in + if value == "" then default else value; + + defaultMergeArg = x : y: if builtins.isAttrs y then + y + else + (y x); + defaultMerge = x: y: x // (defaultMergeArg x y); + foldArgs = merger: f: init: x: + let arg=(merger init (defaultMergeArg init x)); + # now add the function with composed args already applied to the final attrs + base = (setAttrMerge "passthru" {} (f arg) + ( z : z // rec { + function = foldArgs merger f arg; + args = (lib.attrByPath ["passthru" "args"] {} z) // x; + } )); + withStdOverrides = base // { + override = base.passthru.function; + deepOverride = a : (base.passthru.function ((lib.mapAttrs (lib.deepOverrider a) base.passthru.args) // a)); + } ; + in + withStdOverrides; + + + # predecessors: proposed replacement for applyAndFun (which has a bug cause it merges twice) + # the naming "overridableDelayableArgs" tries to express that you can + # - override attr values which have been supplied earlier + # - use attr values before they have been supplied by accessing the fix point + # name "fixed" + # f: the (delayed overridden) arguments are applied to this + # + # initial: initial attrs arguments and settings. see defaultOverridableDelayableArgs + # + # returns: f applied to the arguments // special attributes attrs + # a) merge: merge applied args with new args. Wether an argument is overridden depends on the merge settings + # b) replace: this let's you replace and remove names no matter which merge function has been set + # + # examples: see test cases "res" below; + overridableDelayableArgs = + f : # the function applied to the arguments + initial : # you pass attrs, the functions below are passing a function taking the fix argument + let + takeFixed = if isFunction initial then initial else (fixed : initial); # transform initial to an expression always taking the fixed argument + tidy = args : + let # apply all functions given in "applyPreTidy" in sequence + applyPreTidyFun = fold ( n : a : x : n ( a x ) ) lib.id (maybeAttr "applyPreTidy" [] args); + in removeAttrs (applyPreTidyFun args) ( ["applyPreTidy"] ++ (maybeAttr "removeAttrs" [] args) ); # tidy up args before applying them + fun = n : x : + let newArgs = fixed : + let args = takeFixed fixed; + mergeFun = getAttr n args; + in if isAttrs x then (mergeFun args x) + else assert isFunction x; + mergeFun args (x ( args // { inherit fixed; })); + in overridableDelayableArgs f newArgs; + in + (f (tidy (lib.fix takeFixed))) // { + merge = fun "mergeFun"; + replace = fun "keepFun"; + }; + defaultOverridableDelayableArgs = f : + let defaults = { + mergeFun = mergeAttrByFunc; # default merge function. merge strategie (concatenate lists, strings) is given by mergeAttrBy + keepFun = a : b : { inherit (a) removeAttrs mergeFun keepFun mergeAttrBy; } // b; # even when using replace preserve these values + applyPreTidy = []; # list of functions applied to args before args are tidied up (usage case : prepareDerivationArgs) + mergeAttrBy = mergeAttrBy // { + applyPreTidy = a : b : a ++ b; + removeAttrs = a : b: a ++ b; + }; + removeAttrs = ["mergeFun" "keepFun" "mergeAttrBy" "removeAttrs" "fixed" ]; # before applying the arguments to the function make sure these names are gone + }; + in (overridableDelayableArgs f defaults).merge; + + + + # rec { # an example of how composedArgsAndFun can be used + # a = composedArgsAndFun (x : x) { a = ["2"]; meta = { d = "bar";}; }; + # # meta.d will be lost ! It's your task to preserve it (eg using a merge function) + # b = a.passthru.function { a = [ "3" ]; meta = { d2 = "bar2";}; }; + # # instead of passing/ overriding values you can use a merge function: + # c = b.passthru.function ( x: { a = x.a ++ ["4"]; }); # consider using (maybeAttr "a" [] x) + # } + # result: + # { + # a = { a = ["2"]; meta = { d = "bar"; }; passthru = { function = .. }; }; + # b = { a = ["3"]; meta = { d2 = "bar2"; }; passthru = { function = .. }; }; + # c = { a = ["3" "4"]; meta = { d2 = "bar2"; }; passthru = { function = .. }; }; + # # c2 is equal to c + # } + composedArgsAndFun = f: foldArgs defaultMerge f {}; + + + # shortcut for attrByPath ["name"] default attrs + maybeAttrNullable = name: default: attrs: + if attrs == null then default else + if __hasAttr name attrs then (__getAttr name attrs) else default; + + # shortcut for attrByPath ["name"] default attrs + maybeAttr = name: default: attrs: + if __hasAttr name attrs then (__getAttr name attrs) else default; + + + # Return the second argument if the first one is true or the empty version + # of the second argument. + ifEnable = cond: val: + if cond then val + else if builtins.isList val then [] + else if builtins.isAttrs val then {} + # else if builtins.isString val then "" + else if val == true || val == false then false + else null; + + + # Return true only if there is an attribute and it is true. + checkFlag = attrSet: name: + if name == "true" then true else + if name == "false" then false else + if (elem name (attrByPath ["flags"] [] attrSet)) then true else + attrByPath [name] false attrSet ; + + + # Input : attrSet, [ [name default] ... ], name + # Output : its value or default. + getValue = attrSet: argList: name: + ( attrByPath [name] (if checkFlag attrSet name then true else + if argList == [] then null else + let x = builtins.head argList; in + if (head x) == name then + (head (tail x)) + else (getValue attrSet + (tail argList) name)) attrSet ); + + + # Input : attrSet, [[name default] ...], [ [flagname reqs..] ... ] + # Output : are reqs satisfied? It's asserted. + checkReqs = attrSet : argList : condList : + ( + fold lib.and true + (map (x: let name = (head x) ; in + + ((checkFlag attrSet name) -> + (fold lib.and true + (map (y: let val=(getValue attrSet argList y); in + (val!=null) && (val!=false)) + (tail x))))) condList)) ; + + + # This function has O(n^2) performance. + uniqList = {inputList, acc ? []} : + let go = xs : acc : + if xs == [] + then [] + else let x = head xs; + y = if elem x acc then [] else [x]; + in y ++ go (tail xs) (y ++ acc); + in go inputList acc; + + uniqListExt = {inputList, outputList ? [], + getter ? (x : x), compare ? (x: y: x==y)}: + if inputList == [] then outputList else + let x=head inputList; + isX = y: (compare (getter y) (getter x)); + newOutputList = outputList ++ + (if any isX outputList then [] else [x]); + in uniqListExt {outputList=newOutputList; + inputList = (tail inputList); + inherit getter compare; + }; + + + + condConcat = name: list: checker: + if list == [] then name else + if checker (head list) then + condConcat + (name + (head (tail list))) + (tail (tail list)) + checker + else condConcat + name (tail (tail list)) checker; + + lazyGenericClosure = {startSet, operator}: + let + work = list: doneKeys: result: + if list == [] then + result + else + let x = head list; key = x.key; in + if elem key doneKeys then + work (tail list) doneKeys result + else + work (tail list ++ operator x) ([key] ++ doneKeys) ([x] ++ result); + in + work startSet [] []; + + genericClosure = + if builtins ? genericClosure then builtins.genericClosure + else lazyGenericClosure; + + innerModifySumArgs = f: x: a: b: if b == null then (f a b) // x else + innerModifySumArgs f x (a // b); + modifySumArgs = f: x: innerModifySumArgs f x {}; + + + innerClosePropagation = acc : xs : + if xs == [] + then acc + else let y = head xs; + ys = tail xs; + in if ! isAttrs y + then innerClosePropagation acc ys + else let acc' = [y] ++ acc; + in innerClosePropagation + acc' + (uniqList { inputList = (maybeAttrNullable "propagatedBuildInputs" [] y) + ++ (maybeAttrNullable "propagatedNativeBuildInputs" [] y) + ++ ys; + acc = acc'; + } + ); + + closePropagation = list: (uniqList {inputList = (innerClosePropagation [] list);}); + + # calls a function (f attr value ) for each record item. returns a list + mapAttrsFlatten = f : r : map (attr: f attr (builtins.getAttr attr r) ) (attrNames r); + + # attribute set containing one attribute + nvs = name : value : listToAttrs [ (nameValuePair name value) ]; + # adds / replaces an attribute of an attribute set + setAttr = set : name : v : set // (nvs name v); + + # setAttrMerge (similar to mergeAttrsWithFunc but only merges the values of a particular name) + # setAttrMerge "a" [] { a = [2];} (x : x ++ [3]) -> { a = [2 3]; } + # setAttrMerge "a" [] { } (x : x ++ [3]) -> { a = [ 3]; } + setAttrMerge = name : default : attrs : f : + setAttr attrs name (f (maybeAttr name default attrs)); + + # Using f = a : b = b the result is similar to // + # merge attributes with custom function handling the case that the attribute + # exists in both sets + mergeAttrsWithFunc = f : set1 : set2 : + fold (n: set : if (__hasAttr n set) + then setAttr set n (f (__getAttr n set) (__getAttr n set2)) + else set ) + (set2 // set1) (__attrNames set2); + + # merging two attribute set concatenating the values of same attribute names + # eg { a = 7; } { a = [ 2 3 ]; } becomes { a = [ 7 2 3 ]; } + mergeAttrsConcatenateValues = mergeAttrsWithFunc ( a : b : (toList a) ++ (toList b) ); + + # merges attributes using //, if a name exisits in both attributes + # an error will be triggered unless its listed in mergeLists + # so you can mergeAttrsNoOverride { buildInputs = [a]; } { buildInputs = [a]; } {} to get + # { buildInputs = [a b]; } + # merging buildPhase does'nt really make sense. The cases will be rare where appending /prefixing will fit your needs? + # in these cases the first buildPhase will override the second one + # ! deprecated, use mergeAttrByFunc instead + mergeAttrsNoOverride = { mergeLists ? ["buildInputs" "propagatedBuildInputs"], + overrideSnd ? [ "buildPhase" ] + } : attrs1 : attrs2 : + fold (n: set : + setAttr set n ( if (__hasAttr n set) + then # merge + if elem n mergeLists # attribute contains list, merge them by concatenating + then (__getAttr n attrs2) ++ (__getAttr n attrs1) + else if elem n overrideSnd + then __getAttr n attrs1 + else throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined" + else __getAttr n attrs2 # add attribute not existing in attr1 + )) attrs1 (__attrNames attrs2); + + + # example usage: + # mergeAttrByFunc { + # inherit mergeAttrBy; # defined below + # buildInputs = [ a b ]; + # } { + # buildInputs = [ c d ]; + # }; + # will result in + # { mergeAttrsBy = [...]; buildInputs = [ a b c d ]; } + # is used by prepareDerivationArgs, defaultOverridableDelayableArgs and can be used when composing using + # foldArgs, composedArgsAndFun or applyAndFun. Example: composableDerivation in all-packages.nix + mergeAttrByFunc = x : y : + let + mergeAttrBy2 = { mergeAttrBy=lib.mergeAttrs; } + // (maybeAttr "mergeAttrBy" {} x) + // (maybeAttr "mergeAttrBy" {} y); in + fold lib.mergeAttrs {} [ + x y + (mapAttrs ( a : v : # merge special names using given functions + if (hasAttr a x) + then if (hasAttr a y) + then v (getAttr a x) (getAttr a y) # both have attr, use merge func + else (getAttr a x) # only x has attr + else (getAttr a y) # only y has attr) + ) (removeAttrs mergeAttrBy2 + # don't merge attrs which are neither in x nor y + (filter (a : (! hasAttr a x) && (! hasAttr a y) ) + (attrNames mergeAttrBy2)) + ) + ) + ]; + mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; }; + mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) ["mergeAttrBy"]; + + # merge attrs based on version key into mkDerivation args, see mergeAttrBy to learn about smart merge defaults + # + # This function is best explained by an example: + # + # {version ? "2.x"} : + # + # mkDerivation (mergeAttrsByVersion "package-name" version + # { # version specific settings + # "git" = { src = ..; preConfigre = "autogen.sh"; buildInputs = [automake autoconf libtool]; }; + # "2.x" = { src = ..; }; + # } + # { // shared settings + # buildInputs = [ common build inputs ]; + # meta = { .. } + # } + # ) + # + # Please note that e.g. Eelco Dolstra usually prefers having one file for + # each version. On the other hand there are valuable additional design goals + # - readability + # - do it once only + # - try to avoid duplication + # + # Marc Weber and Michael Raskin sometimes prefer keeping older + # versions around for testing and regression tests - as long as its cheap to + # do so. + # + # Very often it just happens that the "shared" code is the bigger part. + # Then using this function might be appropriate. + # + # Be aware that its easy to cause recompilations in all versions when using + # this function - also if derivations get too complex splitting into multiple + # files is the way to go. + # + # See misc.nix -> versionedDerivation + # discussion: nixpkgs: pull/310 + mergeAttrsByVersion = name: version: attrsByVersion: base: + mergeAttrsByFuncDefaultsClean [ { name = "${name}-${version}"; } base (maybeAttr version (throw "bad version ${version} for ${name}") attrsByVersion)]; + + # sane defaults (same name as attr name so that inherit can be used) + mergeAttrBy = # { buildInputs = concatList; [...]; passthru = mergeAttr; [..]; } + listToAttrs (map (n : nameValuePair n lib.concat) + [ "nativeBuildInputs" "buildInputs" "propagatedBuildInputs" "configureFlags" "prePhases" "postAll" "patches" ]) + // listToAttrs (map (n : nameValuePair n lib.mergeAttrs) [ "passthru" "meta" "cfg" "flags" ]) + // listToAttrs (map (n : nameValuePair n (a: b: "${a}\n${b}") ) [ "preConfigure" "postInstall" ]) + ; + + # prepareDerivationArgs tries to make writing configurable derivations easier + # example: + # prepareDerivationArgs { + # mergeAttrBy = { + # myScript = x : y : x ++ "\n" ++ y; + # }; + # cfg = { + # readlineSupport = true; + # }; + # flags = { + # readline = { + # set = { + # configureFlags = [ "--with-compiler=${compiler}" ]; + # buildInputs = [ compiler ]; + # pass = { inherit compiler; READLINE=1; }; + # assertion = compiler.dllSupport; + # myScript = "foo"; + # }; + # unset = { configureFlags = ["--without-compiler"]; }; + # }; + # }; + # src = ... + # buildPhase = '' ... ''; + # name = ... + # myScript = "bar"; + # }; + # if you don't have need for unset you can omit the surrounding set = { .. } attr + # all attrs except flags cfg and mergeAttrBy will be merged with the + # additional data from flags depending on config settings + # It's used in composableDerivation in all-packages.nix. It's also used + # heavily in the new python and libs implementation + # + # should we check for misspelled cfg options? + # TODO use args.mergeFun here as well? + prepareDerivationArgs = args: + let args2 = { cfg = {}; flags = {}; } // args; + flagName = name : "${name}Support"; + cfgWithDefaults = (listToAttrs (map (n : nameValuePair (flagName n) false) (attrNames args2.flags))) + // args2.cfg; + opts = attrValues (mapAttrs (a : v : + let v2 = if v ? set || v ? unset then v else { set = v; }; + n = if (getAttr (flagName a) cfgWithDefaults) then "set" else "unset"; + attr = maybeAttr n {} v2; in + if (maybeAttr "assertion" true attr) + then attr + else throw "assertion of flag ${a} of derivation ${args.name} failed" + ) args2.flags ); + in removeAttrs + (mergeAttrsByFuncDefaults ([args] ++ opts ++ [{ passthru = cfgWithDefaults; }])) + ["flags" "cfg" "mergeAttrBy" ]; + + + nixType = x: + if isAttrs x then + if x ? outPath then "derivation" + else "aattrs" + else if isFunction x then "function" + else if isList x then "list" + else if x == true then "bool" + else if x == false then "bool" + else if x == null then "null" + else if isInt x then "int" + else "string"; + +} diff --git a/lib/modules.nix b/lib/modules.nix new file mode 100644 index 000000000000..acd10e7bf576 --- /dev/null +++ b/lib/modules.nix @@ -0,0 +1,380 @@ +# NixOS module handling. + +let lib = import ./default.nix; in + +with { inherit (builtins) head; }; +with import ./trivial.nix; +with import ./lists.nix; +with import ./misc.nix; +with import ./attrsets.nix; +with import ./options.nix; +with import ./properties.nix; + +rec { + + # Unfortunately this can also be a string. + isPath = x: !( + builtins.isFunction x + || builtins.isAttrs x + || builtins.isInt x + || builtins.isBool x + || builtins.isList x + ); + + + importIfPath = path: + if isPath path then + import path + else + path; + + + applyIfFunction = f: arg: + if builtins.isFunction f then + f arg + else + f; + + + isModule = m: + (m ? config && isAttrs m.config && ! isOption m.config) + || (m ? options && isAttrs m.options && ! isOption m.options); + + + # Convert module to a set which has imports / options and config + # attributes. + unifyModuleSyntax = m: + let + delayedModule = delayProperties m; + + getImports = + toList (rmProperties (delayedModule.require or [])); + getImportedPaths = filter isPath getImports; + getImportedSets = filter (x: !isPath x) getImports; + + getConfig = + removeAttrs delayedModule ["require" "key" "imports"]; + + in + if isModule m then + { key = "<unknown location>"; } // m + else + { key = "<unknown location>"; + imports = (m.imports or []) ++ getImportedPaths; + config = getConfig; + } // ( + if getImportedSets != [] then + assert length getImportedSets == 1; + { options = head getImportedSets; } + else + {} + ); + + + unifyOptionModule = {key ? "<unknown location>"}: name: index: m: (args: + let + module = lib.applyIfFunction m args; + key_ = rec { + file = key; + option = name; + number = index; + outPath = key; + }; + in if lib.isModule module then + { key = key_; } // module + else + { key = key_; options = module; } + ); + + + moduleClosure = initModules: args: + let + moduleImport = origin: index: m: + let m' = applyIfFunction (importIfPath m) args; + in (unifyModuleSyntax m') // { + # used by generic closure to avoid duplicated imports. + key = + if isPath m then m + else m'.key or (newModuleName origin index); + }; + + getImports = m: m.imports or []; + + newModuleName = origin: index: + "${origin.key}:<import-${toString index}>"; + + topLevel = { + key = "<top-level>"; + }; + + in + (lazyGenericClosure { + startSet = imap (moduleImport topLevel) initModules; + operator = m: imap (moduleImport m) (getImports m); + }); + + + moduleApply = funs: module: + lib.mapAttrs (name: value: + if builtins.hasAttr name funs then + let fun = lib.getAttr name funs; in + fun value + else + value + ) module; + + + # Handle mkMerge function left behind after a delay property. + moduleFlattenMerge = module: + if module ? config && + isProperty module.config && + isMerge module.config.property + then + (map (cfg: { key = module.key; config = cfg; }) module.config.content) + ++ [ (module // { config = {}; }) ] + else + [ module ]; + + + # Handle mkMerge attributes which are left behind by previous delay + # properties and convert them into a list of modules. Delay properties + # inside the config attribute of a module and create a second module if a + # mkMerge attribute was left behind. + # + # Module -> [ Module ] + delayModule = module: + map (moduleApply { config = delayProperties; }) (moduleFlattenMerge module); + + + evalDefinitions = opt: values: + if opt.type.delayOnGlobalEval or false then + map (delayPropertiesWithIter opt.type.iter opt.name) + (evalLocalProperties values) + else + evalProperties values; + + + selectModule = name: m: + { inherit (m) key; + } // ( + if m ? options && builtins.hasAttr name m.options then + { options = lib.getAttr name m.options; } + else {} + ) // ( + if m ? config && builtins.hasAttr name m.config then + { config = lib.getAttr name m.config; } + else {} + ); + + filterModules = name: modules: + filter (m: m ? config || m ? options) ( + map (selectModule name) modules + ); + + + modulesNames = modules: + lib.concatMap (m: [] + ++ optionals (m ? options) (lib.attrNames m.options) + ++ optionals (m ? config) (lib.attrNames m.config) + ) modules; + + + moduleZip = funs: modules: + lib.mapAttrs (name: fun: + fun (catAttrs name modules) + ) funs; + + + moduleMerge = path: modules: + let modules_ = modules; in + let + addName = name: + if path == "" then name else path + "." + name; + + modules = concatLists (map delayModule modules_); + + modulesOf = name: filterModules name modules; + declarationsOf = name: filter (m: m ? options) (modulesOf name); + definitionsOf = name: filter (m: m ? config ) (modulesOf name); + + recurseInto = name: + moduleMerge (addName name) (modulesOf name); + + recurseForOption = name: modules: args: + moduleMerge name ( + moduleClosure modules args + ); + + errorSource = modules: + "The error may come from the following files:\n" + ( + lib.concatStringsSep "\n" ( + map (m: + if m ? key then toString m.key else "<unknown location>" + ) modules + ) + ); + + eol = "\n"; + + allNames = modulesNames modules; + + getResults = m: + let fetchResult = s: mapAttrs (n: v: v.result) s; in { + options = fetchResult m.options; + config = fetchResult m.config; + }; + + endRecursion = { options = {}; config = {}; }; + + in if modules == [] then endRecursion else + getResults (fix (crossResults: moduleZip { + options = lib.zipWithNames allNames (name: values: rec { + config = lib.getAttr name crossResults.config; + + declarations = declarationsOf name; + declarationSources = + map (m: { + source = m.key; + }) declarations; + + hasOptions = values != []; + isOption = any lib.isOption values; + + decls = # add location to sub-module options. + map (m: + mapSubOptions + (unifyOptionModule {inherit (m) key;} name) + m.options + ) declarations; + + decl = + lib.addErrorContext "${eol + }while enhancing option `${addName name}':${eol + }${errorSource declarations}${eol + }" ( + addOptionMakeUp + { name = addName name; recurseInto = recurseForOption; } + (mergeOptionDecls decls) + ); + + value = decl // (with config; { + inherit (config) isNotDefined; + isDefined = ! isNotDefined; + declarations = declarationSources; + definitions = definitionSources; + config = strictResult; + }); + + recurse = (recurseInto name).options; + + result = + if isOption then value + else if !hasOptions then {} + else if all isAttrs values then recurse + else + throw "${eol + }Unexpected type where option declarations are expected.${eol + }${errorSource declarations}${eol + }"; + + }); + + config = lib.zipWithNames allNames (name: values_: rec { + option = lib.getAttr name crossResults.options; + + definitions = definitionsOf name; + definitionSources = + map (m: { + source = m.key; + value = m.config; + }) definitions; + + values = values_ ++ + optionals (option.isOption && option.decl ? extraConfigs) + option.decl.extraConfigs; + + defs = evalDefinitions option.decl values; + + isNotDefined = defs == []; + + value = + lib.addErrorContext "${eol + }while evaluating the option `${addName name}':${eol + }${errorSource (modulesOf name)}${eol + }" ( + let opt = option.decl; in + opt.apply ( + if isNotDefined then + opt.default or (throw "Option `${addName name}' not defined and does not have a default value.") + else opt.merge defs + ) + ); + + strictResult = builtins.tryEval (builtins.toXML value); + + recurse = (recurseInto name).config; + + configIsAnOption = v: isOption (rmProperties v); + errConfigIsAnOption = + let badModules = filter (m: configIsAnOption m.config) definitions; in + "${eol + }Option ${addName name} is defined in the configuration section.${eol + }${errorSource badModules}${eol + }"; + + errDefinedWithoutDeclaration = + let badModules = definitions; in + "${eol + }Option '${addName name}' defined without option declaration.${eol + }${errorSource badModules}${eol + }"; + + result = + if option.isOption then value + else if !option.hasOptions then throw errDefinedWithoutDeclaration + else if any configIsAnOption values then throw errConfigIsAnOption + else if all isAttrs values then recurse + # plain value during the traversal + else throw errDefinedWithoutDeclaration; + + }); + } modules)); + + + fixMergeModules = initModules: {...}@args: + lib.fix (result: + # This trick avoids an infinite loop because names of attribute + # are know and it is not required to evaluate the result of + # moduleMerge to know which attributes are present as arguments. + let module = { inherit (result) options config; }; in + moduleMerge "" ( + moduleClosure initModules (module // args) + ) + ); + + + # Visit all definitions to raise errors related to undeclared options. + checkModule = path: {config, options, ...}@m: + let + eol = "\n"; + addName = name: + if path == "" then name else path + "." + name; + in + if lib.isOption options then + if options ? options then + options.type.fold + (cfg: res: res && checkModule (options.type.docPath path) cfg._args) + true config + else + true + else if isAttrs options && lib.attrNames m.options != [] then + all (name: + lib.addErrorContext "${eol + }while checking the attribute `${addName name}':${eol + }" (checkModule (addName name) (selectModule name m)) + ) (lib.attrNames m.config) + else + builtins.trace "try to evaluate config ${lib.showVal config}." + false; + +} diff --git a/lib/options.nix b/lib/options.nix new file mode 100644 index 000000000000..e8e01083a77a --- /dev/null +++ b/lib/options.nix @@ -0,0 +1,315 @@ +# Nixpkgs/NixOS option handling. + +let lib = import ./default.nix; in + +with { inherit (builtins) head length; }; +with import ./trivial.nix; +with import ./lists.nix; +with import ./misc.nix; +with import ./attrsets.nix; +with import ./properties.nix; + +rec { + + inherit (lib) isType; + + + isOption = isType "option"; + mkOption = attrs: attrs // { + _type = "option"; + # name (this is the name of the attributem it is automatically generated by the traversal) + # default (value used when no definition exists) + # example (documentation) + # description (documentation) + # type (option type, provide a default merge function and ensure type correctness) + # merge (function used to merge definitions into one definition: [ /type/ ] -> /type/) + # apply (convert the option value to ease the manipulation of the option result) + # options (set of sub-options declarations & definitions) + # extraConfigs (list of possible configurations) + }; + + mkEnableOption = name: mkOption { + default = false; + example = true; + description = "Whether to enable ${name}"; + type = lib.types.bool; + }; + + mapSubOptions = f: opt: + if opt ? options then + opt // { + options = imap f (toList opt.options); + } + else + opt; + + # Make the option declaration more user-friendly by adding default + # settings and some verifications based on the declaration content (like + # type correctness). + addOptionMakeUp = {name, recurseInto}: decl: + let + init = { + inherit name; + merge = mergeDefaultOption; + apply = lib.id; + }; + + functionsFromType = opt: + opt // (builtins.intersectAttrs { merge = 1; check = 1; } (decl.type or {})); + + addDeclaration = opt: opt // decl; + + ensureMergeInputType = opt: + if opt ? check then + opt // { + merge = list: + if all opt.check list then + opt.merge list + else + throw "One of option ${name} values has a bad type."; + } + else opt; + + checkDefault = opt: + if opt ? check && opt ? default then + opt // { + default = + if opt.check opt.default then + opt.default + else + throw "The default value of option ${name} has a bad type."; + } + else opt; + + handleOptionSets = opt: + if opt ? type && opt.type.hasOptions then + let + # Evaluate sub-modules. + subModuleMerge = path: vals: + lib.fix (args: + let + result = recurseInto path (opt.options ++ imap (index: v: args: { + key = rec { + #!!! Would be nice if we had the file the val was from + option = path; + number = index; + outPath = "option ${option} config number ${toString number}"; + }; + } // (lib.applyIfFunction v args)) (toList vals)) args; + name = lib.removePrefix (opt.name + ".") path; + extraArgs = opt.extraArgs or {}; + individualExtraArgs = opt.individualExtraArgs or {}; + in { + inherit (result) config options; + inherit name; + } // + (opt.extraArgs or {}) // + (if hasAttr name individualExtraArgs then getAttr name individualExtraArgs else {}) + ); + + # Add _options in sub-modules to make it viewable from other + # modules. + subModuleMergeConfig = path: vals: + let result = subModuleMerge path vals; in + { _args = result; } // result.config; + + in + opt // { + merge = list: + opt.type.iter + subModuleMergeConfig + opt.name + (opt.merge list); + options = + let path = opt.type.docPath opt.name; in + (subModuleMerge path []).options; + } + else + opt; + in + foldl (opt: f: f opt) init [ + # default settings + functionsFromType + + # user settings + addDeclaration + + # override settings + ensureMergeInputType + checkDefault + handleOptionSets + ]; + + # Merge a list of options containning different field. This is useful to + # separate the merge & apply fields from the interface. + mergeOptionDecls = opts: + if opts == [] then {} + else if length opts == 1 then + let opt = head opts; in + if opt ? options then + opt // { options = toList opt.options; } + else + opt + else + fold (opt1: opt2: + lib.addErrorContext "opt1 = ${lib.showVal opt1}\nopt2 = ${lib.showVal opt2}" ( + # You cannot merge if two options have the same field. + assert opt1 ? default -> ! opt2 ? default; + assert opt1 ? example -> ! opt2 ? example; + assert opt1 ? description -> ! opt2 ? description; + assert opt1 ? merge -> ! opt2 ? merge; + assert opt1 ? apply -> ! opt2 ? apply; + assert opt1 ? type -> ! opt2 ? type; + opt1 // opt2 + // optionalAttrs (opt1 ? options || opt2 ? options) { + options = + (toList (opt1.options or [])) + ++ (toList (opt2.options or [])); + } + // optionalAttrs (opt1 ? extraConfigs || opt2 ? extraConfigs) { + extraConfigs = opt1.extraConfigs or [] ++ opt2.extraConfigs or []; + } + // optionalAttrs (opt1 ? extraArgs || opt2 ? extraArgs) { + extraArgs = opt1.extraArgs or {} // opt2.extraArgs or {}; + } + // optionalAttrs (opt1 ? individualExtraArgs || opt2 ? individualExtraArgs) { + individualExtraArgs = zipAttrsWith (name: values: + if length values == 1 then head values else (head values // (head (tail values))) + ) [ (opt1.individualExtraArgs or {}) (opt2.individualExtraArgs or {}) ]; + } + )) {} opts; + + + # !!! This function will be removed because this can be done with the + # multiple option declarations. + addDefaultOptionValues = defs: opts: opts // + builtins.listToAttrs (map (defName: + { name = defName; + value = + let + defValue = builtins.getAttr defName defs; + optValue = builtins.getAttr defName opts; + in + if isOption defValue + then + # `defValue' is an option. + if hasAttr defName opts + then builtins.getAttr defName opts + else defValue.default + else + # `defValue' is an attribute set containing options. + # So recurse. + if hasAttr defName opts && isAttrs optValue + then addDefaultOptionValues defValue optValue + else addDefaultOptionValues defValue {}; + } + ) (attrNames defs)); + + mergeDefaultOption = list: + if length list == 1 then head list + else if all builtins.isFunction list then x: mergeDefaultOption (map (f: f x) list) + else if all isList list then concatLists list + else if all isAttrs list then fold lib.mergeAttrs {} list + else if all builtins.isBool list then fold lib.or false list + else if all builtins.isString list then lib.concatStrings list + else if all builtins.isInt list && all (x: x == head list) list + then head list + else throw "Cannot merge values."; + + mergeTypedOption = typeName: predicate: merge: list: + if all predicate list then merge list + else throw "Expect a ${typeName}."; + + mergeEnableOption = mergeTypedOption "boolean" + (x: true == x || false == x) (fold lib.or false); + + mergeListOption = mergeTypedOption "list" isList concatLists; + + mergeStringOption = mergeTypedOption "string" + (x: if builtins ? isString then builtins.isString x else x + "") + lib.concatStrings; + + mergeOneOption = list: + if list == [] then abort "This case should never happen." + else if length list != 1 then throw "Multiple definitions. Only one is allowed for this option." + else head list; + + + fixableMergeFun = merge: f: config: + merge ( + # generate the list of option sets. + f config + ); + + fixableMergeModules = merge: initModules: {...}@args: config: + fixableMergeFun merge (config: + lib.moduleClosure initModules (args // { inherit config; }) + ) config; + + + fixableDefinitionsOf = initModules: {...}@args: + fixableMergeModules (modules: (lib.moduleMerge "" modules).config) initModules args; + + fixableDeclarationsOf = initModules: {...}@args: + fixableMergeModules (modules: (lib.moduleMerge "" modules).options) initModules args; + + definitionsOf = initModules: {...}@args: + (lib.fix (module: + fixableMergeModules (lib.moduleMerge "") initModules args module.config + )).config; + + declarationsOf = initModules: {...}@args: + (lib.fix (module: + fixableMergeModules (lib.moduleMerge "") initModules args module.config + )).options; + + + # Generate documentation template from the list of option declaration like + # the set generated with filterOptionSets. + optionAttrSetToDocList = ignore: newOptionAttrSetToDocList; + newOptionAttrSetToDocList = attrs: + let options = collect isOption attrs; in + fold (opt: rest: + let + docOption = { + inherit (opt) name; + description = if opt ? description then opt.description else + throw "Option ${opt.name}: No description."; + + declarations = map (x: toString x.source) opt.declarations; + #definitions = map (x: toString x.source) opt.definitions; + } + // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; } + // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; } + // optionalAttrs (opt ? defaultText) { default = opt.defaultText; }; + + subOptions = + if opt ? options then + newOptionAttrSetToDocList opt.options + else + []; + in + [ docOption ] ++ subOptions ++ rest + ) [] options; + + + /* This function recursively removes all derivation attributes from + `x' except for the `name' attribute. This is to make the + generation of `options.xml' much more efficient: the XML + representation of derivations is very large (on the order of + megabytes) and is not actually used by the manual generator. */ + scrubOptionValue = x: + if isDerivation x then { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; } + else if isList x then map scrubOptionValue x + else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"]) + else x; + + + /* For use in the ‘example’ option attribute. It causes the given + text to be included verbatim in documentation. This is necessary + for example values that are not simple values, e.g., + functions. */ + literalExample = text: { _type = "literalExample"; inherit text; }; + + +} diff --git a/lib/platforms.nix b/lib/platforms.nix new file mode 100644 index 000000000000..8be37d7ed1e7 --- /dev/null +++ b/lib/platforms.nix @@ -0,0 +1,16 @@ +let lists = import ./lists.nix; in + +rec { + gnu = linux; /* ++ hurd ++ kfreebsd ++ ... */ + linux = ["i686-linux" "x86_64-linux" "powerpc-linux" "armv5tel-linux" "armv7l-linux" "mips64el-linux"]; + darwin = ["x86_64-darwin"]; + freebsd = ["i686-freebsd" "x86_64-freebsd" "powerpc-freebsd"]; + openbsd = ["i686-openbsd" "x86_64-openbsd"]; + netbsd = ["i686-netbsd" "x86_64-netbsd"]; + cygwin = ["i686-cygwin"]; + unix = linux ++ darwin ++ freebsd ++ openbsd; + all = linux ++ darwin ++ cygwin ++ freebsd ++ openbsd; + none = []; + allBut = platform: lists.filter (x: platform != x) all; + mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux"]; +} diff --git a/lib/properties.nix b/lib/properties.nix new file mode 100644 index 000000000000..22aa8d891d8a --- /dev/null +++ b/lib/properties.nix @@ -0,0 +1,464 @@ +# Nixpkgs/NixOS properties. Generalize the problem of delayable (not yet +# evaluable) properties like mkIf. + +let lib = import ./default.nix; in + +with { inherit (builtins) head tail; }; +with import ./trivial.nix; +with import ./lists.nix; +with import ./misc.nix; +with import ./attrsets.nix; + +rec { + + inherit (lib) isType; + + # Tell that nothing is defined. When properties are evaluated, this type + # is used to remove an entry. Thus if your property evaluation semantic + # implies that you have to mute the content of an attribute, then your + # property should produce this value. + isNotdef = isType "notdef"; + mkNotdef = {_type = "notdef";}; + + # General property type, it has a property attribute and a content + # attribute. The property attribute refers to an attribute set which + # contains a _type attribute and a list of functions which are used to + # evaluate this property. The content attribute is used to stack properties + # on top of each other. + # + # The optional functions which may be contained in the property attribute + # are: + # - onDelay: run on a copied property. + # - onGlobalDelay: run on all copied properties. + # - onEval: run on an evaluated property. + # - onGlobalEval: run on a list of property stack on top of their values. + isProperty = isType "property"; + mkProperty = p@{property, content, ...}: p // { + _type = "property"; + }; + + # Go through the stack of properties and apply the function `op' on all + # property and call the function `nul' on the final value which is not a + # property. The stack is traversed in reversed order. The `op' function + # should expect a property with a content which have been modified. + # + # Warning: The `op' function expects only one argument in order to avoid + # calls to mkProperties as the argument is already a valid property which + # contains the result of the folding inside the content attribute. + foldProperty = op: nul: attrs: + if isProperty attrs then + op (attrs // { + content = foldProperty op nul attrs.content; + }) + else + nul attrs; + + # Simple function which can be used as the `op' argument of the + # foldProperty function. Properties that you don't want to handle can be + # ignored with the `id' function. `isSearched' is a function which should + # check the type of a property and return a boolean value. `thenFun' and + # `elseFun' are functions which behave as the `op' argument of the + # foldProperty function. + foldFilter = isSearched: thenFun: elseFun: attrs: + if isSearched attrs.property then + thenFun attrs + else + elseFun attrs; + + + # Move properties from the current attribute set to the attribute + # contained in this attribute set. This trigger property handlers called + # `onDelay' and `onGlobalDelay'. + delayPropertiesWithIter = iter: path: attrs: + let cleanAttrs = rmProperties attrs; in + if isProperty attrs then + iter (a: v: + lib.addErrorContext "while moving properties on the attribute `${a}':" ( + triggerPropertiesGlobalDelay a ( + triggerPropertiesDelay a ( + copyProperties attrs v + )))) path cleanAttrs + else + attrs; + + delayProperties = # implicit attrs argument. + let + # mapAttrs except that it also recurse into potential mkMerge + # functions. This may cause a strictness issue because looking the + # type of a string implies evaluating it. + iter = fun: path: value: + lib.mapAttrs (attr: val: + if isProperty val && isMerge val.property then + val // { content = map (fun attr) val.content; } + else + fun attr val + ) value; + in + delayPropertiesWithIter iter ""; + + # Call onDelay functions. + triggerPropertiesDelay = name: attrs: + let + callOnDelay = p@{property, ...}: + if property ? onDelay then + property.onDelay name p + else + p; + in + foldProperty callOnDelay id attrs; + + # Call onGlobalDelay functions. + triggerPropertiesGlobalDelay = name: attrs: + let + globalDelayFuns = uniqListExt { + getter = property: property._type; + inputList = foldProperty (p@{property, content, ...}: + if property ? onGlobalDelay then + [ property ] ++ content + else + content + ) (a: []) attrs; + }; + + callOnGlobalDelay = property: content: + property.onGlobalDelay name content; + in + fold callOnGlobalDelay attrs globalDelayFuns; + + # Expect a list of values which may have properties and return the same + # list of values where all properties have been evaluated and where all + # ignored values are removed. This trigger property handlers called + # `onEval' and `onGlobalEval'. + evalProperties = valList: + if valList != [] then + filter (x: !isNotdef x) ( + triggerPropertiesGlobalEval ( + evalLocalProperties valList + ) + ) + else + valList; + + evalLocalProperties = valList: + filter (x: !isNotdef x) ( + map triggerPropertiesEval valList + ); + + # Call onEval function + triggerPropertiesEval = val: + foldProperty (p@{property, ...}: + if property ? onEval then + property.onEval p + else + p + ) id val; + + # Call onGlobalEval function + triggerPropertiesGlobalEval = valList: + let + globalEvalFuns = uniqListExt { + getter = property: property._type; + inputList = + fold (attrs: list: + foldProperty (p@{property, content, ...}: + if property ? onGlobalEval then + [ property ] ++ content + else + content + ) (a: list) attrs + ) [] valList; + }; + + callOnGlobalEval = property: valList: property.onGlobalEval valList; + in + fold callOnGlobalEval valList globalEvalFuns; + + # Remove all properties on top of a value and return the value. + rmProperties = + foldProperty (p@{content, ...}: content) id; + + # Copy properties defined on a value on another value. + copyProperties = attrs: newAttrs: + foldProperty id (x: newAttrs) attrs; + + /* Merge. */ + + # Create "merge" statement which is skipped by the delayProperty function + # and interpreted by the underlying system using properties (modules). + + # Create a "Merge" property which only contains a condition. + isMerge = isType "merge"; + mkMerge = content: mkProperty { + property = { + _type = "merge"; + onDelay = name: val: throw "mkMerge is not the first of the list of properties."; + onEval = val: throw "mkMerge is not allowed on option definitions."; + }; + inherit content; + }; + + /* If. ThenElse. Always. */ + + # create "if" statement that can be delayed on sets until a "then-else" or + # "always" set is reached. When an always set is reached the condition + # is ignore. + + # Create a "If" property which only contains a condition. + isIf = isType "if"; + mkIf = condition: content: mkProperty { + property = { + _type = "if"; + onGlobalDelay = onIfGlobalDelay; + onEval = onIfEval; + inherit condition; + }; + inherit content; + }; + + mkAssert = assertion: message: content: + mkIf + (if assertion then true else throw "\nFailed assertion: ${message}") + content; + + # Evaluate the "If" statements when either "ThenElse" or "Always" + # statement is encountered. Otherwise it removes multiple If statements and + # replaces them by one "If" statement where the condition is the list of all + # conditions joined with a "and" operation. + onIfGlobalDelay = name: content: + let + # extract if statements and non-if statements and repectively put them + # in the attribute list and attrs. + ifProps = + foldProperty + (foldFilter (p: isIf p) + # then, push the condition inside the list list + (p@{property, content, ...}: + { inherit (content) attrs; + list = [property] ++ content.list; + } + ) + # otherwise, add the propertie. + (p@{property, content, ...}: + { inherit (content) list; + attrs = p // { content = content.attrs; }; + } + ) + ) + (attrs: { list = []; inherit attrs; }) + content; + + # compute the list of if statements. + evalIf = content: condition: list: + if list == [] then + mkIf condition content + else + let p = head list; in + evalIf content (condition && p.condition) (tail list); + in + evalIf ifProps.attrs true ifProps.list; + + # Evaluate the condition of the "If" statement to either get the value or + # to ignore the value. + onIfEval = p@{property, content, ...}: + if property.condition then + content + else + mkNotdef; + + /* mkOverride */ + + # Create an "Override" statement which allow the user to define + # priorities between values. The default priority is 100. The lowest + # priorities are kept. The template argument must reproduce the same + # attribute set hierarchy to override leaves of the hierarchy. + isOverride = isType "override"; + mkOverrideTemplate = priority: template: content: mkProperty { + property = { + _type = "override"; + onDelay = onOverrideDelay; + onGlobalEval = onOverrideGlobalEval; + inherit priority template; + }; + inherit content; + }; + + # Like mkOverrideTemplate, but without the template argument. + mkOverride = priority: content: mkOverrideTemplate priority {} content; + + # Sugar to override the default value of the option by making a new + # default value based on the configuration. + mkDefaultValue = mkOverride 1000; + mkDefault = mkOverride 1000; + mkForce = mkOverride 50; + mkStrict = mkOverride 0; + + # Make the template traversal in function of the property traversal. If + # the template define a non-empty attribute set, then the property is + # copied only on all mentionned attributes inside the template. + # Otherwise, the property is kept on all sub-attribute definitions. + onOverrideDelay = name: p@{property, content, ...}: + let inherit (property) template; in + if isAttrs template && template != {} then + if hasAttr name template then + p // { + property = p.property // { + template = builtins.getAttr name template; + }; + } + # Do not override the attribute \name\ + else + content + # Override values defined inside the attribute \name\. + else + p; + + # Keep values having lowest priority numbers only throwing away those having + # a higher priority assigned. + onOverrideGlobalEval = valList: + let + defaultPrio = 100; + + inherit (builtins) lessThan; + + getPrioVal = + foldProperty + (foldFilter isOverride + (p@{property, content, ...}: + if content ? priority && lessThan content.priority property.priority then + content + else + content // { + inherit (property) priority; + } + ) + (p@{property, content, ...}: + content // { + value = p // { content = content.value; }; + } + ) + ) (value: { inherit value; }); + + addDefaultPrio = x: + if x ? priority then x + else x // { priority = defaultPrio; }; + + prioValList = map (x: addDefaultPrio (getPrioVal x)) valList; + + higherPrio = + if prioValList == [] then + defaultPrio + else + fold (x: min: + if lessThan x.priority min then + x.priority + else + min + ) (head prioValList).priority (tail prioValList); + in + map (x: + if x.priority == higherPrio then + x.value + else + mkNotdef + ) prioValList; + + /* mkOrder */ + + # Order definitions based on there index value. This property is useful + # when the result of the merge function depends on the order on the + # initial list. (e.g. concatStrings) Definitions are ordered based on + # their rank. The lowest ranked definition would be the first to element + # of the list used by the merge function. And the highest ranked + # definition would be the last. Definitions which does not have any rank + # value have the default rank of 100. + isOrder = isType "order"; + mkOrder = rank: content: mkProperty { + property = { + _type = "order"; + onGlobalEval = onOrderGlobalEval; + inherit rank; + }; + inherit content; + }; + + mkHeader = mkOrder 10; + mkFooter = mkOrder 1000; + + # Fetch the rank of each definition (add the default rank is none) and + # sort them based on their ranking. + onOrderGlobalEval = valList: + let + defaultRank = 100; + + inherit (builtins) lessThan; + + getRankVal = + foldProperty + (foldFilter isOrder + (p@{property, content, ...}: + if content ? rank then + content + else + content // { + inherit (property) rank; + } + ) + (p@{property, content, ...}: + content // { + value = p // { content = content.value; }; + } + ) + ) (value: { inherit value; }); + + addDefaultRank = x: + if x ? rank then x + else x // { rank = defaultRank; }; + + rankValList = map (x: addDefaultRank (getRankVal x)) valList; + + cmp = x: y: + builtins.lessThan x.rank y.rank; + in + map (x: x.value) (sort cmp rankValList); + + /* mkFixStrictness */ + + # This is a hack used to restore laziness on some option definitions. + # Some option definitions are evaluated when they are not used. This + # error is caused by the strictness of type checking builtins. Builtins + # like 'isAttrs' are too strict because they have to evaluate their + # arguments to check if the type is correct. This evaluation, cause the + # strictness of properties. + # + # Properties can be stacked on top of each other. The stackability of + # properties on top of the option definition is nice for user manipulation + # but require to check if the content of the property is not another + # property. Such testing implies to verify if this is an attribute set + # and if it possess the type 'property'. (see isProperty & typeOf/isType) + # + # To avoid strict evaluation of option definitions, 'mkFixStrictness' is + # introduced. This property protects an option definition by replacing + # the base of the stack of properties by 'mkNotDef', when this property is + # evaluated it returns the original definition. + # + # This property is useful over any elements which depends on options which + # are raising errors when they get evaluated without the proper settings. + # + # Plain list and attribute set are lazy structures, which means that the + # container gets evaluated but not the content. Thus, using this property + # on top of plain list or attribute set is pointless. + # + # This is a Hack, you should avoid it! + + # This property has a long name because you should avoid it. + isFixStrictness = attrs: (typeOf attrs) == "fix-strictness"; + mkFixStrictness = value: + mkProperty { + property = { + _type = "fix-strictness"; + onEval = p: value; + }; + content = mkNotdef; + }; + +} diff --git a/lib/sources.nix b/lib/sources.nix new file mode 100644 index 000000000000..6f8554d340be --- /dev/null +++ b/lib/sources.nix @@ -0,0 +1,29 @@ +# Functions for copying sources to the Nix store. + +let lib = import ./default.nix; in + +rec { + + + # Bring in a path as a source, filtering out all Subversion and CVS + # directories, as well as backup files (*~). + cleanSource = + let filter = name: type: let baseName = baseNameOf (toString name); in ! ( + # Filter out Subversion and CVS directories. + (type == "directory" && (baseName == ".git" || baseName == ".svn" || baseName == "CVS")) || + # Filter out backup files. + (lib.hasSuffix "~" baseName) + ); + in src: builtins.filterSource filter src; + + + # Get all files ending with the specified suffices from the given + # directory. E.g. `sourceFilesBySuffices ./dir [".xml" ".c"]'. + sourceFilesBySuffices = path: exts: + let filter = name: type: + let base = baseNameOf (toString name); + in type != "directory" && lib.any (ext: lib.hasSuffix ext base) exts; + in builtins.filterSource filter path; + + +} diff --git a/lib/strings-with-deps.nix b/lib/strings-with-deps.nix new file mode 100644 index 000000000000..3ad3e5991506 --- /dev/null +++ b/lib/strings-with-deps.nix @@ -0,0 +1,78 @@ +/* +Usage: + + You define you custom builder script by adding all build steps to a list. + for example: + builder = writeScript "fsg-4.4-builder" + (textClosure [doUnpack addInputs preBuild doMake installPhase doForceShare]); + + a step is defined by noDepEntry, fullDepEntry or packEntry. + To ensure that prerequisite are met those are added before the task itself by + textClosureDupList. Duplicated items are removed again. + + See trace/nixpkgs/trunk/pkgs/top-level/builder-defs.nix for some predefined build steps + + Attention: + + let + pkgs = (import /etc/nixos/nixpkgs/pkgs/top-level/all-packages.nix) {}; + in let + inherit (pkgs.stringsWithDeps) fullDepEntry packEntry noDepEntry textClosureMap; + inherit (pkgs.lib) id; + + nameA = noDepEntry "Text a"; + nameB = fullDepEntry "Text b" ["nameA"]; + nameC = fullDepEntry "Text c" ["nameA"]; + + stages = { + nameHeader = noDepEntry "#! /bin/sh \n"; + inherit nameA nameB nameC; + }; + in + textClosureMap id stages + [ "nameHeader" "nameA" "nameB" "nameC" + nameC # <- added twice. add a dep entry if you know that it will be added once only [1] + "nameB" # <- this will not be added again because the attr name (reference) is used + ] + + # result: Str("#! /bin/sh \n\nText a\nText b\nText c\nText c",[]) + + [1] maybe this behaviour should be removed to keep things simple (?) +*/ + +with import ./lists.nix; +with import ./attrsets.nix; +with import ./strings.nix; + +rec { + + /* !!! The interface of this function is kind of messed up, since + it's way too overloaded and almost but not quite computes a + topological sort of the depstrings. */ + + textClosureList = predefined: arg: + let + f = done: todo: + if todo == [] then {result = []; inherit done;} + else + let entry = head todo; in + if isAttrs entry then + let x = f done entry.deps; + y = f x.done (tail todo); + in { result = x.result ++ [entry.text] ++ y.result; + done = y.done; + } + else if hasAttr entry done then f done (tail todo) + else f (done // listToAttrs [{name = entry; value = 1;}]) ([(builtins.getAttr entry predefined)] ++ tail todo); + in (f {} arg).result; + + textClosureMap = f: predefined: names: + concatStringsSep "\n" (map f (textClosureList predefined names)); + + noDepEntry = text: {inherit text; deps = [];}; + fullDepEntry = text: deps: {inherit text deps;}; + packEntry = deps: {inherit deps; text="";}; + + stringAfter = deps: text: { inherit text deps; }; + +} diff --git a/lib/strings.nix b/lib/strings.nix new file mode 100644 index 000000000000..024a9ac7d7a2 --- /dev/null +++ b/lib/strings.nix @@ -0,0 +1,190 @@ +/* String manipulation functions. */ + +let lib = import ./default.nix; + +inherit (builtins) add sub lessThan length; + +in + +rec { + inherit (builtins) stringLength substring head tail; + + + # Concatenate a list of strings. + concatStrings = lib.fold (x: y: x + y) ""; + + + # Map a function over a list and concatenate the resulting strings. + concatMapStrings = f: list: concatStrings (map f list); + concatImapStrings = f: list: concatStrings (lib.imap f list); + + + # Place an element between each element of a list, e.g., + # `intersperse "," ["a" "b" "c"]' returns ["a" "," "b" "," "c"]. + intersperse = separator: list: + if list == [] || length list == 1 + then list + else [(head list) separator] + ++ (intersperse separator (tail list)); + + + # Concatenate a list of strings with a separator between each element, e.g. + # concatStringsSep " " ["foo" "bar" "xyzzy"] == "foo bar xyzzy" + concatStringsSep = separator: list: + concatStrings (intersperse separator list); + + + # Construct a Unix-style search path consisting of each `subDir" + # directory of the given list of packages. For example, + # `makeSearchPath "bin" ["x" "y" "z"]' returns "x/bin:y/bin:z/bin". + makeSearchPath = subDir: packages: + concatStringsSep ":" (map (path: path + "/" + subDir) packages); + + + # Construct a library search path (such as RPATH) containing the + # libraries for a set of packages, e.g. "${pkg1}/lib:${pkg2}/lib:...". + makeLibraryPath = makeSearchPath "lib"; + + + # Idem for Perl search paths. + makePerlPath = makeSearchPath "lib/perl5/site_perl"; + + + # Dependening on the boolean `cond', return either the given string + # or the empty string. + optionalString = cond: string: if cond then string else ""; + + + # Determine whether a filename ends in the given suffix. + hasSuffix = ext: fileName: + let lenFileName = stringLength fileName; + lenExt = stringLength ext; + in !(lessThan lenFileName lenExt) && + substring (sub lenFileName lenExt) lenFileName fileName == ext; + + + # Convert a string to a list of characters (i.e. singleton strings). + # For instance, "abc" becomes ["a" "b" "c"]. This allows you to, + # e.g., map a function over each character. However, note that this + # will likely be horribly inefficient; Nix is not a general purpose + # programming language. Complex string manipulations should, if + # appropriate, be done in a derivation. + stringToCharacters = s: let l = stringLength s; in + if l == 0 + then [] + else map (p: substring p 1 s) (lib.range 0 (sub l 1)); + + + # Manipulate a string charcater by character and replace them by strings + # before concatenating the results. + stringAsChars = f: s: + concatStrings ( + map f (stringToCharacters s) + ); + + + # same as vim escape function. + # Each character contained in list is prefixed by "\" + escape = list : string : + stringAsChars (c: if lib.elem c list then "\\${c}" else c) string; + + + # still ugly slow. But more correct now + # [] for zsh + escapeShellArg = lib.escape (stringToCharacters "\\ ';$`()|<>\t*[]"); + + + # replace characters by their substitutes. This function is equivalent to + # the `tr' command except that one character can be replace by multiple + # ones. e.g., + # replaceChars ["<" ">"] ["<" ">"] "<foo>" returns "<foo>". + replaceChars = del: new: s: + let + subst = c: + (lib.fold + (sub: res: if sub.fst == c then sub else res) + {fst = c; snd = c;} (lib.zipLists del new) + ).snd; + in + stringAsChars subst s; + + + # Case conversion utilities + lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; + upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + toLower = replaceChars upperChars lowerChars; + toUpper = replaceChars lowerChars upperChars; + + + # Compares strings not requiring context equality + # Obviously, a workaround but works on all Nix versions + eqStrings = a: b: (a+(substring 0 0 b)) == ((substring 0 0 a)+b); + + + # Cut a string with a separator and produces a list of strings which were + # separated by this separator. e.g., + # `splitString "." "foo.bar.baz"' returns ["foo" "bar" "baz"]. + splitString = sep: s: + let + sepLen = stringLength sep; + sLen = stringLength s; + lastSearch = sub sLen sepLen; + startWithSep = startAt: + substring startAt sepLen s == sep; + + recurse = index: startAt: + let cutUntil = i: [(substring startAt (sub i startAt) s)]; in + if lessThan index lastSearch then + if startWithSep index then + let restartAt = add index sepLen; in + cutUntil index ++ recurse restartAt restartAt + else + recurse (add index 1) startAt + else + cutUntil sLen; + in + recurse 0 0; + + + # return the suffix of the second argument if the first argument match its + # prefix. e.g., + # `removePrefix "foo." "foo.bar.baz"' returns "bar.baz". + removePrefix = pre: s: + let + preLen = stringLength pre; + sLen = stringLength s; + in + if pre == substring 0 preLen s then + substring preLen (sub sLen preLen) s + else + s; + + # Return true iff string v1 denotes a version older than v2. + versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; + + + # Return true iff string v1 denotes a version equal to or newer than v2. + versionAtLeast = v1: v2: !versionOlder v1 v2; + + + # Get the version of the specified derivation, as specified in its + # ‘name’ attribute. + getVersion = drv: (builtins.parseDrvName drv.name).version; + + + # Extract name with version from URL. Ask for separator which is + # supposed to start extension + nameFromURL = url: sep: let + components = splitString "/" url; + filename = lib.last components; + name = builtins.head (splitString sep filename); + in + assert ! eqStrings name filename; + name; + + + # Create an --{enable,disable}-<feat> string that can be passed to + # standard GNU Autoconf scripts. + enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; + +} diff --git a/lib/systems.nix b/lib/systems.nix new file mode 100644 index 000000000000..1ef869fb0120 --- /dev/null +++ b/lib/systems.nix @@ -0,0 +1,126 @@ +# Define the list of system with their properties. Only systems tested for +# Nixpkgs are listed below + +with import ./lists.nix; +with import ./types.nix; +with import ./attrsets.nix; + +let + lib = import ./default.nix; + setTypes = type: + mapAttrs (name: value: + setType type ({inherit name;} // value) + ); +in + +rec { + + isSignificantByte = isType "significant-byte"; + significantBytes = setTypes "significant-byte" { + bigEndian = {}; + littleEndian = {}; + }; + + + isCpuType = x: typeOf x == "cpu-type" + && elem x.bits [8 16 32 64 128] + && (builtins.lessThan 8 x.bits -> isSignificantByte x.significantByte); + + cpuTypes = with significantBytes; + setTypes "cpu-type" { + arm = { bits = 32; significantByte = littleEndian; }; + armv5tel = { bits = 32; significantByte = littleEndian; }; + armv7l = { bits = 32; significantByte = littleEndian; }; + i686 = { bits = 32; significantByte = littleEndian; }; + powerpc = { bits = 32; significantByte = bigEndian; }; + x86_64 = { bits = 64; significantByte = littleEndian; }; + }; + + + isExecFormat = isType "exec-format"; + execFormats = setTypes "exec-format" { + aout = {}; # a.out + elf = {}; + macho = {}; + pe = {}; + unknow = {}; + }; + + + isKernel = isType "kernel"; + kernels = with execFormats; + setTypes "kernel" { + cygwin = { execFormat = pe; }; + darwin = { execFormat = macho; }; + freebsd = { execFormat = elf; }; + linux = { execFormat = elf; }; + netbsd = { execFormat = elf; }; + none = { execFormat = unknow; }; + openbsd = { execFormat = elf; }; + win32 = { execFormat = pe; }; + }; + + + isArchitecture = isType "architecture"; + architectures = setTypes "architecture" { + apple = {}; + pc = {}; + unknow = {}; + }; + + + isSystem = x: typeOf x == "system" + && isCpuType x.cpu + && isArchitecture x.arch + && isKernel x.kernel; + + mkSystem = { + cpu ? cpuTypes.i686, + arch ? architectures.pc, + kernel ? kernels.linux, + name ? "${cpu.name}-${arch.name}-${kernel.name}" + }: setType "system" { + inherit name cpu arch kernel; + }; + + + isDarwin = matchAttrs { kernel = kernels.darwin; }; + isLinux = matchAttrs { kernel = kernels.linux; }; + isi686 = matchAttrs { cpu = cpuTypes.i686; }; + is64Bit = matchAttrs { cpu = { bits = 64; }; }; + + + # This should revert the job done by config.guess from the gcc compiler. + mkSystemFromString = s: let + l = lib.splitString "-" s; + + getCpu = name: + attrByPath [name] (throw "Unknow cpuType `${name}'.") + cpuTypes; + getArch = name: + attrByPath [name] (throw "Unknow architecture `${name}'.") + architectures; + getKernel = name: + attrByPath [name] (throw "Unknow kernel `${name}'.") + kernels; + + system = + if builtins.length l == 2 then + mkSystem rec { + name = s; + cpu = getCpu (head l); + arch = + if isDarwin system + then architectures.apple + else architectures.pc; + kernel = getKernel (head (tail l)); + } + else + mkSystem { + name = s; + cpu = getCpu (head l); + arch = getArch (head (tail l)); + kernel = getKernel (head (tail (tail l))); + }; + in assert isSystem system; system; +} diff --git a/lib/tests.nix b/lib/tests.nix new file mode 100644 index 000000000000..298bdffc3790 --- /dev/null +++ b/lib/tests.nix @@ -0,0 +1,113 @@ +let inherit (builtins) add; in +with import ./default.nix; + +runTests { + + testId = { + expr = id 1; + expected = 1; + }; + + testConst = { + expr = const 2 3; + expected = 2; + }; + + /* + testOr = { + expr = or true false; + expected = true; + }; + */ + + testAnd = { + expr = and true false; + expected = false; + }; + + testFix = { + expr = fix (x: {a = if x ? a then "a" else "b";}); + expected = {a = "a";}; + }; + + testConcatMapStrings = { + expr = concatMapStrings (x: x + ";") ["a" "b" "c"]; + expected = "a;b;c;"; + }; + + testConcatStringsSep = { + expr = concatStringsSep "," ["a" "b" "c"]; + expected = "a,b,c"; + }; + + testFilter = { + expr = filter (x: x != "a") ["a" "b" "c" "a"]; + expected = ["b" "c"]; + }; + + testFold = { + expr = fold (builtins.add) 0 (range 0 100); + expected = 5050; + }; + + testTake = testAllTrue [ + ([] == (take 0 [ 1 2 3 ])) + ([1] == (take 1 [ 1 2 3 ])) + ([ 1 2 ] == (take 2 [ 1 2 3 ])) + ([ 1 2 3 ] == (take 3 [ 1 2 3 ])) + ([ 1 2 3 ] == (take 4 [ 1 2 3 ])) + ]; + + testFoldAttrs = { + expr = foldAttrs (n: a: [n] ++ a) [] [ + { a = 2; b = 7; } + { a = 3; c = 8; } + ]; + expected = { a = [ 2 3 ]; b = [7]; c = [8];}; + }; + + testOverridableDelayableArgsTest = { + expr = + let res1 = defaultOverridableDelayableArgs id {}; + res2 = defaultOverridableDelayableArgs id { a = 7; }; + res3 = let x = defaultOverridableDelayableArgs id { a = 7; }; + in (x.merge) { b = 10; }; + res4 = let x = defaultOverridableDelayableArgs id { a = 7; }; + in (x.merge) ( x: { b = 10; }); + res5 = let x = defaultOverridableDelayableArgs id { a = 7; }; + in (x.merge) ( x: { a = add x.a 3; }); + res6 = let x = defaultOverridableDelayableArgs id { a = 7; mergeAttrBy = { a = add; }; }; + y = x.merge {}; + in (y.merge) { a = 10; }; + + resRem7 = res6.replace (a : removeAttrs a ["a"]); + + resReplace6 = let x = defaultOverridableDelayableArgs id { a = 7; mergeAttrBy = { a = add; }; }; + x2 = x.merge { a = 20; }; # now we have 27 + in (x2.replace) { a = 10; }; # and override the value by 10 + + # fixed tests (delayed args): (when using them add some comments, please) + resFixed1 = + let x = defaultOverridableDelayableArgs id ( x : { a = 7; c = x.fixed.b; }); + y = x.merge (x : { name = "name-${builtins.toString x.fixed.c}"; }); + in (y.merge) { b = 10; }; + strip = attrs : removeAttrs attrs ["merge" "replace"]; + in all id + [ ((strip res1) == { }) + ((strip res2) == { a = 7; }) + ((strip res3) == { a = 7; b = 10; }) + ((strip res4) == { a = 7; b = 10; }) + ((strip res5) == { a = 10; }) + ((strip res6) == { a = 17; }) + ((strip resRem7) == {}) + ((strip resFixed1) == { a = 7; b = 10; c =10; name = "name-10"; }) + ]; + expected = true; + }; + + testSort = { + expr = sort builtins.lessThan [ 40 2 30 42 ]; + expected = [2 30 40 42]; + }; + +} diff --git a/lib/trivial.nix b/lib/trivial.nix new file mode 100644 index 000000000000..8af3474f2a67 --- /dev/null +++ b/lib/trivial.nix @@ -0,0 +1,38 @@ +with { + inherit (import ./lists.nix) deepSeqList; + inherit (import ./attrsets.nix) deepSeqAttrs; +}; + +rec { + + # Identity function. + id = x: x; + + # Constant function. + const = x: y: x; + + # Named versions corresponding to some builtin operators. + concat = x: y: x ++ y; + or = x: y: x || y; + and = x: y: x && y; + mergeAttrs = x: y: x // y; + + # Take a function and evaluate it with its own returned value. + fix = f: let result = f result; in result; + + # Flip the order of the arguments of a binary function. + flip = f: a: b: f b a; + + # `seq x y' evaluates x, then returns y. That is, it forces strict + # evaluation of its first argument. + seq = x: y: if x == null then y else y; + + # Like `seq', but recurses into lists and attribute sets to force evaluation + # of all list elements/attributes. + deepSeq = x: y: + if builtins.isList x + then deepSeqList x y + else if builtins.isAttrs x + then deepSeqAttrs x y + else seq x y; +} diff --git a/lib/types.nix b/lib/types.nix new file mode 100644 index 000000000000..156d72ac5e73 --- /dev/null +++ b/lib/types.nix @@ -0,0 +1,226 @@ +# Definitions related to run-time type checking. Used in particular +# to type-check NixOS configurations. + +let lib = import ./default.nix; in + +with import ./lists.nix; +with import ./attrsets.nix; +with import ./options.nix; +with import ./trivial.nix; + +rec { + + isType = type: x: (x._type or "") == type; + hasType = x: isAttrs x && x ? _type; + typeOf = x: x._type or ""; + + setType = typeName: value: value // { + _type = typeName; + }; + + + # name (name of the type) + # check (check the config value. Before returning false it should trace the bad value eg using traceValIfNot) + # merge (default merge function) + # iter (iterate on all elements contained in this type) + # fold (fold all elements contained in this type) + # hasOptions (boolean: whatever this option contains an option set) + # delayOnGlobalEval (boolean: should properties go through the evaluation of this option) + # docPath (path concatenated to the option name contained in the option set) + isOptionType = isType "option-type"; + mkOptionType = + { name + , check ? (x: true) + , merge ? mergeDefaultOption + # Handle complex structure types. + , iter ? (f: path: v: f path v) + , fold ? (op: nul: v: op v nul) + , docPath ? lib.id + # If the type can contains option sets. + , hasOptions ? false + , delayOnGlobalEval ? false + }: + + { _type = "option-type"; + inherit name check merge iter fold docPath hasOptions delayOnGlobalEval; + }; + + + types = rec { + + bool = mkOptionType { + name = "boolean"; + check = lib.traceValIfNot builtins.isBool; + merge = fold lib.or false; + }; + + int = mkOptionType { + name = "integer"; + check = lib.traceValIfNot builtins.isInt; + }; + + string = mkOptionType { + name = "string"; + check = lib.traceValIfNot builtins.isString; + merge = lib.concatStrings; + }; + + # Like ‘string’, but add newlines between every value. Useful for + # configuration file contents. + lines = mkOptionType { + name = "string"; + check = lib.traceValIfNot builtins.isString; + merge = lib.concatStringsSep "\n"; + }; + + envVar = mkOptionType { + name = "environment variable"; + inherit (string) check; + merge = lib.concatStringsSep ":"; + }; + + attrs = mkOptionType { + name = "attribute set"; + check = lib.traceValIfNot isAttrs; + merge = fold lib.mergeAttrs {}; + }; + + # derivation is a reserved keyword. + package = mkOptionType { + name = "derivation"; + check = lib.traceValIfNot isDerivation; + }; + + path = mkOptionType { + name = "path"; + # Hacky: there is no ‘isPath’ primop. + check = lib.traceValIfNot (x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/"); + }; + + # drop this in the future: + list = builtins.trace "types.list is deprecated, use types.listOf instead" types.listOf; + + listOf = elemType: mkOptionType { + name = "list of ${elemType.name}s"; + check = value: lib.traceValIfNot isList value && all elemType.check value; + merge = concatLists; + iter = f: path: list: map (elemType.iter f (path + ".*")) list; + fold = op: nul: list: lib.fold (e: l: elemType.fold op l e) nul list; + docPath = path: elemType.docPath (path + ".*"); + inherit (elemType) hasOptions; + + # You cannot define multiple configurations of one entity, therefore + # no reason justify to delay properties inside list elements. + delayOnGlobalEval = false; + }; + + attrsOf = elemType: mkOptionType { + name = "attribute set of ${elemType.name}s"; + check = x: lib.traceValIfNot isAttrs x + && all elemType.check (lib.attrValues x); + merge = lib.zipAttrsWith (name: elemType.merge); + iter = f: path: set: lib.mapAttrs (name: elemType.iter f (path + "." + name)) set; + fold = op: nul: set: fold (e: l: elemType.fold op l e) nul (lib.attrValues set); + docPath = path: elemType.docPath (path + ".<name>"); + inherit (elemType) hasOptions delayOnGlobalEval; + }; + + # List or attribute set of ... + loaOf = elemType: + let + convertIfList = defIdx: def: + if isList def then + listToAttrs ( + flip imap def (elemIdx: elem: + nameValuePair "unnamed-${toString defIdx}.${toString elemIdx}" elem)) + else + def; + listOnly = listOf elemType; + attrOnly = attrsOf elemType; + + in mkOptionType { + name = "list or attribute set of ${elemType.name}s"; + check = x: + if isList x then listOnly.check x + else if isAttrs x then attrOnly.check x + else lib.traceValIfNot (x: false) x; + ## The merge function returns an attribute set + merge = defs: + attrOnly.merge (imap convertIfList defs); + iter = f: path: def: + if isList def then listOnly.iter f path def + else if isAttrs def then attrOnly.iter f path def + else throw "Unexpected value"; + fold = op: nul: def: + if isList def then listOnly.fold op nul def + else if isAttrs def then attrOnly.fold op nul def + else throw "Unexpected value"; + + docPath = path: elemType.docPath (path + ".<name?>"); + inherit (elemType) hasOptions delayOnGlobalEval; + } + ; + + uniq = elemType: mkOptionType { + inherit (elemType) name check iter fold docPath hasOptions; + merge = list: + if length list == 1 then + head list + else + throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option."; + }; + + none = elemType: mkOptionType { + inherit (elemType) name check iter fold docPath hasOptions; + merge = list: + throw "No definitions are allowed for this option."; + }; + + nullOr = elemType: mkOptionType { + inherit (elemType) name merge docPath hasOptions; + check = x: builtins.isNull x || elemType.check x; + iter = f: path: v: if v == null then v else elemType.iter f path v; + fold = op: nul: v: if v == null then nul else elemType.fold op nul v; + }; + + functionTo = elemType: mkOptionType { + name = "function that evaluates to a(n) ${elemType.name}"; + check = lib.traceValIfNot builtins.isFunction; + merge = fns: + args: elemType.merge (map (fn: fn args) fns); + # These are guesses, I don't fully understand iter, fold, delayOnGlobalEval + iter = f: path: v: + args: elemType.iter f path (v args); + fold = op: nul: v: + args: elemType.fold op nul (v args); + inherit (elemType) delayOnGlobalEval; + hasOptions = false; + }; + + # usually used with listOf, attrsOf, loaOf like this: + # users = mkOption { + # type = loaOf optionSet; + # + # # you can omit the list if there is one element only + # options = [ { + # name = mkOption { + # description = "name of the user" + # ... + # }; + # # more options here + # } { more options } ]; + # } + # TODO: !!! document passing options as an argument to optionSet, + # deprecate the current approach. + optionSet = mkOptionType { + name = "option set"; + # merge is done in "options.nix > addOptionMakeUp > handleOptionSets" + merge = lib.id; + check = x: isAttrs x || builtins.isFunction x; + hasOptions = true; + delayOnGlobalEval = true; + }; + + }; + +} |