diff options
author | Alyssa Ross <hi@alyssa.is> | 2019-01-07 02:18:36 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2019-01-07 02:18:47 +0000 |
commit | 36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2 (patch) | |
tree | b3faaf573407b32aa645237a4d16b82778a39a92 /nixpkgs/lib | |
parent | 4e31070265257dc67d120c27e0f75c2344fdfa9a (diff) | |
parent | abf060725d7614bd3b9f96764262dfbc2f9c2199 (diff) | |
download | nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.gz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.bz2 nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.lz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.xz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.zst nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.zip |
Add 'nixpkgs/' from commit 'abf060725d7614bd3b9f96764262dfbc2f9c2199'
git-subtree-dir: nixpkgs git-subtree-mainline: 4e31070265257dc67d120c27e0f75c2344fdfa9a git-subtree-split: abf060725d7614bd3b9f96764262dfbc2f9c2199
Diffstat (limited to 'nixpkgs/lib')
76 files changed, 8470 insertions, 0 deletions
diff --git a/nixpkgs/lib/asserts.nix b/nixpkgs/lib/asserts.nix new file mode 100644 index 000000000000..8a5f1fb3feb7 --- /dev/null +++ b/nixpkgs/lib/asserts.nix @@ -0,0 +1,44 @@ +{ lib }: + +rec { + + /* Print a trace message if pred is false. + Intended to be used to augment asserts with helpful error messages. + + Example: + assertMsg false "nope" + => false + stderr> trace: nope + + assert (assertMsg ("foo" == "bar") "foo is not bar, silly"); "" + stderr> trace: foo is not bar, silly + stderr> assert failed at … + + Type: + assertMsg :: Bool -> String -> Bool + */ + # TODO(Profpatsch): add tests that check stderr + assertMsg = pred: msg: + if pred + then true + else builtins.trace msg false; + + /* Specialized `assertMsg` for checking if val is one of the elements + of a list. Useful for checking enums. + + Example: + let sslLibrary = "libressl" + in assertOneOf "sslLibrary" sslLibrary [ "openssl" "bearssl" ] + => false + stderr> trace: sslLibrary must be one of "openssl", "bearssl", but is: "libressl" + + Type: + assertOneOf :: String -> ComparableVal -> List ComparableVal -> Bool + */ + assertOneOf = name: val: xs: assertMsg + (lib.elem val xs) + "${name} must be one of ${ + lib.generators.toPretty {} xs}, but is: ${ + lib.generators.toPretty {} val}"; + +} diff --git a/nixpkgs/lib/attrsets.nix b/nixpkgs/lib/attrsets.nix new file mode 100644 index 000000000000..d374d229f597 --- /dev/null +++ b/nixpkgs/lib/attrsets.nix @@ -0,0 +1,482 @@ +{ lib }: +# Operations on attribute sets. + +let + inherit (builtins) head tail length; + inherit (lib.trivial) and; + inherit (lib.strings) concatStringsSep; + inherit (lib.lists) fold concatMap concatLists; +in + +rec { + inherit (builtins) attrNames listToAttrs hasAttr isAttrs getAttr; + + + /* Return an attribute from nested attribute sets. + + Example: + x = { a = { b = 3; }; } + attrByPath ["a" "b"] 6 x + => 3 + attrByPath ["z" "z"] 6 x + => 6 + */ + attrByPath = attrPath: default: e: + let attr = head attrPath; + in + if attrPath == [] then e + else if e ? ${attr} + then attrByPath (tail attrPath) default e.${attr} + else default; + + /* Return if an attribute from nested attribute set exists. + + Example: + x = { a = { b = 3; }; } + hasAttrByPath ["a" "b"] x + => true + hasAttrByPath ["z" "z"] x + => false + + */ + hasAttrByPath = attrPath: e: + let attr = head attrPath; + in + if attrPath == [] then true + else if e ? ${attr} + then hasAttrByPath (tail attrPath) e.${attr} + else false; + + + /* Return nested attribute set in which an attribute is set. + + Example: + setAttrByPath ["a" "b"] 3 + => { a = { b = 3; }; } + */ + setAttrByPath = attrPath: value: + if attrPath == [] then value + else listToAttrs + [ { name = head attrPath; value = setAttrByPath (tail attrPath) value; } ]; + + + /* Like `getAttrPath' without a default value. If it doesn't find the + path it will throw. + + Example: + x = { a = { b = 3; }; } + getAttrFromPath ["a" "b"] x + => 3 + getAttrFromPath ["z" "z"] x + => error: cannot find attribute `z.z' + */ + getAttrFromPath = attrPath: set: + let errorMsg = "cannot find attribute `" + concatStringsSep "." attrPath + "'"; + in attrByPath attrPath (abort errorMsg) set; + + + /* Return the specified attributes from a set. + + Example: + attrVals ["a" "b" "c"] as + => [as.a as.b as.c] + */ + attrVals = nameList: set: map (x: set.${x}) nameList; + + + /* Return the values of all attributes in the given set, sorted by + attribute name. + + Example: + attrValues {c = 3; a = 1; b = 2;} + => [1 2 3] + */ + attrValues = builtins.attrValues or (attrs: attrVals (attrNames attrs) attrs); + + + /* Given a set of attribute names, return the set of the corresponding + attributes from the given set. + + Example: + getAttrs [ "a" "b" ] { a = 1; b = 2; c = 3; } + => { a = 1; b = 2; } + */ + getAttrs = names: attrs: genAttrs names (name: attrs.${name}); + + /* Collect each attribute named `attr' from a list of attribute + sets. Sets that don't contain the named attribute are ignored. + + Example: + catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}] + => [1 2] + */ + catAttrs = builtins.catAttrs or + (attr: l: concatLists (map (s: if s ? ${attr} then [s.${attr}] else []) l)); + + + /* Filter an attribute set by removing all attributes for which the + given predicate return false. + + Example: + filterAttrs (n: v: n == "foo") { foo = 1; bar = 2; } + => { foo = 1; } + */ + filterAttrs = pred: set: + listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (attrNames set)); + + + /* Filter an attribute set recursively by removing all attributes for + which the given predicate return false. + + Example: + filterAttrsRecursive (n: v: v != null) { foo = { bar = null; }; } + => { foo = {}; } + */ + filterAttrsRecursive = pred: set: + listToAttrs ( + concatMap (name: + let v = set.${name}; in + if pred name v then [ + (nameValuePair name ( + if isAttrs v then filterAttrsRecursive pred v + else v + )) + ] else [] + ) (attrNames set) + ); + + /* Apply fold functions to values grouped by key. + + Example: + foldAttrs (n: a: [n] ++ a) [] [{ a = 2; } { a = 3; }] + => { a = [ 2 3 ]; } + */ + foldAttrs = op: nul: list_of_attrs: + fold (n: a: + fold (name: o: + o // { ${name} = op n.${name} (a.${name} or nul); } + ) a (attrNames n) + ) {} list_of_attrs; + + + /* Recursively collect sets that verify a given predicate named `pred' + from the set `attrs'. The recursion is stopped when the predicate is + verified. + + Type: + collect :: + (AttrSet -> Bool) -> AttrSet -> [x] + + Example: + collect isList { a = { b = ["b"]; }; c = [1]; } + => [["b"] [1]] + + collect (x: x ? outPath) + { a = { outPath = "a/"; }; b = { outPath = "b/"; }; } + => [{ outPath = "a/"; } { outPath = "b/"; }] + */ + collect = pred: attrs: + if pred attrs then + [ attrs ] + else if isAttrs attrs then + concatMap (collect pred) (attrValues attrs) + else + []; + + + /* Utility function that creates a {name, value} pair as expected by + builtins.listToAttrs. + + Example: + nameValuePair "some" 6 + => { name = "some"; value = 6; } + */ + nameValuePair = name: value: { inherit name value; }; + + + /* Apply a function to each element in an attribute set. The + function takes two arguments --- the attribute name and its value + --- and returns the new value for the attribute. The result is a + new attribute set. + + Example: + mapAttrs (name: value: name + "-" + value) + { x = "foo"; y = "bar"; } + => { x = "x-foo"; y = "y-bar"; } + */ + mapAttrs = builtins.mapAttrs or + (f: set: + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))); + + + /* Like `mapAttrs', but allows the name of each attribute to be + changed in addition to the value. The applied function should + return both the new name and value as a `nameValuePair'. + + Example: + mapAttrs' (name: value: nameValuePair ("foo_" + name) ("bar-" + value)) + { x = "a"; y = "b"; } + => { foo_x = "bar-a"; foo_y = "bar-b"; } + */ + mapAttrs' = f: set: + listToAttrs (map (attr: f attr set.${attr}) (attrNames set)); + + + /* Call a function for each attribute in the given set and return + the result in a list. + + Example: + mapAttrsToList (name: value: name + value) + { x = "a"; y = "b"; } + => [ "xa" "yb" ] + */ + mapAttrsToList = f: attrs: + map (name: f name attrs.${name}) (attrNames attrs); + + + /* Like `mapAttrs', except that it recursively applies itself to + attribute sets. Also, the first argument of the argument + function is a *list* of the names of the containing attributes. + + Type: + mapAttrsRecursive :: + ([String] -> a -> b) -> AttrSet -> AttrSet + + Example: + mapAttrsRecursive (path: value: concatStringsSep "-" (path ++ [value])) + { n = { a = "A"; m = { b = "B"; c = "C"; }; }; d = "D"; } + => { n = { a = "n-a-A"; m = { b = "n-m-b-B"; c = "n-m-c-C"; }; }; d = "d-D"; } + */ + mapAttrsRecursive = mapAttrsRecursiveCond (as: true); + + + /* Like `mapAttrsRecursive', but it takes an additional predicate + function that tells it whether to recursive into an attribute + set. If it returns false, `mapAttrsRecursiveCond' does not + recurse, but does apply the map function. It is returns true, it + does recurse, and does not apply the map function. + + Type: + mapAttrsRecursiveCond :: + (AttrSet -> Bool) -> ([String] -> a -> b) -> AttrSet -> AttrSet + + Example: + # To prevent recursing into derivations (which are attribute + # sets with the attribute "type" equal to "derivation"): + mapAttrsRecursiveCond + (as: !(as ? "type" && as.type == "derivation")) + (x: ... do something ...) + attrs + */ + mapAttrsRecursiveCond = cond: f: set: + let + recurse = path: set: + let + g = + name: value: + if isAttrs value && cond value + then recurse (path ++ [name]) value + else f (path ++ [name]) value; + in mapAttrs g set; + in recurse [] set; + + + /* Generate an attribute set by mapping a function over a list of + attribute names. + + Example: + genAttrs [ "foo" "bar" ] (name: "x_" + name) + => { foo = "x_foo"; bar = "x_bar"; } + */ + genAttrs = names: f: + listToAttrs (map (n: nameValuePair n (f n)) names); + + + /* Check whether the argument is a derivation. Any set with + { type = "derivation"; } counts as a derivation. + + Example: + nixpkgs = import <nixpkgs> {} + isDerivation nixpkgs.ruby + => true + isDerivation "foobar" + => false + */ + isDerivation = x: isAttrs x && x ? type && x.type == "derivation"; + + /* Converts a store path to a fake derivation. */ + toDerivation = path: + let + path' = builtins.storePath path; + res = + { type = "derivation"; + name = builtins.unsafeDiscardStringContext (builtins.substring 33 (-1) (baseNameOf path')); + outPath = path'; + outputs = [ "out" ]; + out = res; + outputName = "out"; + }; + in res; + + + /* If `cond' is true, return the attribute set `as', + otherwise an empty attribute set. + + Example: + optionalAttrs (true) { my = "set"; } + => { my = "set"; } + optionalAttrs (false) { my = "set"; } + => { } + */ + optionalAttrs = cond: as: if cond then as else {}; + + + /* Merge sets of attributes and use the function f to merge attributes + values. + + Example: + zipAttrsWithNames ["a"] (name: vs: vs) [{a = "x";} {a = "y"; b = "z";}] + => { a = ["x" "y"]; } + */ + zipAttrsWithNames = names: f: sets: + listToAttrs (map (name: { + inherit name; + value = f name (catAttrs name sets); + }) names); + + /* Implementation note: Common names appear multiple times in the list of + names, hopefully this does not affect the system because the maximal + laziness avoid computing twice the same expression and listToAttrs does + not care about duplicated attribute names. + + Example: + zipAttrsWith (name: values: values) [{a = "x";} {a = "y"; b = "z";}] + => { a = ["x" "y"]; b = ["z"] } + */ + zipAttrsWith = f: sets: zipAttrsWithNames (concatMap attrNames sets) f sets; + /* Like `zipAttrsWith' with `(name: values: value)' as the function. + + Example: + zipAttrs [{a = "x";} {a = "y"; b = "z";}] + => { a = ["x" "y"]; b = ["z"] } + */ + zipAttrs = zipAttrsWith (name: values: values); + + /* Does the same as the update operator '//' except that attributes are + merged until the given predicate is verified. The predicate should + accept 3 arguments which are the path to reach the attribute, a part of + the first attribute set and a part of the second attribute set. When + the predicate is verified, the value of the first attribute set is + replaced by the value of the second attribute set. + + Example: + recursiveUpdateUntil (path: l: r: path == ["foo"]) { + # first attribute set + foo.bar = 1; + foo.baz = 2; + bar = 3; + } { + #second attribute set + foo.bar = 1; + foo.quz = 2; + baz = 4; + } + + returns: { + foo.bar = 1; # 'foo.*' from the second set + foo.quz = 2; # + bar = 3; # 'bar' from the first set + baz = 4; # 'baz' from the second set + } + + */ + recursiveUpdateUntil = pred: lhs: rhs: + let f = attrPath: + zipAttrsWith (n: values: + let here = attrPath ++ [n]; in + if tail values == [] + || pred here (head (tail values)) (head values) then + head values + else + f here values + ); + in f [] [rhs lhs]; + + /* A recursive variant of the update operator ‘//’. The recursion + stops when one of the attribute values is not an attribute set, + in which case the right hand side value takes precedence over the + left hand side value. + + Example: + recursiveUpdate { + boot.loader.grub.enable = true; + boot.loader.grub.device = "/dev/hda"; + } { + boot.loader.grub.device = ""; + } + + returns: { + boot.loader.grub.enable = true; + boot.loader.grub.device = ""; + } + + */ + recursiveUpdate = lhs: rhs: + recursiveUpdateUntil (path: lhs: rhs: + !(isAttrs lhs && isAttrs rhs) + ) lhs rhs; + + /* Returns true if the pattern is contained in the set. False otherwise. + + Example: + matchAttrs { cpu = {}; } { cpu = { bits = 64; }; } + => true + */ + matchAttrs = pattern: attrs: assert isAttrs pattern; + fold and true (attrValues (zipAttrsWithNames (attrNames pattern) (n: values: + let pat = head values; val = head (tail values); in + if length values == 1 then false + else if isAttrs pat then isAttrs val && matchAttrs pat val + else pat == val + ) [pattern attrs])); + + /* Override only the attributes that are already present in the old set + useful for deep-overriding. + + Example: + overrideExisting {} { a = 1; } + => {} + overrideExisting { b = 2; } { a = 1; } + => { b = 2; } + overrideExisting { a = 3; b = 2; } { a = 1; } + => { a = 1; b = 2; } + */ + overrideExisting = old: new: + mapAttrs (name: value: new.${name} or value) old; + + /* Get a package output. + If no output is found, fallback to `.out` and then to the default. + + Example: + getOutput "dev" pkgs.openssl + => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev" + */ + getOutput = output: pkg: + if pkg.outputUnspecified or false + then pkg.${output} or pkg.out or pkg + else pkg; + + getBin = getOutput "bin"; + getLib = getOutput "lib"; + getDev = getOutput "dev"; + + /* Pick the outputs of packages to place in buildInputs */ + chooseDevOutputs = drvs: builtins.map getDev drvs; + + /*** deprecated stuff ***/ + + zipWithNames = zipAttrsWithNames; + zip = builtins.trace + "lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith; + +} diff --git a/nixpkgs/lib/composable-derivation.nix b/nixpkgs/lib/composable-derivation.nix new file mode 100644 index 000000000000..cb1fdc121e11 --- /dev/null +++ b/nixpkgs/lib/composable-derivation.nix @@ -0,0 +1,113 @@ +{lib, pkgs}: +let inherit (lib) nvs; in +{ + + # composableDerivation basically mixes these features: + # - fix function + # - mergeAttrBy + # - provides shortcuts for "options" such as "--enable-foo" and adding + # buildInputs, see php example + # + # It predates styles which are common today, such as + # * the config attr + # * mkDerivation.override feature + # * overrideDerivation (lib/customization.nix) + # + # Some of the most more important usage examples (which could be rewritten if it was important): + # * php + # * postgis + # * vim_configurable + # + # A minimal example illustrating most features would look like this: + # let base = composableDerivation { (fixed: let inherit (fixed.fixed) name in { + # src = fetchurl { + # } + # buildInputs = [A]; + # preConfigre = "echo ${name}"; + # # attention, "name" attr is missing, thus you cannot instantiate "base". + # } + # in { + # # These all add name attribute, thus you can instantiate those: + # v1 = base.merge ({ name = "foo-add-B"; buildInputs = [B]; }); // B gets merged into buildInputs + # v2 = base.merge ({ name = "mix-in-pre-configure-lines" preConfigre = ""; }); + # v3 = base.replace ({ name = "foo-no-A-only-B;" buildInputs = [B]; }); + # } + # + # So yes, you can think about it being something like nixos modules, and + # you'd be merging "features" in one at a time using .merge or .replace + # Thanks Shea for telling me that I rethink the documentation .. + # + # issues: + # * its complicated to understand + # * some "features" such as exact merge behaviour are buried in mergeAttrBy + # and defaultOverridableDelayableArgs assuming the default behaviour does + # the right thing in the common case + # * Eelco once said using such fix style functions are slow to evaluate + # * Too quick & dirty. Hard to understand for others. The benefit was that + # you were able to create a kernel builder like base derivation and replace + # / add patches the way you want without having to declare function arguments + # + # nice features: + # declaring "optional features" is modular. For instance: + # flags.curl = { + # configureFlags = ["--with-curl=${curl.dev}" "--with-curlwrappers"]; + # buildInputs = [curl openssl]; + # }; + # flags.other = { .. } + # (Example taken from PHP) + # + # alternative styles / related features: + # * Eg see function supporting building the kernel + # * versionedDerivation (discussion about this is still going on - or ended) + # * composedArgsAndFun + # * mkDerivation.override + # * overrideDerivation + # * using { .., *Support ? false }: like configurable options. + # To find those examples use grep + # + # To sum up: It exists for historical reasons - and for most commonly used + # tasks the alternatives should be used + # + # If you have questions about this code ping Marc Weber. + 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/nixpkgs/lib/customisation.nix b/nixpkgs/lib/customisation.nix new file mode 100644 index 000000000000..68062dd0daf0 --- /dev/null +++ b/nixpkgs/lib/customisation.nix @@ -0,0 +1,206 @@ +{ lib }: + +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 overridden + 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 ~/.config/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 lib.flip (extendDerivation true) 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 { })); + + + /* `makeOverridable` takes a function from attribute set to attribute set and + injects `override` attibute which can be used to override arguments of + the function. + + nix-repl> x = {a, b}: { result = a + b; } + + nix-repl> y = lib.makeOverridable x { a = 1; b = 2; } + + nix-repl> y + { override = «lambda»; overrideDerivation = «lambda»; result = 3; } + + nix-repl> y.override { a = 10; } + { override = «lambda»; overrideDerivation = «lambda»; result = 12; } + + Please refer to "Nixpkgs Contributors Guide" section + "<pkg>.overrideDerivation" to learn about `overrideDerivation` and caveats + related to its use. + */ + makeOverridable = f: origArgs: + let + ff = f origArgs; + overrideWith = newArgs: origArgs // (if lib.isFunction newArgs then newArgs origArgs else newArgs); + in + if builtins.isAttrs ff then (ff // { + override = newArgs: makeOverridable f (overrideWith newArgs); + overrideDerivation = fdrv: + makeOverridable (args: overrideDerivation (f args) fdrv) origArgs; + ${if ff ? overrideAttrs then "overrideAttrs" else null} = fdrv: + makeOverridable (args: (f args).overrideAttrs fdrv) origArgs; + }) + else if lib.isFunction ff then { + override = newArgs: makeOverridable f (overrideWith newArgs); + __functor = self: ff; + overrideDerivation = throw "overrideDerivation not yet supported for functors"; + } + else ff; + + + /* 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 lib.isFunction fn then fn else import fn; + auto = builtins.intersectAttrs (lib.functionArgs f) autoArgs; + in makeOverridable f (auto // args); + + + /* Like callPackage, but for a function that returns an attribute + set of derivations. The override function is added to the + individual attributes. */ + callPackagesWith = autoArgs: fn: args: + let + f = if lib.isFunction fn then fn else import fn; + auto = builtins.intersectAttrs (lib.functionArgs f) autoArgs; + origArgs = auto // args; + pkgs = f origArgs; + mkAttrOverridable = name: pkg: makeOverridable (newArgs: (f newArgs).${name}) origArgs; + in lib.mapAttrs mkAttrOverridable pkgs; + + + /* Add attributes to each output of a derivation without changing + the derivation itself and check a given condition when evaluating. */ + extendDerivation = condition: passthru: drv: + 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 (drv.${outputName}) type outputName; + drvPath = assert condition; drv.${outputName}.drvPath; + outPath = assert condition; drv.${outputName}.outPath; + }; + }; + + outputsList = map outputToAttrListElement outputs; + in commonAttrs // { + outputUnspecified = true; + drvPath = assert condition; drv.drvPath; + outPath = assert condition; drv.outPath; + }; + + /* Strip a derivation of all non-essential attributes, returning + only those needed by hydra-eval-jobs. Also strictly evaluate the + result to ensure that there are no thunks kept alive to prevent + garbage collection. */ + hydraJob = drv: + let + outputs = drv.outputs or ["out"]; + + commonAttrs = + { inherit (drv) name system meta; inherit outputs; } + // lib.optionalAttrs (drv._hydraAggregate or false) { + _hydraAggregate = true; + constituents = map hydraJob (lib.flatten drv.constituents); + } + // (lib.listToAttrs outputsList); + + makeOutput = outputName: + let output = drv.${outputName}; in + { name = outputName; + value = commonAttrs // { + outPath = output.outPath; + drvPath = output.drvPath; + type = "derivation"; + inherit outputName; + }; + }; + + outputsList = map makeOutput outputs; + + drv' = (lib.head outputsList).value; + in lib.deepSeq drv' drv'; + + /* Make a set of packages with a common scope. All packages called + with the provided `callPackage' will be evaluated with the same + arguments. Any package in the set may depend on any other. The + `overrideScope'` function allows subsequent modification of the package + set in a consistent way, i.e. all packages in the set will be + called with the overridden packages. The package sets may be + hierarchical: the packages in the set are called with the scope + provided by `newScope' and the set provides a `newScope' attribute + which can form the parent scope for later package sets. */ + makeScope = newScope: f: + let self = f self // { + newScope = scope: newScope (self // scope); + callPackage = self.newScope {}; + overrideScope = g: lib.warn + "`overrideScope` (from `lib.makeScope`) is deprecated. Do `overrideScope' (self: super: { … })` instead of `overrideScope (super: self: { … })`. All other overrides have the parameters in that order, including other definitions of `overrideScope`. This was the only definition violating the pattern." + (makeScope newScope (lib.fixedPoints.extends (lib.flip g) f)); + overrideScope' = g: makeScope newScope (lib.fixedPoints.extends g f); + packages = f; + }; + in self; + +} diff --git a/nixpkgs/lib/debug.nix b/nixpkgs/lib/debug.nix new file mode 100644 index 000000000000..2879f72ed2ba --- /dev/null +++ b/nixpkgs/lib/debug.nix @@ -0,0 +1,253 @@ +/* Collection of functions useful for debugging + broken nix expressions. + + * `trace`-like functions take two values, print + the first to stderr and return the second. + * `traceVal`-like functions take one argument + which both printed and returned. + * `traceSeq`-like functions fully evaluate their + traced value before printing (not just to “weak + head normal form” like trace does by default). + * Functions that end in `-Fn` take an additional + function as their first argument, which is applied + to the traced value before it is printed. +*/ +{ lib }: +let + inherit (builtins) trace isAttrs isList isInt + head substring attrNames; + inherit (lib) id elem isFunction; +in + +rec { + + # -- TRACING -- + + /* Conditionally trace the supplied message, based on a predicate. + + Type: traceIf :: bool -> string -> a -> a + + Example: + traceIf true "hello" 3 + trace: hello + => 3 + */ + traceIf = + # Predicate to check + pred: + # Message that should be traced + msg: + # Value to return + x: if pred then trace msg x else x; + + /* Trace the supplied value after applying a function to it, and + return the original value. + + Type: traceValFn :: (a -> b) -> a -> a + + Example: + traceValFn (v: "mystring ${v}") "foo" + trace: mystring foo + => "foo" + */ + traceValFn = + # Function to apply + f: + # Value to trace and return + x: trace (f x) x; + + /* Trace the supplied value and return it. + + Type: traceVal :: a -> a + + Example: + traceVal 42 + # trace: 42 + => 42 + */ + traceVal = traceValFn id; + + /* `builtins.trace`, but the value is `builtins.deepSeq`ed first. + + Type: traceSeq :: a -> b -> b + + Example: + trace { a.b.c = 3; } null + trace: { a = <CODE>; } + => null + traceSeq { a.b.c = 3; } null + trace: { a = { b = { c = 3; }; }; } + => null + */ + traceSeq = + # The value to trace + x: + # The value to return + y: trace (builtins.deepSeq x x) y; + + /* Like `traceSeq`, but only evaluate down to depth n. + This is very useful because lots of `traceSeq` usages + lead to an infinite recursion. + + Example: + traceSeqN 2 { a.b.c = 3; } null + trace: { a = { b = {…}; }; } + => null + */ + traceSeqN = depth: x: y: with lib; + let snip = v: if isList v then noQuotes "[…]" v + else if isAttrs v then noQuotes "{…}" v + else v; + noQuotes = str: v: { __pretty = const str; val = v; }; + modify = n: fn: v: if (n == 0) then fn v + else if isList v then map (modify (n - 1) fn) v + else if isAttrs v then mapAttrs + (const (modify (n - 1) fn)) v + else v; + in trace (generators.toPretty { allowPrettyValues = true; } + (modify depth snip x)) y; + + /* A combination of `traceVal` and `traceSeq` that applies a + provided function to the value to be traced after `deepSeq`ing + it. + */ + traceValSeqFn = + # Function to apply + f: + # Value to trace + v: traceValFn f (builtins.deepSeq v v); + + /* A combination of `traceVal` and `traceSeq`. */ + traceValSeq = traceValSeqFn id; + + /* A combination of `traceVal` and `traceSeqN` that applies a + provided function to the value to be traced. */ + traceValSeqNFn = + # Function to apply + f: + depth: + # Value to trace + v: traceSeqN depth (f v) v; + + /* A combination of `traceVal` and `traceSeqN`. */ + traceValSeqN = traceValSeqNFn id; + + + # -- TESTING -- + + /* 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 tests only. + */ + runTests = + # Tests to run + 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`. + + Example: + { testX = allTrue [ true ]; } + */ + testAllTrue = expr: { inherit expr; expected = map (x: true) expr; }; + + + # -- DEPRECATED -- + + traceShowVal = x: trace (showVal x) x; + traceShowValMarked = str: x: trace (str + showVal x) x; + + attrNamesToStr = a: + trace ( "Warning: `attrNamesToStr` is deprecated " + + "and will be removed in the next release. " + + "Please use more specific concatenation " + + "for your uses (`lib.concat(Map)StringsSep`)." ) + (lib.concatStringsSep "; " (map (x: "${x}=") (attrNames a))); + + showVal = with lib; + trace ( "Warning: `showVal` is deprecated " + + "and will be removed in the next release, " + + "please use `traceSeqN`" ) + (let + modify = v: + let pr = f: { __pretty = f; val = v; }; + in if isDerivation v then pr + (drv: "<δ:${drv.name}:${concatStringsSep "," + (attrNames drv)}>") + else if [] == v then pr (const "[]") + else if isList v then pr (l: "[ ${go (head l)}, … ]") + else if isAttrs v then pr + (a: "{ ${ concatStringsSep ", " (attrNames a)} }") + else v; + go = x: generators.toPretty + { allowPrettyValues = true; } + (modify x); + in go); + + traceXMLVal = x: + trace ( "Warning: `traceXMLVal` is deprecated " + + "and will be removed in the next release. " + + "Please use `traceValFn builtins.toXML`." ) + (trace (builtins.toXML x) x); + traceXMLValMarked = str: x: + trace ( "Warning: `traceXMLValMarked` is deprecated " + + "and will be removed in the next release. " + + "Please use `traceValFn (x: str + builtins.toXML x)`." ) + (trace (str + builtins.toXML x) 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: + trace ( "Warning: `traceValIfNot` is deprecated " + + "and will be removed in the next release. " + + "Please use `if/then/else` and `traceValSeq 1`.") + (if c x then true else traceSeq (showVal x) false); + + + addErrorContextToAttrs = attrs: + trace ( "Warning: `addErrorContextToAttrs` is deprecated " + + "and will be removed in the next release. " + + "Please use `builtins.addErrorContext` directly." ) + (lib.mapAttrs (a: v: lib.addErrorContext "while evaluating ${a}" v) attrs); + + # 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: + trace ( "Warning: `traceCallXml` is deprecated " + + "and will be removed in the next release. " + + "Please complain if you use the function regularly." ) + (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 (builtins.seq arg arg)}" (expr arg) + ) + else + let r = builtins.seq expr expr; + in trace "${str}\n result:\n${builtins.toXML r}" r + )); +} diff --git a/nixpkgs/lib/default.nix b/nixpkgs/lib/default.nix new file mode 100644 index 000000000000..e4e3e7d325a4 --- /dev/null +++ b/nixpkgs/lib/default.nix @@ -0,0 +1,138 @@ +/* Library of low-level helper functions for nix expressions. + * + * Please implement (mostly) exhaustive unit tests + * for new functions in `./tests.nix'. + */ +let + + inherit (import ./fixed-points.nix {}) makeExtensible; + + lib = makeExtensible (self: let + callLibs = file: import file { lib = self; }; + in with self; { + + # often used, or depending on very little + trivial = callLibs ./trivial.nix; + fixedPoints = callLibs ./fixed-points.nix; + + # datatypes + attrsets = callLibs ./attrsets.nix; + lists = callLibs ./lists.nix; + strings = callLibs ./strings.nix; + stringsWithDeps = callLibs ./strings-with-deps.nix; + + # packaging + customisation = callLibs ./customisation.nix; + maintainers = import ../maintainers/maintainer-list.nix; + meta = callLibs ./meta.nix; + sources = callLibs ./sources.nix; + versions = callLibs ./versions.nix; + + # module system + modules = callLibs ./modules.nix; + options = callLibs ./options.nix; + types = callLibs ./types.nix; + + # constants + licenses = callLibs ./licenses.nix; + systems = callLibs ./systems; + + # misc + asserts = callLibs ./asserts.nix; + debug = callLibs ./debug.nix; + generators = callLibs ./generators.nix; + misc = callLibs ./deprecated.nix; + + # domain-specific + fetchers = callLibs ./fetchers.nix; + + # Eval-time filesystem handling + filesystem = callLibs ./filesystem.nix; + + # back-compat aliases + platforms = systems.forMeta; + + inherit (builtins) add addErrorContext attrNames concatLists + deepSeq elem elemAt filter genericClosure genList getAttr + hasAttr head isAttrs isBool isInt isList isString length + lessThan listToAttrs pathExists readFile replaceStrings seq + stringLength sub substring tail; + inherit (trivial) id const concat or and bitAnd bitOr bitXor bitNot + boolToString mergeAttrs flip mapNullable inNixShell min max + importJSON warn info nixpkgsVersion version mod compare + splitByAndCompare functionArgs setFunctionArgs isFunction; + inherit (fixedPoints) fix fix' converge extends composeExtensions + makeExtensible makeExtensibleWithCustomName; + inherit (attrsets) attrByPath hasAttrByPath setAttrByPath + getAttrFromPath attrVals attrValues getAttrs catAttrs filterAttrs + filterAttrsRecursive foldAttrs collect nameValuePair mapAttrs + mapAttrs' mapAttrsToList mapAttrsRecursive mapAttrsRecursiveCond + genAttrs isDerivation toDerivation optionalAttrs + zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil + recursiveUpdate matchAttrs overrideExisting getOutput getBin + getLib getDev chooseDevOutputs zipWithNames zip; + inherit (lists) singleton foldr fold foldl foldl' imap0 imap1 + concatMap flatten remove findSingle findFirst any all count + optional optionals toList range partition zipListsWith zipLists + reverseList listDfs toposort sort naturalSort compareLists take + drop sublist last init crossLists unique intersectLists + subtractLists mutuallyExclusive groupBy groupBy'; + inherit (strings) concatStrings concatMapStrings concatImapStrings + intersperse concatStringsSep concatMapStringsSep + concatImapStringsSep makeSearchPath makeSearchPathOutput + makeLibraryPath makeBinPath makePerlPath makeFullPerlPath optionalString + hasPrefix hasSuffix stringToCharacters stringAsChars escape + escapeShellArg escapeShellArgs replaceChars lowerChars + upperChars toLower toUpper addContextFrom splitString + removePrefix removeSuffix versionOlder versionAtLeast getVersion + nameFromURL enableFeature enableFeatureAs withFeature + withFeatureAs fixedWidthString fixedWidthNumber isStorePath + toInt readPathsFromFile fileContents; + inherit (stringsWithDeps) textClosureList textClosureMap + noDepEntry fullDepEntry packEntry stringAfter; + inherit (customisation) overrideDerivation makeOverridable + callPackageWith callPackagesWith extendDerivation hydraJob + makeScope; + inherit (meta) addMetaAttrs dontDistribute setName updateName + appendToName mapDerivationAttrset lowPrio lowPrioSet hiPrio + hiPrioSet; + inherit (sources) pathType pathIsDirectory cleanSourceFilter + cleanSource sourceByRegex sourceFilesBySuffices + commitIdFromGitRepo cleanSourceWith pathHasContext + canCleanSource; + inherit (modules) evalModules closeModules unifyModuleSyntax + applyIfFunction unpackSubmodule packSubmodule mergeModules + mergeModules' mergeOptionDecls evalOptionValue mergeDefinitions + pushDownProperties dischargeProperties filterOverrides + sortProperties fixupOptionType mkIf mkAssert mkMerge mkOverride + mkOptionDefault mkDefault mkForce mkVMOverride mkStrict + mkFixStrictness mkOrder mkBefore mkAfter mkAliasDefinitions + mkAliasAndWrapDefinitions fixMergeModules mkRemovedOptionModule + mkRenamedOptionModule mkMergedOptionModule mkChangedOptionModule + mkAliasOptionModule doRename filterModules; + inherit (options) isOption mkEnableOption mkSinkUndeclaredOptions + mergeDefaultOption mergeOneOption mergeEqualOption getValues + getFiles optionAttrSetToDocList optionAttrSetToDocList' + scrubOptionValue literalExample showOption showFiles + unknownModule mkOption; + inherit (types) isType setType defaultTypeMerge defaultFunctor + isOptionType mkOptionType; + inherit (asserts) + assertMsg assertOneOf; + inherit (debug) addErrorContextToAttrs traceIf traceVal traceValFn + traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq + traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal + traceShowValMarked showVal traceCall traceCall2 traceCall3 + traceValIfNot runTests testAllTrue traceCallXml attrNamesToStr; + inherit (misc) maybeEnv defaultMergeArg defaultMerge foldArgs + defaultOverridableDelayableArgs composedArgsAndFun + maybeAttrNullable maybeAttr ifEnable checkFlag getValue + checkReqs uniqList uniqListExt condConcat lazyGenericClosure + innerModifySumArgs modifySumArgs innerClosePropagation + closePropagation mapAttrsFlatten nvs setAttr setAttrMerge + mergeAttrsWithFunc mergeAttrsConcatenateValues + mergeAttrsNoOverride mergeAttrByFunc mergeAttrsByFuncDefaults + mergeAttrsByFuncDefaultsClean mergeAttrBy prepareDerivationArgs + nixType imap overridableDelayableArgs; + }); +in lib diff --git a/nixpkgs/lib/deprecated.nix b/nixpkgs/lib/deprecated.nix new file mode 100644 index 000000000000..34cf336d1f42 --- /dev/null +++ b/nixpkgs/lib/deprecated.nix @@ -0,0 +1,393 @@ +{ lib }: +let + inherit (builtins) head tail isList isAttrs isInt attrNames; + +in + +with lib.lists; +with lib.attrsets; +with lib.strings; + +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; + }; + 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 lib.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 = args.${n}; + in if isAttrs x then (mergeFun args x) + else assert lib.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 = maybeAttr; + + # shortcut for attrByPath ["name"] default attrs + maybeAttr = name: default: attrs: attrs.${name} or 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 [] []; + + 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 r.${attr}) (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 set ? ${n} + then setAttr set n (f set.${n} set2.${n}) + 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 exists 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 doesn't 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 set ? ${n} + then # merge + if elem n mergeLists # attribute contains list, merge them by concatenating + then attrs2.${n} ++ attrs1.${n} + else if elem n overrideSnd + then attrs1.${n} + else throw "error mergeAttrsNoOverride, attribute ${n} given in both attributes - no merge func defined" + else attrs2.${n} # 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 x ? ${a} + then if y ? ${a} + then v x.${a} y.${a} # both have attr, use merge func + else x.${a} # only x has attr + else y.${a} # only y has attr) + ) (removeAttrs mergeAttrBy2 + # don't merge attrs which are neither in x nor y + (filter (a: ! x ? ${a} && ! y ? ${a}) + (attrNames mergeAttrBy2)) + ) + ) + ]; + mergeAttrsByFuncDefaults = foldl mergeAttrByFunc { inherit mergeAttrBy; }; + mergeAttrsByFuncDefaultsClean = list: removeAttrs (mergeAttrsByFuncDefaults list) ["mergeAttrBy"]; + + # 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 cfgWithDefaults.${flagName a} 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 "attrs" + else if lib.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"; + + /* deprecated: + + For historical reasons, imap has an index starting at 1. + + But for consistency with the rest of the library we want an index + starting at zero. + */ + imap = imap1; +} diff --git a/nixpkgs/lib/fetchers.nix b/nixpkgs/lib/fetchers.nix new file mode 100644 index 000000000000..1107353b51dd --- /dev/null +++ b/nixpkgs/lib/fetchers.nix @@ -0,0 +1,13 @@ +# snippets that can be shared by multiple fetchers (pkgs/build-support) +{ lib }: +{ + + proxyImpureEnvVars = [ + # We borrow these environment variables from the caller to allow + # easy proxy configuration. This is impure, but a fixed-output + # derivation like fetchurl is allowed to do so since its result is + # by definition pure. + "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy" + ]; + +} diff --git a/nixpkgs/lib/filesystem.nix b/nixpkgs/lib/filesystem.nix new file mode 100644 index 000000000000..fc35a1a72c64 --- /dev/null +++ b/nixpkgs/lib/filesystem.nix @@ -0,0 +1,45 @@ +{ lib }: +{ # haskellPathsInDir : Path -> Map String Path + # A map of all haskell packages defined in the given path, + # identified by having a cabal file with the same name as the + # directory itself. + haskellPathsInDir = root: + let # Files in the root + root-files = builtins.attrNames (builtins.readDir root); + # Files with their full paths + root-files-with-paths = + map (file: + { name = file; value = root + "/${file}"; } + ) root-files; + # Subdirectories of the root with a cabal file. + cabal-subdirs = + builtins.filter ({ name, value }: + builtins.pathExists (value + "/${name}.cabal") + ) root-files-with-paths; + in builtins.listToAttrs cabal-subdirs; + # locateDominatingFile : RegExp + # -> Path + # -> Nullable { path : Path; + # matches : [ MatchResults ]; + # } + # Find the first directory containing a file matching 'pattern' + # upward from a given 'file'. + # Returns 'null' if no directories contain a file matching 'pattern'. + locateDominatingFile = pattern: file: + let go = path: + let files = builtins.attrNames (builtins.readDir path); + matches = builtins.filter (match: match != null) + (map (builtins.match pattern) files); + in + if builtins.length matches != 0 + then { inherit path matches; } + else if path == /. + then null + else go (dirOf path); + parent = dirOf file; + isDir = + let base = baseNameOf file; + type = (builtins.readDir parent).${base} or null; + in file == /. || type == "directory"; + in go (if isDir then file else parent); +} diff --git a/nixpkgs/lib/fixed-points.nix b/nixpkgs/lib/fixed-points.nix new file mode 100644 index 000000000000..2f818c88de5d --- /dev/null +++ b/nixpkgs/lib/fixed-points.nix @@ -0,0 +1,101 @@ +{ ... }: +rec { + # Compute the fixed point of the given function `f`, which is usually an + # attribute set that expects its final, non-recursive representation as an + # argument: + # + # f = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } + # + # Nix evaluates this recursion until all references to `self` have been + # resolved. At that point, the final result is returned and `f x = x` holds: + # + # nix-repl> fix f + # { bar = "bar"; foo = "foo"; foobar = "foobar"; } + # + # Type: fix :: (a -> a) -> a + # + # See https://en.wikipedia.org/wiki/Fixed-point_combinator for further + # details. + fix = f: let x = f x; in x; + + # A variant of `fix` that records the original recursive attribute set in the + # result. This is useful in combination with the `extends` function to + # implement deep overriding. See pkgs/development/haskell-modules/default.nix + # for a concrete example. + fix' = f: let x = f x // { __unfix__ = f; }; in x; + + # Return the fixpoint that `f` converges to when called recursively, starting + # with the input `x`. + # + # nix-repl> converge (x: x / 2) 16 + # 0 + converge = f: x: + if (f x) == x + then x + else converge f (f x); + + # Modify the contents of an explicitly recursive attribute set in a way that + # honors `self`-references. This is accomplished with a function + # + # g = self: super: { foo = super.foo + " + "; } + # + # that has access to the unmodified input (`super`) as well as the final + # non-recursive representation of the attribute set (`self`). `extends` + # differs from the native `//` operator insofar as that it's applied *before* + # references to `self` are resolved: + # + # nix-repl> fix (extends g f) + # { bar = "bar"; foo = "foo + "; foobar = "foo + bar"; } + # + # The name of the function is inspired by object-oriented inheritance, i.e. + # think of it as an infix operator `g extends f` that mimics the syntax from + # Java. It may seem counter-intuitive to have the "base class" as the second + # argument, but it's nice this way if several uses of `extends` are cascaded. + # + # To get a better understanding how `extends` turns a function with a fix + # point (the package set we start with) into a new function with a different fix + # point (the desired packages set) lets just see, how `extends g f` + # unfolds with `g` and `f` defined above: + # + # extends g f = self: let super = f self; in super // g self super; + # = self: let super = { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }; in super // g self super + # = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // g self { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } + # = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // { foo = "foo" + " + "; } + # = self: { foo = "foo + "; bar = "bar"; foobar = self.foo + self.bar; } + # + extends = f: rattrs: self: let super = rattrs self; in super // f self super; + + # Compose two extending functions of the type expected by 'extends' + # into one where changes made in the first are available in the + # 'super' of the second + composeExtensions = + f: g: self: super: + let fApplied = f self super; + super' = super // fApplied; + in fApplied // g self super'; + + # Create an overridable, recursive attribute set. For example: + # + # nix-repl> obj = makeExtensible (self: { }) + # + # nix-repl> obj + # { __unfix__ = «lambda»; extend = «lambda»; } + # + # nix-repl> obj = obj.extend (self: super: { foo = "foo"; }) + # + # nix-repl> obj + # { __unfix__ = «lambda»; extend = «lambda»; foo = "foo"; } + # + # nix-repl> obj = obj.extend (self: super: { foo = super.foo + " + "; bar = "bar"; foobar = self.foo + self.bar; }) + # + # nix-repl> obj + # { __unfix__ = «lambda»; bar = "bar"; extend = «lambda»; foo = "foo + "; foobar = "foo + bar"; } + makeExtensible = makeExtensibleWithCustomName "extend"; + + # Same as `makeExtensible` but the name of the extending attribute is + # customized. + makeExtensibleWithCustomName = extenderName: rattrs: + fix' rattrs // { + ${extenderName} = f: makeExtensibleWithCustomName extenderName (extends f rattrs); + }; +} diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix new file mode 100644 index 000000000000..863ba847423e --- /dev/null +++ b/nixpkgs/lib/generators.nix @@ -0,0 +1,227 @@ +/* Functions that generate widespread file + * formats from nix data structures. + * + * They all follow a similar interface: + * generator { config-attrs } data + * + * `config-attrs` are “holes” in the generators + * with sensible default implementations that + * can be overwritten. The default implementations + * are mostly generators themselves, called with + * their respective default values; they can be reused. + * + * Tests can be found in ./tests.nix + * Documentation in the manual, #sec-generators + */ +{ lib }: +with (lib).trivial; +let + libStr = lib.strings; + libAttr = lib.attrsets; + + inherit (lib) isFunction; +in + +rec { + + ## -- HELPER FUNCTIONS & DEFAULTS -- + + /* Convert a value to a sensible default string representation. + * The builtin `toString` function has some strange defaults, + * suitable for bash scripts but not much else. + */ + mkValueStringDefault = {}: v: with builtins; + let err = t: v: abort + ("generators.mkValueStringDefault: " + + "${t} not supported: ${toPretty {} v}"); + in if isInt v then toString v + # we default to not quoting strings + else if isString v then v + # isString returns "1", which is not a good default + else if true == v then "true" + # here it returns to "", which is even less of a good default + else if false == v then "false" + else if null == v then "null" + # if you have lists you probably want to replace this + else if isList v then err "lists" v + # same as for lists, might want to replace + else if isAttrs v then err "attrsets" v + else if isFunction v then err "functions" v + else err "this value is" (toString v); + + + /* Generate a line of key k and value v, separated by + * character sep. If sep appears in k, it is escaped. + * Helper for synaxes with different separators. + * + * mkValueString specifies how values should be formatted. + * + * mkKeyValueDefault {} ":" "f:oo" "bar" + * > "f\:oo:bar" + */ + mkKeyValueDefault = { + mkValueString ? mkValueStringDefault {} + }: sep: k: v: + "${libStr.escape [sep] k}${sep}${mkValueString v}"; + + + ## -- FILE FORMAT GENERATORS -- + + + /* Generate a key-value-style config file from an attrset. + * + * mkKeyValue is the same as in toINI. + */ + toKeyValue = { + mkKeyValue ? mkKeyValueDefault {} "=" + }: attrs: + let mkLine = k: v: mkKeyValue k v + "\n"; + in libStr.concatStrings (libAttr.mapAttrsToList mkLine attrs); + + + /* Generate an INI-style config file from an + * attrset of sections to an attrset of key-value pairs. + * + * generators.toINI {} { + * foo = { hi = "${pkgs.hello}"; ciao = "bar"; }; + * baz = { "also, integers" = 42; }; + * } + * + *> [baz] + *> also, integers=42 + *> + *> [foo] + *> ciao=bar + *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10 + * + * The mk* configuration attributes can generically change + * the way sections and key-value strings are generated. + * + * For more examples see the test cases in ./tests.nix. + */ + toINI = { + # apply transformations (e.g. escapes) to section names + mkSectionName ? (name: libStr.escape [ "[" "]" ] name), + # format a setting line from key and value + mkKeyValue ? mkKeyValueDefault {} "=" + }: attrsOfAttrs: + let + # map function to string for each key val + mapAttrsToStringsSep = sep: mapFn: attrs: + libStr.concatStringsSep sep + (libAttr.mapAttrsToList mapFn attrs); + mkSection = sectName: sectValues: '' + [${mkSectionName sectName}] + '' + toKeyValue { inherit mkKeyValue; } sectValues; + in + # map input to ini sections + mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; + + + /* Generates JSON from an arbitrary (non-function) value. + * For more information see the documentation of the builtin. + */ + toJSON = {}: builtins.toJSON; + + + /* YAML has been a strict superset of JSON since 1.2, so we + * use toJSON. Before it only had a few differences referring + * to implicit typing rules, so it should work with older + * parsers as well. + */ + toYAML = {}@args: toJSON args; + + + /* Pretty print a value, akin to `builtins.trace`. + * Should probably be a builtin as well. + */ + toPretty = { + /* If this option is true, attrsets like { __pretty = fn; val = …; } + will use fn to convert val to a pretty printed representation. + (This means fn is type Val -> String.) */ + allowPrettyValues ? false + }@args: v: with builtins; + let isPath = v: typeOf v == "path"; + in if isInt v then toString v + else if isFloat v then "~${toString v}" + else if isString v then ''"${libStr.escape [''"''] v}"'' + else if true == v then "true" + else if false == v then "false" + else if null == v then "null" + else if isPath v then toString v + else if isList v then "[ " + + libStr.concatMapStringsSep " " (toPretty args) v + + " ]" + else if isAttrs v then + # apply pretty values if allowed + if attrNames v == [ "__pretty" "val" ] && allowPrettyValues + then v.__pretty v.val + # TODO: there is probably a better representation? + else if v ? type && v.type == "derivation" then + "<δ:${v.name}>" + # "<δ:${concatStringsSep "," (builtins.attrNames v)}>" + else "{ " + + libStr.concatStringsSep " " (libAttr.mapAttrsToList + (name: value: + "${toPretty args name} = ${toPretty args value};") v) + + " }" + else if isFunction v then + let fna = lib.functionArgs v; + showFnas = concatStringsSep "," (libAttr.mapAttrsToList + (name: hasDefVal: if hasDefVal then "(${name})" else name) + fna); + in if fna == {} then "<λ>" + else "<λ:{${showFnas}}>" + else abort "generators.toPretty: should never happen (v = ${v})"; + + # PLIST handling + toPlist = {}: v: let + isFloat = builtins.isFloat or (x: false); + expr = ind: x: with builtins; + if isNull x then "" else + if isBool x then bool ind x else + if isInt x then int ind x else + if isString x then str ind x else + if isList x then list ind x else + if isAttrs x then attrs ind x else + if isFloat x then float ind x else + abort "generators.toPlist: should never happen (v = ${v})"; + + literal = ind: x: ind + x; + + bool = ind: x: literal ind (if x then "<true/>" else "<false/>"); + int = ind: x: literal ind "<integer>${toString x}</integer>"; + str = ind: x: literal ind "<string>${x}</string>"; + key = ind: x: literal ind "<key>${x}</key>"; + float = ind: x: literal ind "<real>${toString x}</real>"; + + indent = ind: expr "\t${ind}"; + + item = ind: libStr.concatMapStringsSep "\n" (indent ind); + + list = ind: x: libStr.concatStringsSep "\n" [ + (literal ind "<array>") + (item ind x) + (literal ind "</array>") + ]; + + attrs = ind: x: libStr.concatStringsSep "\n" [ + (literal ind "<dict>") + (attr ind x) + (literal ind "</dict>") + ]; + + attr = let attrFilter = name: value: name != "_module" && value != null; + in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList + (name: value: lib.optional (attrFilter name value) [ + (key "\t${ind}" name) + (expr "\t${ind}" value) + ]) x)); + + in ''<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +${expr "" v} +</plist>''; + +} diff --git a/nixpkgs/lib/kernel.nix b/nixpkgs/lib/kernel.nix new file mode 100644 index 000000000000..45b33aea7b87 --- /dev/null +++ b/nixpkgs/lib/kernel.nix @@ -0,0 +1,57 @@ +{ lib +# we pass the kernel version here to keep a nice syntax `whenOlder "4.13"` +# kernelVersion, e.g., config.boot.kernelPackages.version +, version +, mkValuePreprocess ? null +}: + +with lib; +rec { + # Common patterns + when = cond: opt: if cond then opt else null; + whenAtLeast = ver: when (versionAtLeast version ver); + whenOlder = ver: when (versionOlder version ver); + whenBetween = verLow: verHigh: when (versionAtLeast version verLow && versionOlder version verHigh); + + # Keeping these around in case we decide to change this horrible implementation :) + option = x: if x == null then null else "?${x}"; + yes = "y"; + no = "n"; + module = "m"; + + mkValue = val: + let + isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"]; + in + if val == "" then "\"\"" + else if val == yes || val == module || val == no then val + else if all isNumber (stringToCharacters val) then val + else if substring 0 2 val == "0x" then val + else val; # FIXME: fix quoting one day + + + # generate nix intermediate kernel config file of the form + # + # VIRTIO_MMIO m + # VIRTIO_BLK y + # VIRTIO_CONSOLE n + # NET_9P_VIRTIO? y + # + # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as + # 'yes' or vice-versa + # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158 + # returns a string, expr should be an attribute set + generateNixKConf = exprs: mkValuePreprocess: + let + mkConfigLine = key: rawval: + let + val = if builtins.isFunction mkValuePreprocess then mkValuePreprocess rawval else rawval; + in + if val == null + then "" + else if hasPrefix "?" val + then "${key}? ${mkValue (removePrefix "?" val)}\n" + else "${key} ${mkValue val}\n"; + mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg); + in mkConf exprs; +} diff --git a/nixpkgs/lib/licenses.nix b/nixpkgs/lib/licenses.nix new file mode 100644 index 000000000000..fc9cb42621d4 --- /dev/null +++ b/nixpkgs/lib/licenses.nix @@ -0,0 +1,732 @@ +{ lib }: +let + + spdx = lic: lic // { + url = "http://spdx.org/licenses/${lic.spdxId}.html"; + }; + +in + +lib.mapAttrs (n: v: v // { shortName = n; }) rec { + /* License identifiers from spdx.org where possible. + * 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. + */ + + abstyles = spdx { + spdxId = "Abstyles"; + fullName = "Abstyles License"; + }; + + afl21 = spdx { + spdxId = "AFL-2.1"; + fullName = "Academic Free License v2.1"; + }; + + afl3 = spdx { + spdxId = "AFL-3.0"; + fullName = "Academic Free License v3.0"; + }; + + agpl3 = spdx { + spdxId = "AGPL-3.0-only"; + fullName = "GNU Affero General Public License v3.0 only"; + }; + + agpl3Plus = spdx { + spdxId = "AGPL-3.0-or-later"; + fullName = "GNU Affero General Public License v3.0 or later"; + }; + + amazonsl = { + fullName = "Amazon Software License"; + url = http://aws.amazon.com/asl/; + free = false; + }; + + amd = { + fullName = "AMD License Agreement"; + url = http://developer.amd.com/amd-license-agreement/; + free = false; + }; + + apsl20 = spdx { + spdxId = "APSL-2.0"; + fullName = "Apple Public Source License 2.0"; + }; + + arphicpl = { + fullName = "Arphic Public License"; + url = https://www.freedesktop.org/wiki/Arphic_Public_License/; + }; + + artistic1 = spdx { + spdxId = "Artistic-1.0"; + fullName = "Artistic License 1.0"; + }; + + artistic2 = spdx { + spdxId = "Artistic-2.0"; + fullName = "Artistic License 2.0"; + }; + + asl20 = spdx { + spdxId = "Apache-2.0"; + fullName = "Apache License 2.0"; + }; + + boost = spdx { + spdxId = "BSL-1.0"; + fullName = "Boost Software License 1.0"; + }; + + beerware = spdx { + spdxId = "Beerware"; + fullName = ''Beerware License''; + }; + + bsd0 = spdx { + spdxId = "0BSD"; + fullName = "BSD Zero Clause License"; + }; + + bsd2 = spdx { + spdxId = "BSD-2-Clause"; + fullName = ''BSD 2-clause "Simplified" License''; + }; + + bsd3 = spdx { + spdxId = "BSD-3-Clause"; + fullName = ''BSD 3-clause "New" or "Revised" License''; + }; + + bsdOriginal = spdx { + spdxId = "BSD-4-Clause"; + fullName = ''BSD 4-clause "Original" or "Old" License''; + }; + + bsl11 = { + fullName = "Business Source License 1.1"; + url = https://mariadb.com/bsl11; + free = false; + }; + + clArtistic = spdx { + spdxId = "ClArtistic"; + fullName = "Clarified Artistic License"; + }; + + cc0 = spdx { + spdxId = "CC0-1.0"; + fullName = "Creative Commons Zero v1.0 Universal"; + }; + + cc-by-nc-sa-20 = spdx { + spdxId = "CC-BY-NC-SA-2.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 2.0"; + free = false; + }; + + cc-by-nc-sa-25 = spdx { + spdxId = "CC-BY-NC-SA-2.5"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 2.5"; + free = false; + }; + + cc-by-nc-sa-30 = spdx { + spdxId = "CC-BY-NC-SA-3.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 3.0"; + free = false; + }; + + cc-by-nc-sa-40 = spdx { + spdxId = "CC-BY-NC-SA-4.0"; + fullName = "Creative Commons Attribution Non Commercial Share Alike 4.0"; + free = false; + }; + + cc-by-nc-40 = spdx { + spdxId = "CC-BY-NC-4.0"; + fullName = "Creative Commons Attribution Non Commercial 4.0 International"; + free = false; + }; + + cc-by-nd-30 = spdx { + spdxId = "CC-BY-ND-3.0"; + fullName = "Creative Commons Attribution-No Derivative Works v3.00"; + free = false; + }; + + cc-by-sa-25 = spdx { + spdxId = "CC-BY-SA-2.5"; + fullName = "Creative Commons Attribution Share Alike 2.5"; + }; + + cc-by-30 = spdx { + spdxId = "CC-BY-3.0"; + fullName = "Creative Commons Attribution 3.0"; + }; + + cc-by-sa-30 = spdx { + spdxId = "CC-BY-SA-3.0"; + fullName = "Creative Commons Attribution Share Alike 3.0"; + }; + + cc-by-40 = spdx { + spdxId = "CC-BY-4.0"; + fullName = "Creative Commons Attribution 4.0"; + }; + + cc-by-sa-40 = spdx { + spdxId = "CC-BY-SA-4.0"; + fullName = "Creative Commons Attribution Share Alike 4.0"; + }; + + cddl = spdx { + spdxId = "CDDL-1.0"; + fullName = "Common Development and Distribution License 1.0"; + }; + + cecill20 = spdx { + spdxId = "CECILL-2.0"; + fullName = "CeCILL Free Software License Agreement v2.0"; + }; + + cecill-b = spdx { + spdxId = "CECILL-B"; + fullName = "CeCILL-B Free Software License Agreement"; + }; + + cecill-c = spdx { + spdxId = "CECILL-C"; + fullName = "CeCILL-C Free Software License Agreement"; + }; + + cpal10 = spdx { + spdxId = "CPAL-1.0"; + fullName = "Common Public Attribution License 1.0"; + }; + + cpl10 = spdx { + spdxId = "CPL-1.0"; + fullName = "Common Public License 1.0"; + }; + + curl = { + fullName = "MIT/X11 derivate"; + url = "https://curl.haxx.se/docs/copyright.html"; + }; + + doc = spdx { + spdxId = "DOC"; + fullName = "DOC License"; + }; + + eapl = { + fullName = "EPSON AVASYS PUBLIC LICENSE"; + url = http://avasys.jp/hp/menu000000700/hpg000000603.htm; + free = false; + }; + + efl10 = spdx { + spdxId = "EFL-1.0"; + fullName = "Eiffel Forum License v1.0"; + }; + + efl20 = spdx { + spdxId = "EFL-2.0"; + fullName = "Eiffel Forum License v2.0"; + }; + + elastic = { + fullName = "ELASTIC LICENSE"; + url = https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt; + free = false; + }; + + epl10 = spdx { + spdxId = "EPL-1.0"; + fullName = "Eclipse Public License 1.0"; + }; + + epl20 = spdx { + spdxId = "EPL-2.0"; + fullName = "Eclipse Public License 2.0"; + }; + + epson = { + fullName = "Seiko Epson Corporation Software License Agreement for Linux"; + url = https://download.ebz.epson.net/dsc/du/02/eula/global/LINUX_EN.html; + free = false; + }; + + eupl11 = spdx { + spdxId = "EUPL-1.1"; + fullName = "European Union Public License 1.1"; + }; + + fdl12 = spdx { + spdxId = "GFDL-1.2-only"; + fullName = "GNU Free Documentation License v1.2 only"; + }; + + fdl12Plus = spdx { + spdxId = "GFDL-1.2-or-later"; + fullName = "GNU Free Documentation License v1.2 or later"; + }; + + fdl13 = spdx { + spdxId = "GFDL-1.3-only"; + fullName = "GNU Free Documentation License v1.3 only"; + }; + + fdl13Plus = spdx { + spdxId = "GFDL-1.3-or-later"; + fullName = "GNU Free Documentation License v1.3 or later"; + }; + + ffsl = { + fullName = "Floodgap Free Software License"; + url = http://www.floodgap.com/software/ffsl/license.html; + free = false; + }; + + free = { + fullName = "Unspecified free software license"; + }; + + g4sl = { + fullName = "Geant4 Software License"; + url = https://geant4.web.cern.ch/geant4/license/LICENSE.html; + }; + + geogebra = { + fullName = "GeoGebra Non-Commercial License Agreement"; + url = https://www.geogebra.org/license; + free = false; + }; + + gpl1 = spdx { + spdxId = "GPL-1.0-only"; + fullName = "GNU General Public License v1.0 only"; + }; + + gpl1Plus = spdx { + spdxId = "GPL-1.0-or-later"; + fullName = "GNU General Public License v1.0 or later"; + }; + + gpl2 = spdx { + spdxId = "GPL-2.0-only"; + fullName = "GNU General Public License v2.0 only"; + }; + + gpl2Classpath = spdx { + spdxId = "GPL-2.0-with-classpath-exception"; + fullName = "GNU General Public License v2.0 only (with Classpath exception)"; + }; + + gpl2ClasspathPlus = { + fullName = "GNU General Public License v2.0 or later (with Classpath exception)"; + url = https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception; + }; + + gpl2Oss = { + fullName = "GNU General Public License version 2 only (with OSI approved licenses linking exception)"; + url = https://www.mysql.com/about/legal/licensing/foss-exception; + }; + + gpl2Plus = spdx { + spdxId = "GPL-2.0-or-later"; + fullName = "GNU General Public License v2.0 or later"; + }; + + gpl3 = spdx { + spdxId = "GPL-3.0-only"; + fullName = "GNU General Public License v3.0 only"; + }; + + gpl3Plus = spdx { + spdxId = "GPL-3.0-or-later"; + fullName = "GNU General Public License v3.0 or later"; + }; + + gpl3ClasspathPlus = { + fullName = "GNU General Public License v3.0 or later (with Classpath exception)"; + url = https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception; + }; + + hpnd = spdx { + spdxId = "HPND"; + fullName = "Historic Permission Notice and Disclaimer"; + }; + + # Intel's license, seems free + iasl = { + fullName = "iASL"; + url = http://www.calculate-linux.org/packages/licenses/iASL; + }; + + ijg = spdx { + spdxId = "IJG"; + fullName = "Independent JPEG Group License"; + }; + + imagemagick = spdx { + fullName = "ImageMagick License"; + spdxId = "imagemagick"; + }; + + inria-compcert = { + fullName = "INRIA Non-Commercial License Agreement for the CompCert verified compiler"; + url = "http://compcert.inria.fr/doc/LICENSE"; + free = false; + }; + + inria-icesl = { + fullName = "INRIA Non-Commercial License Agreement for IceSL"; + url = "http://shapeforge.loria.fr/icesl/EULA_IceSL_binary.pdf"; + free = false; + }; + + ipa = spdx { + spdxId = "IPA"; + fullName = "IPA Font License"; + }; + + ipl10 = spdx { + spdxId = "IPL-1.0"; + fullName = "IBM Public License v1.0"; + }; + + isc = spdx { + spdxId = "ISC"; + fullName = "ISC License"; + }; + + # Proprietary binaries; free to redistribute without modification. + issl = { + fullName = "Intel Simplified Software License"; + url = https://software.intel.com/en-us/license/intel-simplified-software-license; + free = false; + }; + + jasper = spdx { + spdxId = "JasPer-2.0"; + fullName = "JasPer License"; + }; + + lgpl2 = spdx { + spdxId = "LGPL-2.0-only"; + fullName = "GNU Library General Public License v2 only"; + }; + + lgpl2Plus = spdx { + spdxId = "LGPL-2.0-or-later"; + fullName = "GNU Library General Public License v2 or later"; + }; + + lgpl21 = spdx { + spdxId = "LGPL-2.1-only"; + fullName = "GNU Library General Public License v2.1 only"; + }; + + lgpl21Plus = spdx { + spdxId = "LGPL-2.1-or-later"; + fullName = "GNU Library General Public License v2.1 or later"; + }; + + lgpl3 = spdx { + spdxId = "LGPL-3.0-only"; + fullName = "GNU Lesser General Public License v3.0 only"; + }; + + lgpl3Plus = spdx { + spdxId = "LGPL-3.0-or-later"; + fullName = "GNU Lesser General Public License v3.0 or later"; + }; + + libpng = spdx { + spdxId = "Libpng"; + fullName = "libpng License"; + }; + + libtiff = spdx { + spdxId = "libtiff"; + fullName = "libtiff License"; + }; + + llgpl21 = { + 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; + }; + + lppl12 = spdx { + spdxId = "LPPL-1.2"; + fullName = "LaTeX Project Public License v1.2"; + }; + + lppl13c = spdx { + spdxId = "LPPL-1.3c"; + fullName = "LaTeX Project Public License v1.3c"; + }; + + lpl-102 = spdx { + spdxId = "LPL-1.02"; + fullName = "Lucent Public License v1.02"; + }; + + miros = { + fullName = "MirOS License"; + url = https://opensource.org/licenses/MirOS; + }; + + # spdx.org does not (yet) differentiate between the X11 and Expat versions + # for details see http://en.wikipedia.org/wiki/MIT_License#Various_versions + mit = spdx { + spdxId = "MIT"; + fullName = "MIT License"; + }; + + mpl10 = spdx { + spdxId = "MPL-1.0"; + fullName = "Mozilla Public License 1.0"; + }; + + mpl11 = spdx { + spdxId = "MPL-1.1"; + fullName = "Mozilla Public License 1.1"; + }; + + mpl20 = spdx { + spdxId = "MPL-2.0"; + fullName = "Mozilla Public License 2.0"; + }; + + mspl = spdx { + spdxId = "MS-PL"; + fullName = "Microsoft Public License"; + }; + + msrla = { + fullName = "Microsoft Research License Agreement"; + url = "http://research.microsoft.com/en-us/projects/pex/msr-la.txt"; + free = false; + }; + + nasa13 = spdx { + spdxId = "NASA-1.3"; + fullName = "NASA Open Source Agreement 1.3"; + free = false; + }; + + ncsa = spdx { + spdxId = "NCSA"; + fullName = "University of Illinois/NCSA Open Source License"; + }; + + notion_lgpl = { + url = "https://raw.githubusercontent.com/raboof/notion/master/LICENSE"; + fullName = "Notion modified LGPL"; + }; + + nposl3 = spdx { + spdxId = "NPOSL-3.0"; + fullName = "Non-Profit Open Software License 3.0"; + }; + + ocamlpro_nc = { + fullName = "OCamlPro Non Commercial license version 1"; + url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf"; + free = false; + }; + + ofl = spdx { + spdxId = "OFL-1.1"; + fullName = "SIL Open Font License 1.1"; + }; + + openldap = spdx { + spdxId = "OLDAP-2.8"; + fullName = "Open LDAP Public License v2.8"; + }; + + openssl = spdx { + spdxId = "OpenSSL"; + fullName = "OpenSSL License"; + }; + + osl21 = spdx { + spdxId = "OSL-2.1"; + fullName = "Open Software License 2.1"; + }; + + osl3 = spdx { + spdxId = "OSL-3.0"; + fullName = "Open Software License 3.0"; + }; + + php301 = spdx { + spdxId = "PHP-3.01"; + fullName = "PHP License v3.01"; + }; + + postgresql = spdx { + spdxId = "PostgreSQL"; + fullName = "PostgreSQL License"; + }; + + postman = { + fullName = "Postman EULA"; + url = https://www.getpostman.com/licenses/postman_base_app; + free = false; + }; + + psfl = spdx { + spdxId = "Python-2.0"; + fullName = "Python Software Foundation License version 2"; + #url = http://docs.python.org/license.html; + }; + + publicDomain = { + fullName = "Public Domain"; + }; + + purdueBsd = { + fullName = " Purdue BSD-Style License"; # also know as lsof license + url = https://enterprise.dejacode.com/licenses/public/purdue-bsd; + }; + + qpl = spdx { + spdxId = "QPL-1.0"; + fullName = "Q Public License 1.0"; + }; + + qwt = { + fullName = "Qwt License, Version 1.0"; + url = http://qwt.sourceforge.net/qwtlicense.html; + }; + + ruby = spdx { + spdxId = "Ruby"; + fullName = "Ruby License"; + }; + + sendmail = spdx { + spdxId = "Sendmail"; + fullName = "Sendmail License"; + }; + + sgi-b-20 = spdx { + spdxId = "SGI-B-2.0"; + fullName = "SGI Free Software License B v2.0"; + }; + + sleepycat = spdx { + spdxId = "Sleepycat"; + fullName = "Sleepycat License"; + }; + + smail = { + shortName = "smail"; + fullName = "SMAIL General Public License"; + url = http://metadata.ftp-master.debian.org/changelogs/main/d/debianutils/debianutils_4.8.1_copyright; + }; + + tcltk = spdx { + spdxId = "TCL"; + fullName = "TCL/TK License"; + }; + + ufl = { + fullName = "Ubuntu Font License 1.0"; + url = http://font.ubuntu.com/ufl/ubuntu-font-licence-1.0.txt; + }; + + unfree = { + fullName = "Unfree"; + free = false; + }; + + unfreeRedistributable = { + fullName = "Unfree redistributable"; + free = false; + }; + + unfreeRedistributableFirmware = { + fullName = "Unfree redistributable firmware"; + # Note: we currently consider these "free" for inclusion in the + # channel and NixOS images. + }; + + unlicense = spdx { + spdxId = "Unlicense"; + fullName = "The Unlicense"; + }; + + upl = { + fullName = "Universal Permissive License"; + url = "https://oss.oracle.com/licenses/upl/"; + }; + + vim = spdx { + spdxId = "Vim"; + fullName = "Vim License"; + }; + + virtualbox-puel = { + fullName = "Oracle VM VirtualBox Extension Pack Personal Use and Evaluation License (PUEL)"; + url = "https://www.virtualbox.org/wiki/VirtualBox_PUEL"; + free = false; + }; + + vsl10 = spdx { + spdxId = "VSL-1.0"; + fullName = "Vovida Software License v1.0"; + }; + + watcom = spdx { + spdxId = "Watcom-1.0"; + fullName = "Sybase Open Watcom Public License 1.0"; + }; + + w3c = spdx { + spdxId = "W3C"; + fullName = "W3C Software Notice and License"; + }; + + wadalab = { + fullName = "Wadalab Font License"; + url = https://fedoraproject.org/wiki/Licensing:Wadalab?rd=Licensing/Wadalab; + }; + + wtfpl = spdx { + spdxId = "WTFPL"; + fullName = "Do What The F*ck You Want To Public License"; + }; + + wxWindows = spdx { + spdxId = "wxWindows"; + fullName = "wxWindows Library Licence, Version 3.1"; + }; + + xfig = { + fullName = "xfig"; + url = "http://mcj.sourceforge.net/authors.html#xfig"; + }; + + zlib = spdx { + spdxId = "Zlib"; + fullName = "zlib License"; + }; + + zpl20 = spdx { + spdxId = "ZPL-2.0"; + fullName = "Zope Public License 2.0"; + }; + + zpl21 = spdx { + spdxId = "ZPL-2.1"; + fullName = "Zope Public License 2.1"; + }; +} diff --git a/nixpkgs/lib/lists.nix b/nixpkgs/lib/lists.nix new file mode 100644 index 000000000000..be541427c247 --- /dev/null +++ b/nixpkgs/lib/lists.nix @@ -0,0 +1,663 @@ +# General list operations. + +{ lib }: +with lib.trivial; +let + inherit (lib.strings) toInt; +in +rec { + + inherit (builtins) head tail length isList elemAt concatLists filter elem genList; + + /* 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. + + Type: singleton :: a -> [a] + + Example: + singleton "foo" + => [ "foo" ] + */ + singleton = x: [x]; + + /* “right fold” a binary function `op` between successive elements of + `list` with `nul' as the starting value, i.e., + `foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`. + + Type: foldr :: (a -> b -> b) -> b -> [a] -> b + + Example: + concat = foldr (a: b: a + b) "z" + concat [ "a" "b" "c" ] + => "abcz" + # different types + strange = foldr (int: str: toString (int + 1) + str) "a" + strange [ 1 2 3 4 ] + => "2345a" + */ + foldr = op: nul: list: + let + len = length list; + fold' = n: + if n == len + then nul + else op (elemAt list n) (fold' (n + 1)); + in fold' 0; + + /* `fold` is an alias of `foldr` for historic reasons */ + # FIXME(Profpatsch): deprecate? + fold = foldr; + + + /* “left fold”, like `foldr`, but from the left: + `foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`. + + Type: foldl :: (b -> a -> b) -> b -> [a] -> b + + Example: + lconcat = foldl (a: b: a + b) "z" + lconcat [ "a" "b" "c" ] + => "zabc" + # different types + lstrange = foldl (str: int: str + toString (int + 1)) "" + strange [ 1 2 3 4 ] + => "a2345" + */ + foldl = op: nul: list: + let + foldl' = n: + if n == -1 + then nul + else op (foldl' (n - 1)) (elemAt list n); + in foldl' (length list - 1); + + /* Strict version of `foldl`. + + The difference is that evaluation is forced upon access. Usually used + with small whole results (in contract with lazily-generated list or large + lists where only a part is consumed.) + + Type: foldl' :: (b -> a -> b) -> b -> [a] -> b + */ + foldl' = builtins.foldl' or foldl; + + /* Map with index starting from 0 + + Type: imap0 :: (int -> a -> b) -> [a] -> [b] + + Example: + imap0 (i: v: "${v}-${toString i}") ["a" "b"] + => [ "a-0" "b-1" ] + */ + imap0 = f: list: genList (n: f n (elemAt list n)) (length list); + + /* Map with index starting from 1 + + Type: imap1 :: (int -> a -> b) -> [a] -> [b] + + Example: + imap1 (i: v: "${v}-${toString i}") ["a" "b"] + => [ "a-1" "b-2" ] + */ + imap1 = f: list: genList (n: f (n + 1) (elemAt list n)) (length list); + + /* Map and concatenate the result. + + Type: concatMap :: (a -> [b]) -> [a] -> [b] + + Example: + concatMap (x: [x] ++ ["z"]) ["a" "b"] + => [ "a" "z" "b" "z" ] + */ + concatMap = builtins.concatMap or (f: list: concatLists (map f list)); + + /* Flatten the argument into a single list; that is, nested lists are + spliced into the top-level lists. + + Example: + flatten [1 [2 [3] 4] 5] + => [1 2 3 4 5] + flatten 1 + => [1] + */ + flatten = x: + if isList x + then concatMap (y: flatten y) x + else [x]; + + /* Remove elements equal to 'e' from a list. Useful for buildInputs. + + Type: remove :: a -> [a] -> [a] + + Example: + remove 3 [ 1 3 4 3 ] + => [ 1 4 ] + */ + remove = + # Element to remove from the list + e: filter (x: x != e); + + /* 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. + + Type: findSingle :: (a -> bool) -> a -> a -> [a] -> a + + Example: + findSingle (x: x == 3) "none" "multiple" [ 1 3 3 ] + => "multiple" + findSingle (x: x == 3) "none" "multiple" [ 1 3 ] + => 3 + findSingle (x: x == 3) "none" "multiple" [ 1 9 ] + => "none" + */ + findSingle = + # Predicate + pred: + # Default value to return if element was not found. + default: + # Default value to return if more than one element was found + multiple: + # Input list + 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 return `default` if no such element exists. + + Type: findFirst :: (a -> bool) -> a -> [a] -> a + + Example: + findFirst (x: x > 3) 7 [ 1 6 4 ] + => 6 + findFirst (x: x > 9) 7 [ 1 6 4 ] + => 7 + */ + findFirst = + # Predicate + pred: + # Default value to return + default: + # Input list + list: + let found = filter pred list; + in if found == [] then default else head found; + + /* Return true if function `pred` returns true for at least one + element of `list`. + + Type: any :: (a -> bool) -> [a] -> bool + + Example: + any isString [ 1 "a" { } ] + => true + any isString [ 1 { } ] + => false + */ + any = builtins.any or (pred: foldr (x: y: if pred x then true else y) false); + + /* Return true if function `pred` returns true for all elements of + `list`. + + Type: all :: (a -> bool) -> [a] -> bool + + Example: + all (x: x < 3) [ 1 2 ] + => true + all (x: x < 3) [ 1 2 3 ] + => false + */ + all = builtins.all or (pred: foldr (x: y: if pred x then y else false) true); + + /* Count how many elements of `list` match the supplied predicate + function. + + Type: count :: (a -> bool) -> [a] -> int + + Example: + count (x: x == 3) [ 3 2 3 4 6 ] + => 2 + */ + count = + # Predicate + pred: foldl' (c: x: if pred x then c + 1 else c) 0; + + /* 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'). + + Type: optional :: bool -> a -> [a] + + Example: + optional true "foo" + => [ "foo" ] + optional false "foo" + => [ ] + */ + optional = cond: elem: if cond then [elem] else []; + + /* Return a list or an empty list, depending on a boolean value. + + Type: optionals :: bool -> [a] -> [a] + + Example: + optionals true [ 2 3 ] + => [ 2 3 ] + optionals false [ 2 3 ] + => [ ] + */ + optionals = + # Condition + cond: + # List to return if condition is true + 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. + + Example: + toList [ 1 2 ] + => [ 1 2 ] + toList "hi" + => [ "hi "] + */ + toList = x: if isList x then x else [x]; + + /* Return a list of integers from `first' up to and including `last'. + + Type: range :: int -> int -> [int] + + Example: + range 2 4 + => [ 2 3 4 ] + range 3 2 + => [ ] + */ + range = + # First integer in the range + first: + # Last integer in the range + last: + if first > last then + [] + else + genList (n: first + n) (last - first + 1); + + /* Splits the elements of a list in two lists, `right` and + `wrong`, depending on the evaluation of a predicate. + + Type: (a -> bool) -> [a] -> { right :: [a], wrong :: [a] } + + Example: + partition (x: x > 2) [ 5 1 2 3 4 ] + => { right = [ 5 3 4 ]; wrong = [ 1 2 ]; } + */ + partition = builtins.partition or (pred: + foldr (h: t: + if pred h + then { right = [h] ++ t.right; wrong = t.wrong; } + else { right = t.right; wrong = [h] ++ t.wrong; } + ) { right = []; wrong = []; }); + + /* Splits the elements of a list into many lists, using the return value of a predicate. + Predicate should return a string which becomes keys of attrset `groupBy' returns. + + `groupBy'` allows to customise the combining function and initial value + + Example: + groupBy (x: boolToString (x > 2)) [ 5 1 2 3 4 ] + => { true = [ 5 3 4 ]; false = [ 1 2 ]; } + groupBy (x: x.name) [ {name = "icewm"; script = "icewm &";} + {name = "xfce"; script = "xfce4-session &";} + {name = "icewm"; script = "icewmbg &";} + {name = "mate"; script = "gnome-session &";} + ] + => { icewm = [ { name = "icewm"; script = "icewm &"; } + { name = "icewm"; script = "icewmbg &"; } ]; + mate = [ { name = "mate"; script = "gnome-session &"; } ]; + xfce = [ { name = "xfce"; script = "xfce4-session &"; } ]; + } + + groupBy' builtins.add 0 (x: boolToString (x > 2)) [ 5 1 2 3 4 ] + => { true = 12; false = 3; } + */ + groupBy' = op: nul: pred: lst: + foldl' (r: e: + let + key = pred e; + in + r // { ${key} = op (r.${key} or nul) e; } + ) {} lst; + + groupBy = groupBy' (sum: e: sum ++ [e]) []; + + /* Merges two lists of the same size together. If the sizes aren't the same + the merging stops at the shortest. How both lists are merged is defined + by the first argument. + + Type: zipListsWith :: (a -> b -> c) -> [a] -> [b] -> [c] + + Example: + zipListsWith (a: b: a + b) ["h" "l"] ["e" "o"] + => ["he" "lo"] + */ + zipListsWith = + # Function to zip elements of both lists + f: + # First list + fst: + # Second list + snd: + genList + (n: f (elemAt fst n) (elemAt snd n)) (min (length fst) (length snd)); + + /* Merges two lists of the same size together. If the sizes aren't the same + the merging stops at the shortest. + + Type: zipLists :: [a] -> [b] -> [{ fst :: a, snd :: b}] + + Example: + zipLists [ 1 2 ] [ "a" "b" ] + => [ { fst = 1; snd = "a"; } { fst = 2; snd = "b"; } ] + */ + zipLists = zipListsWith (fst: snd: { inherit fst snd; }); + + /* Reverse the order of the elements of a list. + + Type: reverseList :: [a] -> [a] + + Example: + + reverseList [ "b" "o" "j" ] + => [ "j" "o" "b" ] + */ + reverseList = xs: + let l = length xs; in genList (n: elemAt xs (l - n - 1)) l; + + /* Depth-First Search (DFS) for lists `list != []`. + + `before a b == true` means that `b` depends on `a` (there's an + edge from `b` to `a`). + + Example: + listDfs true hasPrefix [ "/home/user" "other" "/" "/home" ] + == { minimal = "/"; # minimal element + visited = [ "/home/user" ]; # seen elements (in reverse order) + rest = [ "/home" "other" ]; # everything else + } + + listDfs true hasPrefix [ "/home/user" "other" "/" "/home" "/" ] + == { cycle = "/"; # cycle encountered at this element + loops = [ "/" ]; # and continues to these elements + visited = [ "/" "/home/user" ]; # elements leading to the cycle (in reverse order) + rest = [ "/home" "other" ]; # everything else + + */ + listDfs = stopOnCycles: before: list: + let + dfs' = us: visited: rest: + let + c = filter (x: before x us) visited; + b = partition (x: before x us) rest; + in if stopOnCycles && (length c > 0) + then { cycle = us; loops = c; inherit visited rest; } + else if length b.right == 0 + then # nothing is before us + { minimal = us; inherit visited rest; } + else # grab the first one before us and continue + dfs' (head b.right) + ([ us ] ++ visited) + (tail b.right ++ b.wrong); + in dfs' (head list) [] (tail list); + + /* Sort a list based on a partial ordering using DFS. This + implementation is O(N^2), if your ordering is linear, use `sort` + instead. + + `before a b == true` means that `b` should be after `a` + in the result. + + Example: + + toposort hasPrefix [ "/home/user" "other" "/" "/home" ] + == { result = [ "/" "/home" "/home/user" "other" ]; } + + toposort hasPrefix [ "/home/user" "other" "/" "/home" "/" ] + == { cycle = [ "/home/user" "/" "/" ]; # path leading to a cycle + loops = [ "/" ]; } # loops back to these elements + + toposort hasPrefix [ "other" "/home/user" "/home" "/" ] + == { result = [ "other" "/" "/home" "/home/user" ]; } + + toposort (a: b: a < b) [ 3 2 1 ] == { result = [ 1 2 3 ]; } + + */ + toposort = before: list: + let + dfsthis = listDfs true before list; + toporest = toposort before (dfsthis.visited ++ dfsthis.rest); + in + if length list < 2 + then # finish + { result = list; } + else if dfsthis ? "cycle" + then # there's a cycle, starting from the current vertex, return it + { cycle = reverseList ([ dfsthis.cycle ] ++ dfsthis.visited); + inherit (dfsthis) loops; } + else if toporest ? "cycle" + then # there's a cycle somewhere else in the graph, return it + toporest + # Slow, but short. Can be made a bit faster with an explicit stack. + else # there are no cycles + { result = [ dfsthis.minimal ] ++ toporest.result; }; + + /* 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. + + Example: + sort (a: b: a < b) [ 5 3 7 ] + => [ 3 5 7 ] + */ + sort = builtins.sort or ( + strictLess: list: + let + len = length list; + first = head list; + pivot' = n: acc@{ left, right }: let el = elemAt list n; next = pivot' (n + 1); 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 len < 2 then list + else (sort strictLess pivot.left) ++ [ first ] ++ (sort strictLess pivot.right)); + + /* Compare two lists element-by-element. + + Example: + compareLists compare [] [] + => 0 + compareLists compare [] [ "a" ] + => -1 + compareLists compare [ "a" ] [] + => 1 + compareLists compare [ "a" "b" ] [ "a" "c" ] + => 1 + */ + compareLists = cmp: a: b: + if a == [] + then if b == [] + then 0 + else -1 + else if b == [] + then 1 + else let rel = cmp (head a) (head b); in + if rel == 0 + then compareLists cmp (tail a) (tail b) + else rel; + + /* Sort list using "Natural sorting". + Numeric portions of strings are sorted in numeric order. + + Example: + naturalSort ["disk11" "disk8" "disk100" "disk9"] + => ["disk8" "disk9" "disk11" "disk100"] + naturalSort ["10.46.133.149" "10.5.16.62" "10.54.16.25"] + => ["10.5.16.62" "10.46.133.149" "10.54.16.25"] + naturalSort ["v0.2" "v0.15" "v0.0.9"] + => [ "v0.0.9" "v0.2" "v0.15" ] + */ + naturalSort = lst: + let + vectorise = s: map (x: if isList x then toInt (head x) else x) (builtins.split "(0|[1-9][0-9]*)" s); + prepared = map (x: [ (vectorise x) x ]) lst; # remember vectorised version for O(n) regex splits + less = a: b: (compareLists compare (head a) (head b)) < 0; + in + map (x: elemAt x 1) (sort less prepared); + + /* Return the first (at most) N elements of a list. + + Type: take :: int -> [a] -> [a] + + Example: + take 2 [ "a" "b" "c" "d" ] + => [ "a" "b" ] + take 2 [ ] + => [ ] + */ + take = + # Number of elements to take + count: sublist 0 count; + + /* Remove the first (at most) N elements of a list. + + Type: drop :: int -> [a] -> [a] + + Example: + drop 2 [ "a" "b" "c" "d" ] + => [ "c" "d" ] + drop 2 [ ] + => [ ] + */ + drop = + # Number of elements to drop + count: + # Input list + list: sublist count (length list) list; + + /* Return a list consisting of at most `count` elements of `list`, + starting at index `start`. + + Type: sublist :: int -> int -> [a] -> [a] + + Example: + sublist 1 3 [ "a" "b" "c" "d" "e" ] + => [ "b" "c" "d" ] + sublist 1 3 [ ] + => [ ] + */ + sublist = + # Index at which to start the sublist + start: + # Number of elements to take + count: + # Input list + list: + let len = length list; in + genList + (n: elemAt list (n + start)) + (if start >= len then 0 + else if start + count > len then len - start + else count); + + /* Return the last element of a list. + + This function throws an error if the list is empty. + + Type: last :: [a] -> a + + Example: + last [ 1 2 3 ] + => 3 + */ + last = list: + assert lib.assertMsg (list != []) "lists.last: list must not be empty!"; + elemAt list (length list - 1); + + /* Return all elements but the last. + + This function throws an error if the list is empty. + + Type: init :: [a] -> [a] + + Example: + init [ 1 2 3 ] + => [ 1 2 ] + */ + init = list: + assert lib.assertMsg (list != []) "lists.init: list must not be empty!"; + take (length list - 1) list; + + + /* Return the image of the cross product of some lists by a function. + + Example: + crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]] + => [ "13" "14" "23" "24" ] + */ + crossLists = f: foldl (fs: args: concatMap (f: map f args) fs) [f]; + + + /* Remove duplicate elements from the list. O(n^2) complexity. + + Type: unique :: [a] -> [a] + + Example: + unique [ 3 2 3 4 ] + => [ 3 2 4 ] + */ + unique = list: + if list == [] then + [] + else + let + x = head list; + xs = unique (drop 1 list); + in [x] ++ remove x xs; + + /* Intersects list 'e' and another list. O(nm) complexity. + + Example: + intersectLists [ 1 2 3 ] [ 6 3 2 ] + => [ 3 2 ] + */ + intersectLists = e: filter (x: elem x e); + + /* Subtracts list 'e' from another list. O(nm) complexity. + + Example: + subtractLists [ 3 2 ] [ 1 2 3 4 5 3 ] + => [ 1 4 5 ] + */ + subtractLists = e: filter (x: !(elem x e)); + + /* Test if two lists have no common element. + It should be slightly more efficient than (intersectLists a b == []) + */ + mutuallyExclusive = a: b: + (builtins.length a) == 0 || + (!(builtins.elem (builtins.head a) b) && + mutuallyExclusive (builtins.tail a) b); + +} diff --git a/nixpkgs/lib/meta.nix b/nixpkgs/lib/meta.nix new file mode 100644 index 000000000000..199030c103af --- /dev/null +++ b/nixpkgs/lib/meta.nix @@ -0,0 +1,89 @@ +/* Some functions for manipulating meta attributes, as well as the + name attribute. */ + +{ lib }: + +rec { + + + /* Add to or override the meta attributes of the given + derivation. + + Example: + addMetaAttrs {description = "Bla blah";} somePkg + */ + addMetaAttrs = newAttrs: drv: + drv // { meta = (drv.meta or {}) // newAttrs; }; + + + /* Disable Hydra builds of given derivation. + */ + dontDistribute = drv: addMetaAttrs { hydraPlatforms = []; } drv; + + + /* 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 (before the version + part). */ + appendToName = suffix: updateName (name: + let x = builtins.parseDrvName name; in "${x.name}-${suffix}-${x.version}"); + + + /* Apply a function to each derivation and only to derivations in an attrset + */ + mapDerivationAttrset = f: set: lib.mapAttrs (name: pkg: if lib.isDerivation pkg then (f pkg) else pkg) set; + + + /* 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; + + + /* Apply lowPrio to an attrset with derivations + */ + lowPrioSet = set: mapDerivationAttrset lowPrio set; + + + /* 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; + + + /* Apply hiPrio to an attrset with derivations + */ + hiPrioSet = set: mapDerivationAttrset hiPrio set; + + + /* Check to see if a platform is matched by the given `meta.platforms` + element. + + A `meta.platform` pattern is either + + 1. (legacy) a system string. + + 2. (modern) a pattern for the platform `parsed` field. + + We can inject these into a patten for the whole of a structured platform, + and then match that. + */ + platformMatch = platform: elem: let + pattern = + if builtins.isString elem + then { system = elem; } + else { parsed = elem; }; + in lib.matchAttrs pattern platform; +} diff --git a/nixpkgs/lib/minver.nix b/nixpkgs/lib/minver.nix new file mode 100644 index 000000000000..fee6b65a2447 --- /dev/null +++ b/nixpkgs/lib/minver.nix @@ -0,0 +1,2 @@ +# Expose the minimum required version for evaluating Nixpkgs +"2.0" diff --git a/nixpkgs/lib/modules.nix b/nixpkgs/lib/modules.nix new file mode 100644 index 000000000000..5fb83a4a538c --- /dev/null +++ b/nixpkgs/lib/modules.nix @@ -0,0 +1,715 @@ +{ lib }: + +with lib.lists; +with lib.strings; +with lib.trivial; +with lib.attrsets; +with lib.options; +with lib.debug; +with lib.types; + +rec { + + /* Evaluate a set of modules. The result is a set of two + attributes: ‘options’: the nested set of all option declarations, + and ‘config’: the nested set of all option values. + !!! Please think twice before adding to this argument list! The more + that is specified here instead of in the modules themselves the harder + it is to transparently move a set of modules to be a submodule of another + config (as the proper arguments need to be replicated at each call to + evalModules) and the less declarative the module set is. */ + evalModules = { modules + , prefix ? [] + , # This should only be used for special arguments that need to be evaluated + # when resolving module structure (like in imports). For everything else, + # there's _module.args. If specialArgs.modulesPath is defined it will be + # used as the base path for disabledModules. + specialArgs ? {} + , # This would be remove in the future, Prefer _module.args option instead. + args ? {} + , # This would be remove in the future, Prefer _module.check option instead. + check ? true + }: + let + # This internal module declare internal options under the `_module' + # attribute. These options are fragile, as they are used by the + # module system to change the interpretation of modules. + internalModule = rec { + _file = ./modules.nix; + + key = _file; + + options = { + _module.args = mkOption { + type = types.attrsOf types.unspecified; + internal = true; + description = "Arguments passed to each module."; + }; + + _module.check = mkOption { + type = types.bool; + internal = true; + default = check; + description = "Whether to check whether all option definitions have matching declarations."; + }; + }; + + config = { + _module.args = args; + }; + }; + + closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options lib; } // specialArgs); + + options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") closed)); + + # Traverse options and extract the option values into the final + # config set. At the same time, check whether all option + # definitions have matching declarations. + # !!! _module.check's value can't depend on any other config values + # without an infinite recursion. One way around this is to make the + # 'config' passed around to the modules be unconditionally unchecked, + # and only do the check in 'result'. + config = yieldConfig prefix options; + yieldConfig = prefix: set: + let res = removeAttrs (mapAttrs (n: v: + if isOption v then v.value + else yieldConfig (prefix ++ [n]) v) set) ["_definedNames"]; + in + if options._module.check.value && set ? _definedNames then + foldl' (res: m: + foldl' (res: name: + if set ? ${name} then res else throw "The option `${showOption (prefix ++ [name])}' defined in `${m.file}' does not exist.") + res m.names) + res set._definedNames + else + res; + result = { inherit options config; }; + in result; + + + # Filter disabled modules. Modules can be disabled allowing + # their implementation to be replaced. + filterModules = modulesPath: modules: + let + moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; + disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules); + in + filter (m: !(elem m.key disabledKeys)) modules; + + /* Close a set of modules under the ‘imports’ relation. */ + closeModules = modules: args: + let + toClosureList = file: parentKey: imap1 (n: x: + if isAttrs x || isFunction x then + let key = "${parentKey}:anon-${toString n}"; in + unifyModuleSyntax file key (unpackSubmodule (applyIfFunction key) x args) + else + let file = toString x; key = toString x; in + unifyModuleSyntax file key (applyIfFunction key (import x) args)); + in + builtins.genericClosure { + startSet = toClosureList unknownModule "" modules; + operator = m: toClosureList m.file m.key m.imports; + }; + + /* Massage a module into canonical form, that is, a set consisting + of ‘options’, ‘config’ and ‘imports’ attributes. */ + unifyModuleSyntax = file: key: m: + let metaSet = if m ? meta + then { meta = m.meta; } + else {}; + in + if m ? config || m ? options then + let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in + if badAttrs != {} then + throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'." + else + { file = m._file or file; + key = toString m.key or key; + disabledModules = m.disabledModules or []; + imports = m.imports or []; + options = m.options or {}; + config = mkMerge [ (m.config or {}) metaSet ]; + } + else + { file = m._file or file; + key = toString m.key or key; + disabledModules = m.disabledModules or []; + imports = m.require or [] ++ m.imports or []; + options = {}; + config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ]; + }; + + applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then + let + # Module arguments are resolved in a strict manner when attribute set + # deconstruction is used. As the arguments are now defined with the + # config._module.args option, the strictness used on the attribute + # set argument would cause an infinite loop, if the result of the + # option is given as argument. + # + # To work-around the strictness issue on the deconstruction of the + # attributes set argument, we create a new attribute set which is + # constructed to satisfy the expected set of attributes. Thus calling + # a module will resolve strictly the attributes used as argument but + # not their values. The values are forwarding the result of the + # evaluation of the option. + requiredArgs = builtins.attrNames (lib.functionArgs f); + context = name: ''while evaluating the module argument `${name}' in "${key}":''; + extraArgs = builtins.listToAttrs (map (name: { + inherit name; + value = builtins.addErrorContext (context name) + (args.${name} or config._module.args.${name}); + }) requiredArgs); + + # Note: we append in the opposite order such that we can add an error + # context on the explicited arguments of "args" too. This update + # operator is used to make the "args@{ ... }: with args.lib;" notation + # works. + in f (args // extraArgs) + else + f; + + /* We have to pack and unpack submodules. We cannot wrap the expected + result of the function as we would no longer be able to list the arguments + of the submodule. (see applyIfFunction) */ + unpackSubmodule = unpack: m: args: + if isType "submodule" m then + { _file = m.file; } // (unpack m.submodule args) + else unpack m args; + + packSubmodule = file: m: + { _type = "submodule"; file = file; submodule = m; }; + + /* Merge a list of modules. This will recurse over the option + declarations in all modules, combining them into a single set. + At the same time, for each option declaration, it will merge the + corresponding option definitions in all machines, returning them + in the ‘value’ attribute of each option. */ + mergeModules = prefix: modules: + mergeModules' prefix modules + (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); + + mergeModules' = prefix: options: configs: + let + /* byName is like foldAttrs, but will look for attributes to merge in the + specified attribute name. + + byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"]) + [ + { + hidden="baz"; + foo={qux="bar"; gla="flop";}; + } + { + hidden="fli"; + foo={qux="gne"; gli="flip";}; + } + ] + ===> + { + gla = [ "module.hidden=baz,value=flop" ]; + gli = [ "module.hidden=fli,value=flip" ]; + qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ]; + } + */ + byName = attr: f: modules: foldl' (acc: module: + foldl' (inner: name: + inner // { ${name} = (acc.${name} or []) ++ (f module module.${attr}.${name}); } + ) acc (attrNames module.${attr}) + ) {} modules; + # an attrset 'name' => list of submodules that declare ‘name’. + declsByName = byName "options" + (module: option: [{ inherit (module) file; options = option; }]) + options; + # an attrset 'name' => list of submodules that define ‘name’. + defnsByName = byName "config" (module: value: + map (config: { inherit (module) file; inherit config; }) (pushDownProperties value) + ) configs; + # extract the definitions for each loc + defnsByName' = byName "config" + (module: value: [{ inherit (module) file; inherit value; }]) + configs; + in + (flip mapAttrs declsByName (name: decls: + # We're descending into attribute ‘name’. + let + loc = prefix ++ [name]; + defns = defnsByName.${name} or []; + defns' = defnsByName'.${name} or []; + nrOptions = count (m: isOption m.options) decls; + in + if nrOptions == length decls then + let opt = fixupOptionType loc (mergeOptionDecls loc decls); + in evalOptionValue loc opt defns' + else if nrOptions != 0 then + let + firstOption = findFirst (m: isOption m.options) "" decls; + firstNonOption = findFirst (m: !isOption m.options) "" decls; + in + throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." + else + mergeModules' loc decls defns + )) + // { _definedNames = map (m: { inherit (m) file; names = attrNames m.config; }) configs; }; + + /* Merge multiple option declarations into a single declaration. In + general, there should be only one declaration of each option. + The exception is the ‘options’ attribute, which specifies + sub-options. These can be specified multiple times to allow one + module to add sub-options to an option declared somewhere else + (e.g. multiple modules define sub-options for ‘fileSystems’). + + 'loc' is the list of attribute names where the option is located. + + 'opts' is a list of modules. Each module has an options attribute which + correspond to the definition of 'loc' in 'opt.file'. */ + mergeOptionDecls = loc: opts: + foldl' (res: opt: + let t = res.type; + t' = opt.options.type; + mergedType = t.typeMerge t'.functor; + typesMergeable = mergedType != null; + typeSet = if (bothHave "type") && typesMergeable + then { type = mergedType; } + else {}; + bothHave = k: opt.options ? ${k} && res ? ${k}; + in + if bothHave "default" || + bothHave "example" || + bothHave "description" || + bothHave "apply" || + (bothHave "type" && (! typesMergeable)) + then + throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}." + else + let + /* Add the modules of the current option to the list of modules + already collected. The options attribute except either a list of + submodules or a submodule. For each submodule, we add the file of the + current option declaration as the file use for the submodule. If the + submodule defines any filename, then we ignore the enclosing option file. */ + options' = toList opt.options.options; + coerceOption = file: opt: + if isFunction opt then packSubmodule file opt + else packSubmodule file { options = opt; }; + getSubModules = opt.options.type.getSubModules or null; + submodules = + if getSubModules != null then map (packSubmodule opt.file) getSubModules ++ res.options + else if opt.options ? options then map (coerceOption opt.file) options' ++ res.options + else res.options; + in opt.options // res // + { declarations = res.declarations ++ [opt.file]; + options = submodules; + } // typeSet + ) { inherit loc; declarations = []; options = []; } opts; + + /* Merge all the definitions of an option to produce the final + config value. */ + evalOptionValue = loc: opt: defs: + let + # Add in the default value for this option, if any. + defs' = + (optional (opt ? default) + { file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs; + + # Handle properties, check types, and merge everything together. + res = + if opt.readOnly or false && length defs' > 1 then + throw "The option `${showOption loc}' is read-only, but it's set multiple times." + else + mergeDefinitions loc opt.type defs'; + + # Check whether the option is defined, and apply the ‘apply’ + # function to the merged value. This allows options to yield a + # value computed from the definitions. + value = + if !res.isDefined then + throw "The option `${showOption loc}' is used but not defined." + else if opt ? apply then + opt.apply res.mergedValue + else + res.mergedValue; + + in opt // + { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value; + inherit (res.defsFinal') highestPrio; + definitions = map (def: def.value) res.defsFinal; + files = map (def: def.file) res.defsFinal; + inherit (res) isDefined; + }; + + # Merge definitions of a value of a given type. + mergeDefinitions = loc: type: defs: rec { + defsFinal' = + let + # Process mkMerge and mkIf properties. + defs' = concatMap (m: + map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value) + ) defs; + + # Process mkOverride properties. + defs'' = filterOverrides' defs'; + + # Sort mkOrder properties. + defs''' = + # Avoid sorting if we don't have to. + if any (def: def.value._type or "" == "order") defs''.values + then sortProperties defs''.values + else defs''.values; + in { + values = defs'''; + inherit (defs'') highestPrio; + }; + + defsFinal = defsFinal'.values; + + # Type-check the remaining definitions, and merge them. + mergedValue = foldl' (res: def: + if type.check def.value then res + else throw "The option value `${showOption loc}' in `${def.file}' is not of type `${type.description}'.") + (type.merge loc defsFinal) defsFinal; + + isDefined = defsFinal != []; + + optionalValue = + if isDefined then { value = mergedValue; } + else {}; + }; + + /* Given a config set, expand mkMerge properties, and push down the + other properties into the children. The result is a list of + config sets that do not have properties at top-level. For + example, + + mkMerge [ { boot = set1; } (mkIf cond { boot = set2; services = set3; }) ] + + is transformed into + + [ { boot = set1; } { boot = mkIf cond set2; services = mkIf cond set3; } ]. + + This transform is the critical step that allows mkIf conditions + to refer to the full configuration without creating an infinite + recursion. + */ + pushDownProperties = cfg: + if cfg._type or "" == "merge" then + concatMap pushDownProperties cfg.contents + else if cfg._type or "" == "if" then + map (mapAttrs (n: v: mkIf cfg.condition v)) (pushDownProperties cfg.content) + else if cfg._type or "" == "override" then + map (mapAttrs (n: v: mkOverride cfg.priority v)) (pushDownProperties cfg.content) + else # FIXME: handle mkOrder? + [ cfg ]; + + /* Given a config value, expand mkMerge properties, and discharge + any mkIf conditions. That is, this is the place where mkIf + conditions are actually evaluated. The result is a list of + config values. For example, ‘mkIf false x’ yields ‘[]’, + ‘mkIf true x’ yields ‘[x]’, and + + mkMerge [ 1 (mkIf true 2) (mkIf true (mkIf false 3)) ] + + yields ‘[ 1 2 ]’. + */ + dischargeProperties = def: + if def._type or "" == "merge" then + concatMap dischargeProperties def.contents + else if def._type or "" == "if" then + if isBool def.condition then + if def.condition then + dischargeProperties def.content + else + [ ] + else + throw "‘mkIf’ called with a non-Boolean condition" + else + [ def ]; + + /* Given a list of config values, process the mkOverride properties, + that is, return the values that have the highest (that is, + numerically lowest) priority, and strip the mkOverride + properties. For example, + + [ { file = "/1"; value = mkOverride 10 "a"; } + { file = "/2"; value = mkOverride 20 "b"; } + { file = "/3"; value = "z"; } + { file = "/4"; value = mkOverride 10 "d"; } + ] + + yields + + [ { file = "/1"; value = "a"; } + { file = "/4"; value = "d"; } + ] + + Note that "z" has the default priority 100. + */ + filterOverrides = defs: (filterOverrides' defs).values; + + filterOverrides' = defs: + let + defaultPrio = 100; + getPrio = def: if def.value._type or "" == "override" then def.value.priority else defaultPrio; + highestPrio = foldl' (prio: def: min (getPrio def) prio) 9999 defs; + strip = def: if def.value._type or "" == "override" then def // { value = def.value.content; } else def; + in { + values = concatMap (def: if getPrio def == highestPrio then [(strip def)] else []) defs; + inherit highestPrio; + }; + + /* Sort a list of properties. The sort priority of a property is + 1000 by default, but can be overridden by wrapping the property + using mkOrder. */ + sortProperties = defs: + let + strip = def: + if def.value._type or "" == "order" + then def // { value = def.value.content; inherit (def.value) priority; } + else def; + defs' = map strip defs; + compare = a: b: (a.priority or 1000) < (b.priority or 1000); + in sort compare defs'; + + /* Hack for backward compatibility: convert options of type + optionSet to options of type submodule. FIXME: remove + eventually. */ + fixupOptionType = loc: opt: + let + options = opt.options or + (throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}."); + f = tp: + let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet"); + in + if tp.name == "option set" || tp.name == "submodule" then + throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}." + else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options) + else if optionSetIn "loaOf" then types.loaOf (types.submodule options) + else if optionSetIn "listOf" then types.listOf (types.submodule options) + else if optionSetIn "nullOr" then types.nullOr (types.submodule options) + else tp; + in + if opt.type.getSubModules or null == null + then opt // { type = f (opt.type or types.unspecified); } + else opt // { type = opt.type.substSubModules opt.options; options = []; }; + + + /* Properties. */ + + mkIf = condition: content: + { _type = "if"; + inherit condition content; + }; + + mkAssert = assertion: message: content: + mkIf + (if assertion then true else throw "\nFailed assertion: ${message}") + content; + + mkMerge = contents: + { _type = "merge"; + inherit contents; + }; + + mkOverride = priority: content: + { _type = "override"; + inherit priority content; + }; + + mkOptionDefault = mkOverride 1500; # priority of option defaults + mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default + mkForce = mkOverride 50; + mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’ + + mkStrict = builtins.trace "`mkStrict' is obsolete; use `mkOverride 0' instead." (mkOverride 0); + + mkFixStrictness = id; # obsolete, no-op + + mkOrder = priority: content: + { _type = "order"; + inherit priority content; + }; + + mkBefore = mkOrder 500; + mkAfter = mkOrder 1500; + + + # Convenient property used to transfer all definitions and their + # properties from one option to another. This property is useful for + # renaming options, and also for including properties from another module + # system, including sub-modules. + # + # { config, options, ... }: + # + # { + # # 'bar' might not always be defined in the current module-set. + # config.foo.enable = mkAliasDefinitions (options.bar.enable or {}); + # + # # 'barbaz' has to be defined in the current module-set. + # config.foobar.paths = mkAliasDefinitions options.barbaz.paths; + # } + # + # Note, this is different than taking the value of the option and using it + # as a definition, as the new definition will not keep the mkOverride / + # mkDefault properties of the previous option. + # + mkAliasDefinitions = mkAliasAndWrapDefinitions id; + mkAliasAndWrapDefinitions = wrap: option: + mkIf (isOption option && option.isDefined) (wrap (mkMerge option.definitions)); + + + /* Compatibility. */ + fixMergeModules = modules: args: evalModules { inherit modules args; check = false; }; + + + /* Return a module that causes a warning to be shown if the + specified option is defined. For example, + + mkRemovedOptionModule [ "boot" "loader" "grub" "bootDevice" ] "<replacement instructions>" + + causes a warning if the user defines boot.loader.grub.bootDevice. + + replacementInstructions is a string that provides instructions on + how to achieve the same functionality without the removed option, + or alternatively a reasoning why the functionality is not needed. + replacementInstructions SHOULD be provided! + */ + mkRemovedOptionModule = optionName: replacementInstructions: + { options, ... }: + { options = setAttrByPath optionName (mkOption { + visible = false; + }); + config.warnings = + let opt = getAttrFromPath optionName options; in + optional opt.isDefined '' + The option definition `${showOption optionName}' in ${showFiles opt.files} no longer has any effect; please remove it. + ${replacementInstructions}''; + }; + + /* Return a module that causes a warning to be shown if the + specified "from" option is defined; the defined value is however + forwarded to the "to" option. This can be used to rename options + while providing backward compatibility. For example, + + mkRenamedOptionModule [ "boot" "copyKernels" ] [ "boot" "loader" "grub" "copyKernels" ] + + forwards any definitions of boot.copyKernels to + boot.loader.grub.copyKernels while printing a warning. + */ + mkRenamedOptionModule = from: to: doRename { + inherit from to; + visible = false; + warn = true; + use = builtins.trace "Obsolete option `${showOption from}' is used. It was renamed to `${showOption to}'."; + }; + + /* Return a module that causes a warning to be shown if any of the "from" + option is defined; the defined values can be used in the "mergeFn" to set + the "to" value. + This function can be used to merge multiple options into one that has a + different type. + + "mergeFn" takes the module "config" as a parameter and must return a value + of "to" option type. + + mkMergedOptionModule + [ [ "a" "b" "c" ] + [ "d" "e" "f" ] ] + [ "x" "y" "z" ] + (config: + let value = p: getAttrFromPath p config; + in + if (value [ "a" "b" "c" ]) == true then "foo" + else if (value [ "d" "e" "f" ]) == true then "bar" + else "baz") + + - options.a.b.c is a removed boolean option + - options.d.e.f is a removed boolean option + - options.x.y.z is a new str option that combines a.b.c and d.e.f + functionality + + This show a warning if any a.b.c or d.e.f is set, and set the value of + x.y.z to the result of the merge function + */ + mkMergedOptionModule = from: to: mergeFn: + { config, options, ... }: + { + options = foldl recursiveUpdate {} (map (path: setAttrByPath path (mkOption { + visible = false; + # To use the value in mergeFn without triggering errors + default = "_mkMergedOptionModule"; + })) from); + + config = { + warnings = filter (x: x != "") (map (f: + let val = getAttrFromPath f config; + opt = getAttrFromPath f options; + in + optionalString + (val != "_mkMergedOptionModule") + "The option `${showOption f}' defined in ${showFiles opt.files} has been changed to `${showOption to}' that has a different type. Please read `${showOption to}' documentation and update your configuration accordingly." + ) from); + } // setAttrByPath to (mkMerge + (optional + (any (f: (getAttrFromPath f config) != "_mkMergedOptionModule") from) + (mergeFn config))); + }; + + /* Single "from" version of mkMergedOptionModule. + Return a module that causes a warning to be shown if the "from" option is + defined; the defined value can be used in the "mergeFn" to set the "to" + value. + This function can be used to change an option into another that has a + different type. + + "mergeFn" takes the module "config" as a parameter and must return a value of + "to" option type. + + mkChangedOptionModule [ "a" "b" "c" ] [ "x" "y" "z" ] + (config: + let value = getAttrFromPath [ "a" "b" "c" ] config; + in + if value > 100 then "high" + else "normal") + + - options.a.b.c is a removed int option + - options.x.y.z is a new str option that supersedes a.b.c + + This show a warning if a.b.c is set, and set the value of x.y.z to the + result of the change function + */ + mkChangedOptionModule = from: to: changeFn: + mkMergedOptionModule [ from ] to changeFn; + + /* Like ‘mkRenamedOptionModule’, but doesn't show a warning. */ + mkAliasOptionModule = from: to: doRename { + inherit from to; + visible = true; + warn = false; + use = id; + }; + + doRename = { from, to, visible, warn, use }: + { config, options, ... }: + let + fromOpt = getAttrFromPath from options; + toOf = attrByPath to + (abort "Renaming error: option `${showOption to}' does not exist."); + in + { + options = setAttrByPath from (mkOption { + inherit visible; + description = "Alias of <option>${showOption to}</option>."; + apply = x: use (toOf config); + }); + config = mkMerge [ + { + warnings = optional (warn && fromOpt.isDefined) + "The option `${showOption from}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption to}'."; + } + (mkAliasAndWrapDefinitions (setAttrByPath to) fromOpt) + ]; + }; + +} diff --git a/nixpkgs/lib/options.nix b/nixpkgs/lib/options.nix new file mode 100644 index 000000000000..791930eafbd0 --- /dev/null +++ b/nixpkgs/lib/options.nix @@ -0,0 +1,207 @@ +# Nixpkgs/NixOS option handling. +{ lib }: + +with lib.trivial; +with lib.lists; +with lib.attrsets; +with lib.strings; + +rec { + + /* Returns true when the given argument is an option + + Type: isOption :: a -> bool + + Example: + isOption 1 // => false + isOption (mkOption {}) // => true + */ + isOption = lib.isType "option"; + + /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys: + + All keys default to `null` when not given. + + Example: + mkOption { } // => { _type = "option"; } + mkOption { defaultText = "foo"; } // => { _type = "option"; defaultText = "foo"; } + */ + mkOption = + { + # Default value used when no definition is given in the configuration. + default ? null, + # Textual representation of the default, for the manual. + defaultText ? null, + # Example value used in the manual. + example ? null, + # String describing the option. + description ? null, + # Related packages used in the manual (see `genRelatedPackages` in ../nixos/doc/manual/default.nix). + relatedPackages ? null, + # Option type, providing type-checking and value merging. + type ? null, + # Function that converts the option value to something else. + apply ? null, + # Whether the option is for NixOS developers only. + internal ? null, + # Whether the option shows up in the manual. + visible ? null, + # Whether the option can be set only once + readOnly ? null, + # Obsolete, used by types.optionSet. + options ? null + } @ attrs: + attrs // { _type = "option"; }; + + /* Creates an Option attribute set for a boolean value option i.e an + option to be toggled on or off: + + Example: + mkEnableOption "foo" + => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; } + */ + mkEnableOption = + # Name for the created option + name: mkOption { + default = false; + example = true; + description = "Whether to enable ${name}."; + type = lib.types.bool; + }; + + /* This option accepts anything, but it does not produce any result. + + This is useful for sharing a module across different module sets + without having to implement similar features as long as the + values of the options are not accessed. */ + mkSinkUndeclaredOptions = attrs: mkOption ({ + internal = true; + visible = false; + default = false; + description = "Sink for option definitions."; + type = mkOptionType { + name = "sink"; + check = x: true; + merge = loc: defs: false; + }; + apply = x: throw "Option value is not readable because the option is not declared."; + } // attrs); + + mergeDefaultOption = loc: defs: + let list = getValues defs; in + if length list == 1 then head list + else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list) + else if all isList list then concatLists list + else if all isAttrs list then foldl' lib.mergeAttrs {} list + else if all isBool list then foldl' lib.or false list + else if all isString list then lib.concatStrings list + else if all isInt list && all (x: x == head list) list then head list + else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}."; + + mergeOneOption = loc: defs: + if defs == [] then abort "This case should never happen." + else if length defs != 1 then + throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}." + else (head defs).value; + + /* "Merge" option definitions by checking that they all have the same value. */ + mergeEqualOption = loc: defs: + if defs == [] then abort "This case should never happen." + else foldl' (val: def: + if def.value != val then + throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}." + else + val) (head defs).value defs; + + /* Extracts values of all "value" keys of the given list. + + Type: getValues :: [ { value :: a } ] -> [a] + + Example: + getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ] + getValues [ ] // => [ ] + */ + getValues = map (x: x.value); + + /* Extracts values of all "file" keys of the given list + + Type: getFiles :: [ { file :: a } ] -> [a] + + Example: + getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ] + getFiles [ ] // => [ ] + */ + getFiles = map (x: x.file); + + # Generate documentation template from the list of option declaration like + # the set generated with filterOptionSets. + optionAttrSetToDocList = optionAttrSetToDocList' []; + + optionAttrSetToDocList' = prefix: options: + concatMap (opt: + let + docOption = rec { + loc = opt.loc; + name = showOption opt.loc; + description = opt.description or (throw "Option `${name}' has no description."); + declarations = filter (x: x != unknownModule) opt.declarations; + internal = opt.internal or false; + visible = opt.visible or true; + readOnly = opt.readOnly or false; + type = opt.type.description or null; + } + // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; } + // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; } + // optionalAttrs (opt ? defaultText) { default = opt.defaultText; } + // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; }; + + subOptions = + let ss = opt.type.getSubOptions opt.loc; + in if ss != {} then optionAttrSetToDocList' opt.loc ss else []; + in + [ docOption ] ++ subOptions) (collect isOption 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; }; + + # Helper functions. + + /* Convert an option, described as a list of the option parts in to a + safe, human readable version. + + Example: + (showOption ["foo" "bar" "baz"]) == "foo.bar.baz" + (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux" + */ + showOption = parts: let + escapeOptionPart = part: + let + escaped = lib.strings.escapeNixString part; + in if escaped == "\"${part}\"" + then part + else escaped; + in (concatStringsSep ".") (map escapeOptionPart parts); + showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); + unknownModule = "<unknown-file>"; + +} diff --git a/nixpkgs/lib/sources.nix b/nixpkgs/lib/sources.nix new file mode 100644 index 000000000000..1a9f3f7d1f34 --- /dev/null +++ b/nixpkgs/lib/sources.nix @@ -0,0 +1,104 @@ +# Functions for copying sources to the Nix store. +{ lib }: + +rec { + + # Returns the type of a path: regular (for file), symlink, or directory + pathType = p: with builtins; getAttr (baseNameOf p) (readDir (dirOf p)); + + # Returns true if the path exists and is a directory, false otherwise + pathIsDirectory = p: if builtins.pathExists p then (pathType p) == "directory" else false; + + # Bring in a path as a source, filtering out all Subversion and CVS + # directories, as well as backup files (*~). + cleanSourceFilter = name: type: let baseName = baseNameOf (toString name); in ! ( + # Filter out Subversion and CVS directories. + (type == "directory" && (baseName == ".git" || baseName == ".svn" || baseName == "CVS" || baseName == ".hg")) || + # Filter out editor backup / swap files. + lib.hasSuffix "~" baseName || + builtins.match "^\\.sw[a-z]$" baseName != null || + builtins.match "^\\..*\\.sw[a-z]$" baseName != null || + + # Filter out generates files. + lib.hasSuffix ".o" baseName || + lib.hasSuffix ".so" baseName || + # Filter out nix-build result symlinks + (type == "symlink" && lib.hasPrefix "result" baseName) + ); + + # Filters a source tree removing version control files and directories using cleanSourceWith + # + # Example: + # cleanSource ./. + cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; }; + + # Like `builtins.filterSource`, except it will compose with itself, + # allowing you to chain multiple calls together without any + # intermediate copies being put in the nix store. + # + # lib.cleanSourceWith f (lib.cleanSourceWith g ./.) # Succeeds! + # builtins.filterSource f (builtins.filterSource g ./.) # Fails! + cleanSourceWith = { filter, src }: + let + isFiltered = src ? _isLibCleanSourceWith; + origSrc = if isFiltered then src.origSrc else src; + filter' = if isFiltered then name: type: filter name type && src.filter name type else filter; + in { + inherit origSrc; + filter = filter'; + outPath = builtins.filterSource filter' origSrc; + _isLibCleanSourceWith = true; + }; + + # Filter sources by a list of regular expressions. + # + # E.g. `src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"]` + sourceByRegex = src: regexes: cleanSourceWith { + filter = (path: type: + let relPath = lib.removePrefix (toString src + "/") (toString path); + in lib.any (re: builtins.match re relPath != null) regexes); + inherit src; + }; + + # Get all files ending with the specified suffices from the given + # directory or its descendants. 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 cleanSourceWith { inherit filter; src = path; }; + + + # Get the commit id of a git repo + # Example: commitIdFromGitRepo <nixpkgs/.git> + commitIdFromGitRepo = + let readCommitFromFile = file: path: + with builtins; + let fileName = toString path + "/" + file; + packedRefsName = toString path + "/packed-refs"; + in if lib.pathExists fileName + then + let fileContent = lib.fileContents fileName; + # Sometimes git stores the commitId directly in the file but + # sometimes it stores something like: «ref: refs/heads/branch-name» + matchRef = match "^ref: (.*)$" fileContent; + in if isNull matchRef + then fileContent + else readCommitFromFile (lib.head matchRef) path + # Sometimes, the file isn't there at all and has been packed away in the + # packed-refs file, so we have to grep through it: + else if lib.pathExists packedRefsName + then + let fileContent = readFile packedRefsName; + matchRef = match (".*\n([^\n ]*) " + file + "\n.*") fileContent; + in if isNull matchRef + then throw ("Could not find " + file + " in " + packedRefsName) + else lib.head matchRef + else throw ("Not a .git directory: " + path); + in readCommitFromFile "HEAD"; + + pathHasContext = builtins.hasContext or (lib.hasPrefix builtins.storeDir); + + canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src)); +} diff --git a/nixpkgs/lib/strings-with-deps.nix b/nixpkgs/lib/strings-with-deps.nix new file mode 100644 index 000000000000..e3336983428f --- /dev/null +++ b/nixpkgs/lib/strings-with-deps.nix @@ -0,0 +1,79 @@ +{ lib }: +/* +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 <nixpkgs>) {}; + 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 lib.lists; +with lib.attrsets; +with lib.strings; + +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 done ? ${entry} then f done (tail todo) + else f (done // listToAttrs [{name = entry; value = 1;}]) ([predefined.${entry}] ++ 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/nixpkgs/lib/strings.nix b/nixpkgs/lib/strings.nix new file mode 100644 index 000000000000..48420a367815 --- /dev/null +++ b/nixpkgs/lib/strings.nix @@ -0,0 +1,684 @@ +/* String manipulation functions. */ +{ lib }: +let + +inherit (builtins) length; + +in + +rec { + + inherit (builtins) stringLength substring head tail isString replaceStrings; + + /* Concatenate a list of strings. + + Type: concatStrings :: [string] -> string + + Example: + concatStrings ["foo" "bar"] + => "foobar" + */ + concatStrings = builtins.concatStringsSep ""; + + /* Map a function over a list and concatenate the resulting strings. + + Type: concatMapStrings :: (a -> string) -> [a] -> string + + Example: + concatMapStrings (x: "a" + x) ["foo" "bar"] + => "afooabar" + */ + concatMapStrings = f: list: concatStrings (map f list); + + /* Like `concatMapStrings` except that the f functions also gets the + position as a parameter. + + Type: concatImapStrings :: (int -> a -> string) -> [a] -> string + + Example: + concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"] + => "1-foo2-bar" + */ + concatImapStrings = f: list: concatStrings (lib.imap1 f list); + + /* Place an element between each element of a list + + Type: intersperse :: a -> [a] -> [a] + + Example: + intersperse "/" ["usr" "local" "bin"] + => ["usr" "/" "local" "/" "bin"]. + */ + intersperse = + # Separator to add between elements + separator: + # Input list + list: + if list == [] || length list == 1 + then list + else tail (lib.concatMap (x: [separator x]) list); + + /* Concatenate a list of strings with a separator between each element + + Type: concatStringsSep :: string -> [string] -> string + + Example: + concatStringsSep "/" ["usr" "local" "bin"] + => "usr/local/bin" + */ + concatStringsSep = builtins.concatStringsSep or (separator: list: + concatStrings (intersperse separator list)); + + /* Maps a function over a list of strings and then concatenates the + result with the specified separator interspersed between + elements. + + Type: concatMapStringsSep :: string -> (string -> string) -> [string] -> string + + Example: + concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"] + => "FOO-BAR-BAZ" + */ + concatMapStringsSep = + # Separator to add between elements + sep: + # Function to map over the list + f: + # List of input strings + list: concatStringsSep sep (map f list); + + /* Same as `concatMapStringsSep`, but the mapping function + additionally receives the position of its argument. + + Type: concatMapStringsSep :: string -> (int -> string -> string) -> [string] -> string + + Example: + concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ] + => "6-3-2" + */ + concatImapStringsSep = + # Separator to add between elements + sep: + # Function that receives elements and their positions + f: + # List of input strings + list: concatStringsSep sep (lib.imap1 f list); + + /* Construct a Unix-style, colon-separated search path consisting of + the given `subDir` appended to each of the given paths. + + Type: makeSearchPath :: string -> [string] -> string + + Example: + makeSearchPath "bin" ["/root" "/usr" "/usr/local"] + => "/root/bin:/usr/bin:/usr/local/bin" + makeSearchPath "bin" [""] + => "/bin" + */ + makeSearchPath = + # Directory name to append + subDir: + # List of base paths + paths: + concatStringsSep ":" (map (path: path + "/" + subDir) (builtins.filter (x: x != null) paths)); + + /* Construct a Unix-style search path by appending the given + `subDir` to the specified `output` of each of the packages. If no + output by the given name is found, fallback to `.out` and then to + the default. + + Type: string -> string -> [package] -> string + + Example: + makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ] + => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin" + */ + makeSearchPathOutput = + # Package output to use + output: + # Directory name to append + subDir: + # List of packages + pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs); + + /* Construct a library search path (such as RPATH) containing the + libraries for a set of packages + + Example: + makeLibraryPath [ "/usr" "/usr/local" ] + => "/usr/lib:/usr/local/lib" + pkgs = import <nixpkgs> { } + makeLibraryPath [ pkgs.openssl pkgs.zlib ] + => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib" + */ + makeLibraryPath = makeSearchPathOutput "lib" "lib"; + + /* Construct a binary search path (such as $PATH) containing the + binaries for a set of packages. + + Example: + makeBinPath ["/root" "/usr" "/usr/local"] + => "/root/bin:/usr/bin:/usr/local/bin" + */ + makeBinPath = makeSearchPathOutput "bin" "bin"; + + + /* Construct a perl search path (such as $PERL5LIB) + + Example: + pkgs = import <nixpkgs> { } + makePerlPath [ pkgs.perlPackages.libnet ] + => "/nix/store/n0m1fk9c960d8wlrs62sncnadygqqc6y-perl-Net-SMTP-1.25/lib/perl5/site_perl" + */ + # FIXME(zimbatm): this should be moved in perl-specific code + makePerlPath = makeSearchPathOutput "lib" "lib/perl5/site_perl"; + + /* Construct a perl search path recursively including all dependencies (such as $PERL5LIB) + + Example: + pkgs = import <nixpkgs> { } + makeFullPerlPath [ pkgs.perlPackages.CGI ] + => "/nix/store/fddivfrdc1xql02h9q500fpnqy12c74n-perl-CGI-4.38/lib/perl5/site_perl:/nix/store/8hsvdalmsxqkjg0c5ifigpf31vc4vsy2-perl-HTML-Parser-3.72/lib/perl5/site_perl:/nix/store/zhc7wh0xl8hz3y3f71nhlw1559iyvzld-perl-HTML-Tagset-3.20/lib/perl5/site_perl" + */ + makeFullPerlPath = deps: makePerlPath (lib.misc.closePropagation deps); + + /* Depending on the boolean `cond', return either the given string + or the empty string. Useful to concatenate against a bigger string. + + Type: optionalString :: bool -> string -> string + + Example: + optionalString true "some-string" + => "some-string" + optionalString false "some-string" + => "" + */ + optionalString = + # Condition + cond: + # String to return if condition is true + string: if cond then string else ""; + + /* Determine whether a string has given prefix. + + Type: hasPrefix :: string -> string -> bool + + Example: + hasPrefix "foo" "foobar" + => true + hasPrefix "foo" "barfoo" + => false + */ + hasPrefix = + # Prefix to check for + pref: + # Input string + str: substring 0 (stringLength pref) str == pref; + + /* Determine whether a string has given suffix. + + Type: hasSuffix :: string -> string -> bool + + Example: + hasSuffix "foo" "foobar" + => false + hasSuffix "foo" "barfoo" + => true + */ + hasSuffix = + # Suffix to check for + suffix: + # Input string + content: + let + lenContent = stringLength content; + lenSuffix = stringLength suffix; + in lenContent >= lenSuffix && + substring (lenContent - lenSuffix) lenContent content == suffix; + + /* Determine whether a string contains the given infix + + Type: hasInfix :: string -> string -> bool + + Example: + hasInfix "bc" "abcd" + => true + hasInfix "ab" "abcd" + => true + hasInfix "cd" "abcd" + => true + hasInfix "foo" "abcd" + => false + */ + hasInfix = infix: content: + let + drop = x: substring 1 (stringLength x) x; + in hasPrefix infix content + || content != "" && hasInfix infix (drop content); + + /* Convert a string to a list of characters (i.e. singleton strings). + This allows you to, e.g., map a function over each character. However, + 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. + Also note that Nix treats strings as a list of bytes and thus doesn't + handle unicode. + + Type: stringtoCharacters :: string -> [string] + + Example: + stringToCharacters "" + => [ ] + stringToCharacters "abc" + => [ "a" "b" "c" ] + stringToCharacters "💩" + => [ "�" "�" "�" "�" ] + */ + stringToCharacters = s: + map (p: substring p 1 s) (lib.range 0 (stringLength s - 1)); + + /* Manipulate a string character by character and replace them by + strings before concatenating the results. + + Type: stringAsChars :: (string -> string) -> string -> string + + Example: + stringAsChars (x: if x == "a" then "i" else x) "nax" + => "nix" + */ + stringAsChars = + # Function to map over each individual character + f: + # Input string + s: concatStrings ( + map f (stringToCharacters s) + ); + + /* Escape occurrence of the elements of `list` in `string` by + prefixing it with a backslash. + + Type: escape :: [string] -> string -> string + + Example: + escape ["(" ")"] "(foo)" + => "\\(foo\\)" + */ + escape = list: replaceChars list (map (c: "\\${c}") list); + + /* Quote string to be used safely within the Bourne shell. + + Type: escapeShellArg :: string -> string + + Example: + escapeShellArg "esc'ape\nme" + => "'esc'\\''ape\nme'" + */ + escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'"; + + /* Quote all arguments to be safely passed to the Bourne shell. + + Type: escapeShellArgs :: [string] -> string + + Example: + escapeShellArgs ["one" "two three" "four'five"] + => "'one' 'two three' 'four'\\''five'" + */ + escapeShellArgs = concatMapStringsSep " " escapeShellArg; + + /* Turn a string into a Nix expression representing that string + + Type: string -> string + + Example: + escapeNixString "hello\${}\n" + => "\"hello\\\${}\\n\"" + */ + escapeNixString = s: escape ["$"] (builtins.toJSON s); + + # Obsolete - use replaceStrings instead. + replaceChars = builtins.replaceStrings or ( + del: new: s: + let + substList = lib.zipLists del new; + subst = c: + let found = lib.findFirst (sub: sub.fst == c) null substList; in + if found == null then + c + else + found.snd; + in + stringAsChars subst s); + + # Case conversion utilities. + lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; + upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /* Converts an ASCII string to lower-case. + + Type: toLower :: string -> string + + Example: + toLower "HOME" + => "home" + */ + toLower = replaceChars upperChars lowerChars; + + /* Converts an ASCII string to upper-case. + + Type: toUpper :: string -> string + + Example: + toUpper "home" + => "HOME" + */ + toUpper = replaceChars lowerChars upperChars; + + /* Appends string context from another string. This is an implementation + detail of Nix. + + Strings in Nix carry an invisible `context` which is a list of strings + representing store paths. If the string is later used in a derivation + attribute, the derivation will properly populate the inputDrvs and + inputSrcs. + + Example: + pkgs = import <nixpkgs> { }; + addContextFrom pkgs.coreutils "bar" + => "bar" + */ + addContextFrom = a: b: substring 0 0 a + b; + + /* Cut a string with a separator and produces a list of strings which + were separated by this separator. + + NOTE: this function is not performant and should never be used. + + Example: + splitString "." "foo.bar.baz" + => [ "foo" "bar" "baz" ] + splitString "/" "/usr/local/bin" + => [ "" "usr" "local" "bin" ] + */ + splitString = _sep: _s: + let + sep = addContextFrom _s _sep; + s = addContextFrom _sep _s; + sepLen = stringLength sep; + sLen = stringLength s; + lastSearch = sLen - sepLen; + startWithSep = startAt: + substring startAt sepLen s == sep; + + recurse = index: startAt: + let cutUntil = i: [(substring startAt (i - startAt) s)]; in + if index <= lastSearch then + if startWithSep index then + let restartAt = index + sepLen; in + cutUntil index ++ recurse restartAt restartAt + else + recurse (index + 1) startAt + else + cutUntil sLen; + in + recurse 0 0; + + /* Return a string without the specified prefix, if the prefix matches. + + Type: string -> string -> string + + Example: + removePrefix "foo." "foo.bar.baz" + => "bar.baz" + removePrefix "xxx" "foo.bar.baz" + => "foo.bar.baz" + */ + removePrefix = + # Prefix to remove if it matches + prefix: + # Input string + str: + let + preLen = stringLength prefix; + sLen = stringLength str; + in + if hasPrefix prefix str then + substring preLen (sLen - preLen) str + else + str; + + /* Return a string without the specified suffix, if the suffix matches. + + Type: string -> string -> string + + Example: + removeSuffix "front" "homefront" + => "home" + removeSuffix "xxx" "homefront" + => "homefront" + */ + removeSuffix = + # Suffix to remove if it matches + suffix: + # Input string + str: + let + sufLen = stringLength suffix; + sLen = stringLength str; + in + if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then + substring 0 (sLen - sufLen) str + else + str; + + /* Return true if string v1 denotes a version older than v2. + + Example: + versionOlder "1.1" "1.2" + => true + versionOlder "1.1" "1.1" + => false + */ + versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; + + /* Return true if string v1 denotes a version equal to or newer than v2. + + Example: + versionAtLeast "1.1" "1.0" + => true + versionAtLeast "1.1" "1.1" + => true + versionAtLeast "1.1" "1.2" + => false + */ + versionAtLeast = v1: v2: !versionOlder v1 v2; + + /* This function takes an argument that's either a derivation or a + derivation's "name" attribute and extracts the version part from that + argument. + + Example: + getVersion "youtube-dl-2016.01.01" + => "2016.01.01" + getVersion pkgs.youtube-dl + => "2016.01.01" + */ + getVersion = x: + let + parse = drv: (builtins.parseDrvName drv).version; + in if isString x + then parse x + else x.version or (parse x.name); + + /* Extract name with version from URL. Ask for separator which is + supposed to start extension. + + Example: + nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-" + => "nix" + nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_" + => "nix-1.7-x86" + */ + nameFromURL = url: sep: + let + components = splitString "/" url; + filename = lib.last components; + name = builtins.head (splitString sep filename); + in assert name != filename; name; + + /* Create an --{enable,disable}-<feat> string that can be passed to + standard GNU Autoconf scripts. + + Example: + enableFeature true "shared" + => "--enable-shared" + enableFeature false "shared" + => "--disable-shared" + */ + enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; + + /* Create an --{enable-<feat>=<value>,disable-<feat>} string that can be passed to + standard GNU Autoconf scripts. + + Example: + enableFeature true "shared" "foo" + => "--enable-shared=foo" + enableFeature false "shared" (throw "ignored") + => "--disable-shared" + */ + enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}"; + + /* Create an --{with,without}-<feat> string that can be passed to + standard GNU Autoconf scripts. + + Example: + withFeature true "shared" + => "--with-shared" + withFeature false "shared" + => "--without-shared" + */ + withFeature = with_: feat: "--${if with_ then "with" else "without"}-${feat}"; + + /* Create an --{with-<feat>=<value>,without-<feat>} string that can be passed to + standard GNU Autoconf scripts. + + Example: + with_Feature true "shared" "foo" + => "--with-shared=foo" + with_Feature false "shared" (throw "ignored") + => "--without-shared" + */ + withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}"; + + /* Create a fixed width string with additional prefix to match + required width. + + This function will fail if the input string is longer than the + requested length. + + Type: fixedWidthString :: int -> string -> string + + Example: + fixedWidthString 5 "0" (toString 15) + => "00015" + */ + fixedWidthString = width: filler: str: + let + strw = lib.stringLength str; + reqWidth = width - (lib.stringLength filler); + in + assert lib.assertMsg (strw <= width) + "fixedWidthString: requested string length (${ + toString width}) must not be shorter than actual length (${ + toString strw})"; + if strw == width then str else filler + fixedWidthString reqWidth filler str; + + /* Format a number adding leading zeroes up to fixed width. + + Example: + fixedWidthNumber 5 15 + => "00015" + */ + fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); + + /* Check whether a value can be coerced to a string */ + isCoercibleToString = x: + builtins.elem (builtins.typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || + (builtins.isList x && lib.all isCoercibleToString x) || + x ? outPath || + x ? __toString; + + /* Check whether a value is a store path. + + Example: + isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python" + => false + isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/" + => true + isStorePath pkgs.python + => true + isStorePath [] || isStorePath 42 || isStorePath {} || … + => false + */ + isStorePath = x: + if isCoercibleToString x then + let str = toString x; in + builtins.substring 0 1 str == "/" + && dirOf str == builtins.storeDir + else + false; + + /* Parse a string string as an int. + + Type: string -> int + + Example: + toInt "1337" + => 1337 + toInt "-4" + => -4 + toInt "3.14" + => error: floating point JSON numbers are not supported + */ + # Obviously, it is a bit hacky to use fromJSON this way. + toInt = str: + let may_be_int = builtins.fromJSON str; in + if builtins.isInt may_be_int + then may_be_int + else throw "Could not convert ${str} to int."; + + /* Read a list of paths from `file`, relative to the `rootPath`. + Lines beginning with `#` are treated as comments and ignored. + Whitespace is significant. + + NOTE: This function is not performant and should be avoided. + + Example: + readPathsFromFile /prefix + ./pkgs/development/libraries/qt-5/5.4/qtbase/series + => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch" + "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch" + "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch" + "/prefix/nix-profiles-library-paths.patch" + "/prefix/compose-search-path.patch" ] + */ + readPathsFromFile = rootPath: file: + let + lines = lib.splitString "\n" (builtins.readFile file); + removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); + relativePaths = removeComments lines; + absolutePaths = builtins.map (path: rootPath + "/${path}") relativePaths; + in + absolutePaths; + + /* Read the contents of a file removing the trailing \n + + Type: fileContents :: path -> string + + Example: + $ echo "1.0" > ./version + + fileContents ./version + => "1.0" + */ + fileContents = file: removeSuffix "\n" (builtins.readFile file); +} diff --git a/nixpkgs/lib/systems/default.nix b/nixpkgs/lib/systems/default.nix new file mode 100644 index 000000000000..25df5e174069 --- /dev/null +++ b/nixpkgs/lib/systems/default.nix @@ -0,0 +1,120 @@ +{ lib }: + let inherit (lib.attrsets) mapAttrs; in + +rec { + doubles = import ./doubles.nix { inherit lib; }; + forMeta = import ./for-meta.nix { inherit lib; }; + parse = import ./parse.nix { inherit lib; }; + inspect = import ./inspect.nix { inherit lib; }; + platforms = import ./platforms.nix { inherit lib; }; + examples = import ./examples.nix { inherit lib; }; + + # Elaborate a `localSystem` or `crossSystem` so that it contains everything + # necessary. + # + # `parsed` is inferred from args, both because there are two options with one + # clearly prefered, and to prevent cycles. A simpler fixed point where the RHS + # always just used `final.*` would fail on both counts. + elaborate = args: let + final = { + # Prefer to parse `config` as it is strictly more informative. + parsed = parse.mkSystemFromString (if args ? config then args.config else args.system); + # Either of these can be losslessly-extracted from `parsed` iff parsing succeeds. + system = parse.doubleFromSystem final.parsed; + config = parse.tripleFromSystem final.parsed; + # Just a guess, based on `system` + platform = platforms.selectBySystem final.system; + # Derived meta-data + libc = + /**/ if final.isDarwin then "libSystem" + else if final.isMinGW then "msvcrt" + else if final.isMusl then "musl" + else if final.isUClibc then "uclibc" + else if final.isAndroid then "bionic" + else if final.isLinux /* default */ then "glibc" + else if final.isAvr then "avrlibc" + # TODO(@Ericson2314) think more about other operating systems + else "native/impure"; + extensions = { + sharedLibrary = + /**/ if final.isDarwin then ".dylib" + else if final.isWindows then ".dll" + else ".so"; + executable = + /**/ if final.isWindows then ".exe" + else ""; + }; + # Misc boolean options + useAndroidPrebuilt = false; + useiOSPrebuilt = false; + + # Output from uname + uname = { + # uname -s + system = { + "linux" = "Linux"; + "windows" = "Windows"; + "darwin" = "Darwin"; + "netbsd" = "NetBSD"; + "freebsd" = "FreeBSD"; + "openbsd" = "OpenBSD"; + }.${final.parsed.kernel.name} or null; + + # uname -p + processor = final.parsed.cpu.name; + + # uname -r + release = null; + }; + + qemuArch = + if final.isArm then "arm" + else if final.isx86_64 then "x86_64" + else if final.isx86 then "i386" + else { + "powerpc" = "ppc"; + "powerpc64" = "ppc64"; + "powerpc64le" = "ppc64"; + "mips64" = "mips"; + "mipsel64" = "mipsel"; + }.${final.parsed.cpu.name} or final.parsed.cpu.name; + + emulator = pkgs: let + qemu-user = pkgs.qemu.override { + smartcardSupport = false; + spiceSupport = false; + openGLSupport = false; + virglSupport = false; + vncSupport = false; + gtkSupport = false; + sdlSupport = false; + pulseSupport = false; + smbdSupport = false; + seccompSupport = false; + hostCpuTargets = ["${final.qemuArch}-linux-user"]; + }; + wine-name = "wine${toString final.parsed.cpu.bits}"; + wine = (pkgs.winePackagesFor wine-name).minimal; + in + if final.parsed.kernel.name == pkgs.stdenv.hostPlatform.parsed.kernel.name && + (final.parsed.cpu.name == pkgs.stdenv.hostPlatform.parsed.cpu.name || + (final.platform.isi686 && pkgs.stdenv.hostPlatform.isx86_64)) + then pkgs.runtimeShell + else if final.isWindows + then "${wine}/bin/${wine-name}" + else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux + then "${qemu-user}/bin/qemu-${final.qemuArch}" + else throw "Don't know how to run ${final.config} executables."; + + } // mapAttrs (n: v: v final.parsed) inspect.predicates + // args; + in assert final.useAndroidPrebuilt -> final.isAndroid; + assert lib.foldl + (pass: { assertion, message }: + if assertion final + then pass + else throw message) + true + (final.parsed.abi.assertions or []); + final; +} diff --git a/nixpkgs/lib/systems/doubles.nix b/nixpkgs/lib/systems/doubles.nix new file mode 100644 index 000000000000..58677c0bdd90 --- /dev/null +++ b/nixpkgs/lib/systems/doubles.nix @@ -0,0 +1,51 @@ +{ lib }: +let + inherit (lib) lists; + inherit (lib.systems) parse; + inherit (lib.systems.inspect) predicates; + inherit (lib.attrsets) matchAttrs; + + all = [ + "aarch64-linux" + "armv5tel-linux" "armv6l-linux" "armv7l-linux" + + "mipsel-linux" + + "i686-cygwin" "i686-freebsd" "i686-linux" "i686-netbsd" "i686-openbsd" + + "x86_64-cygwin" "x86_64-darwin" "x86_64-freebsd" "x86_64-linux" + "x86_64-netbsd" "x86_64-openbsd" "x86_64-solaris" + + "x86_64-windows" "i686-windows" + ]; + + allParsed = map parse.mkSystemFromString all; + + filterDoubles = f: map parse.doubleFromSystem (lists.filter f allParsed); + +in rec { + inherit all; + + none = []; + + arm = filterDoubles predicates.isAarch32; + aarch64 = filterDoubles predicates.isAarch64; + x86 = filterDoubles predicates.isx86; + i686 = filterDoubles predicates.isi686; + x86_64 = filterDoubles predicates.isx86_64; + mips = filterDoubles predicates.isMips; + + cygwin = filterDoubles predicates.isCygwin; + darwin = filterDoubles predicates.isDarwin; + freebsd = filterDoubles predicates.isFreeBSD; + # Should be better, but MinGW is unclear. + gnu = filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnu; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabi; }) ++ filterDoubles (matchAttrs { kernel = parse.kernels.linux; abi = parse.abis.gnueabihf; }); + illumos = filterDoubles predicates.isSunOS; + linux = filterDoubles predicates.isLinux; + netbsd = filterDoubles predicates.isNetBSD; + openbsd = filterDoubles predicates.isOpenBSD; + unix = filterDoubles predicates.isUnix; + windows = filterDoubles predicates.isWindows; + + mesaPlatforms = ["i686-linux" "x86_64-linux" "x86_64-darwin" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "aarch64-linux" "powerpc64le-linux"]; +} diff --git a/nixpkgs/lib/systems/examples.nix b/nixpkgs/lib/systems/examples.nix new file mode 100644 index 000000000000..ac1633a1a15f --- /dev/null +++ b/nixpkgs/lib/systems/examples.nix @@ -0,0 +1,215 @@ +# These can be passed to nixpkgs as either the `localSystem` or +# `crossSystem`. They are put here for user convenience, but also used by cross +# tests and linux cross stdenv building, so handle with care! +{ lib }: +let + platforms = import ./platforms.nix { inherit lib; }; + + riscv = bits: { + config = "riscv${bits}-unknown-linux-gnu"; + platform = platforms.riscv-multiplatform bits; + }; +in + +rec { + # + # Linux + # + powernv = { + config = "powerpc64le-unknown-linux-gnu"; + platform = platforms.powernv; + }; + musl-power = { + config = "powerpc64le-unknown-linux-musl"; + platform = platforms.powernv; + }; + + sheevaplug = rec { + config = "armv5tel-unknown-linux-gnueabi"; + platform = platforms.sheevaplug; + }; + + raspberryPi = rec { + config = "armv6l-unknown-linux-gnueabihf"; + platform = platforms.raspberrypi; + }; + + armv7l-hf-multiplatform = rec { + config = "armv7l-unknown-linux-gnueabihf"; + platform = platforms.armv7l-hf-multiplatform; + }; + + aarch64-multiplatform = rec { + config = "aarch64-unknown-linux-gnu"; + platform = platforms.aarch64-multiplatform; + }; + + armv5te-android-prebuilt = rec { + config = "armv5tel-unknown-linux-androideabi"; + sdkVer = "21"; + ndkVer = "18b"; + platform = platforms.armv5te-android; + useAndroidPrebuilt = true; + }; + + armv7a-android-prebuilt = rec { + config = "armv7a-unknown-linux-androideabi"; + sdkVer = "24"; + ndkVer = "18b"; + platform = platforms.armv7a-android; + useAndroidPrebuilt = true; + }; + + aarch64-android-prebuilt = rec { + config = "aarch64-unknown-linux-android"; + sdkVer = "24"; + ndkVer = "18b"; + platform = platforms.aarch64-multiplatform; + useAndroidPrebuilt = true; + }; + + scaleway-c1 = armv7l-hf-multiplatform // rec { + platform = platforms.scaleway-c1; + inherit (platform.gcc) fpu; + }; + + pogoplug4 = rec { + config = "armv5tel-unknown-linux-gnueabi"; + platform = platforms.pogoplug4; + }; + + ben-nanonote = rec { + config = "mipsel-unknown-linux-uclibc"; + platform = platforms.ben_nanonote; + }; + + fuloongminipc = rec { + config = "mipsel-unknown-linux-gnu"; + platform = platforms.fuloong2f_n32; + }; + + muslpi = raspberryPi // { + config = "armv6l-unknown-linux-musleabihf"; + }; + + aarch64-multiplatform-musl = aarch64-multiplatform // { + config = "aarch64-unknown-linux-musl"; + }; + + musl64 = { config = "x86_64-unknown-linux-musl"; }; + musl32 = { config = "i686-unknown-linux-musl"; }; + + riscv64 = riscv "64"; + riscv32 = riscv "32"; + + avr = { + config = "avr"; + }; + + arm-embedded = { + config = "arm-none-eabi"; + libc = "newlib"; + }; + armhf-embedded = { + config = "arm-none-eabihf"; + libc = "newlib"; + }; + + aarch64-embedded = { + config = "aarch64-none-elf"; + libc = "newlib"; + }; + + aarch64be-embedded = { + config = "aarch64_be-none-elf"; + libc = "newlib"; + }; + + ppc-embedded = { + config = "powerpc-none-eabi"; + libc = "newlib"; + }; + + ppcle-embedded = { + config = "powerpcle-none-eabi"; + libc = "newlib"; + }; + + alpha-embedded = { + config = "alpha-elf"; + libc = "newlib"; + }; + + i686-embedded = { + config = "i686-elf"; + libc = "newlib"; + }; + + x86_64-embedded = { + config = "x86_64-elf"; + libc = "newlib"; + }; + + # + # Darwin + # + + iphone64 = { + config = "aarch64-apple-ios"; + # config = "aarch64-apple-darwin14"; + sdkVer = "10.2"; + xcodeVer = "8.2"; + xcodePlatform = "iPhoneOS"; + useiOSPrebuilt = true; + platform = {}; + }; + + iphone32 = { + config = "armv7a-apple-ios"; + # config = "arm-apple-darwin10"; + sdkVer = "10.2"; + xcodeVer = "8.2"; + xcodePlatform = "iPhoneOS"; + useiOSPrebuilt = true; + platform = {}; + }; + + iphone64-simulator = { + config = "x86_64-apple-ios"; + # config = "x86_64-apple-darwin14"; + sdkVer = "10.2"; + xcodeVer = "8.2"; + xcodePlatform = "iPhoneSimulator"; + useiOSPrebuilt = true; + platform = {}; + }; + + iphone32-simulator = { + config = "i686-apple-ios"; + # config = "i386-apple-darwin11"; + sdkVer = "10.2"; + xcodeVer = "8.2"; + xcodePlatform = "iPhoneSimulator"; + useiOSPrebuilt = true; + platform = {}; + }; + + # + # Windows + # + + # 32 bit mingw-w64 + mingw32 = { + config = "i686-pc-mingw32"; + libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain + platform = {}; + }; + + # 64 bit mingw-w64 + mingwW64 = { + # That's the triplet they use in the mingw-w64 docs. + config = "x86_64-pc-mingw32"; + libc = "msvcrt"; # This distinguishes the mingw (non posix) toolchain + platform = {}; + }; +} diff --git a/nixpkgs/lib/systems/for-meta.nix b/nixpkgs/lib/systems/for-meta.nix new file mode 100644 index 000000000000..51fb6ae760d1 --- /dev/null +++ b/nixpkgs/lib/systems/for-meta.nix @@ -0,0 +1,37 @@ +{ lib }: +let + inherit (lib.systems) parse; + inherit (lib.systems.inspect) patterns; + + abis = lib.mapAttrs (_: abi: builtins.removeAttrs abi [ "assertions" ]) parse.abis; + +in rec { + all = [ {} ]; # `{}` matches anything + none = []; + + arm = [ patterns.isAarch32 ]; + aarch64 = [ patterns.isAarch64 ]; + x86 = [ patterns.isx86 ]; + i686 = [ patterns.isi686 ]; + x86_64 = [ patterns.isx86_64 ]; + mips = [ patterns.isMips ]; + riscv = [ patterns.isRiscV ]; + + cygwin = [ patterns.isCygwin ]; + darwin = [ patterns.isDarwin ]; + freebsd = [ patterns.isFreeBSD ]; + # Should be better, but MinGW is unclear. + gnu = [ + { kernel = parse.kernels.linux; abi = abis.gnu; } + { kernel = parse.kernels.linux; abi = abis.gnueabi; } + { kernel = parse.kernels.linux; abi = abis.gnueabihf; } + ]; + illumos = [ patterns.isSunOS ]; + linux = [ patterns.isLinux ]; + netbsd = [ patterns.isNetBSD ]; + openbsd = [ patterns.isOpenBSD ]; + unix = patterns.isUnix; # Actually a list + windows = [ patterns.isWindows ]; + + inherit (lib.systems.doubles) mesaPlatforms; +} diff --git a/nixpkgs/lib/systems/inspect.nix b/nixpkgs/lib/systems/inspect.nix new file mode 100644 index 000000000000..2fcf1afe4628 --- /dev/null +++ b/nixpkgs/lib/systems/inspect.nix @@ -0,0 +1,60 @@ +{ lib }: +with import ./parse.nix { inherit lib; }; +with lib.attrsets; +with lib.lists; + +let abis_ = abis; in +let abis = lib.mapAttrs (_: abi: builtins.removeAttrs abi [ "assertions" ]) abis_; in + +rec { + patterns = rec { + isi686 = { cpu = cpuTypes.i686; }; + isx86_64 = { cpu = cpuTypes.x86_64; }; + isPowerPC = { cpu = cpuTypes.powerpc; }; + isPower = { cpu = { family = "power"; }; }; + isx86 = { cpu = { family = "x86"; }; }; + isAarch32 = { cpu = { family = "arm"; bits = 32; }; }; + isAarch64 = { cpu = { family = "arm"; bits = 64; }; }; + isMips = { cpu = { family = "mips"; }; }; + isRiscV = { cpu = { family = "riscv"; }; }; + isSparc = { cpu = { family = "sparc"; }; }; + isWasm = { cpu = { family = "wasm"; }; }; + isAvr = { cpu = { family = "avr"; }; }; + + is32bit = { cpu = { bits = 32; }; }; + is64bit = { cpu = { bits = 64; }; }; + isBigEndian = { cpu = { significantByte = significantBytes.bigEndian; }; }; + isLittleEndian = { cpu = { significantByte = significantBytes.littleEndian; }; }; + + isBSD = { kernel = { families = { inherit (kernelFamilies) bsd; }; }; }; + isDarwin = { kernel = { families = { inherit (kernelFamilies) darwin; }; }; }; + isUnix = [ isBSD isDarwin isLinux isSunOS isCygwin ]; + + isMacOS = { kernel = kernels.macos; }; + isiOS = { kernel = kernels.ios; }; + isLinux = { kernel = kernels.linux; }; + isSunOS = { kernel = kernels.solaris; }; + isFreeBSD = { kernel = kernels.freebsd; }; + isNetBSD = { kernel = kernels.netbsd; }; + isOpenBSD = { kernel = kernels.openbsd; }; + isWindows = { kernel = kernels.windows; }; + isCygwin = { kernel = kernels.windows; abi = abis.cygnus; }; + isMinGW = { kernel = kernels.windows; abi = abis.gnu; }; + + isAndroid = [ { abi = abis.android; } { abi = abis.androideabi; } ]; + isMusl = with abis; map (a: { abi = a; }) [ musl musleabi musleabihf ]; + isUClibc = with abis; map (a: { abi = a; }) [ uclibc uclibceabi uclibceabihf ]; + + isEfi = map (family: { cpu.family = family; }) + [ "x86" "arm" "aarch64" ]; + + # Deprecated after 18.03 + isArm = isAarch32; + }; + + matchAnyAttrs = patterns: + if builtins.isList patterns then attrs: any (pattern: matchAttrs pattern attrs) patterns + else matchAttrs patterns; + + predicates = mapAttrs (_: matchAnyAttrs) patterns; +} diff --git a/nixpkgs/lib/systems/parse.nix b/nixpkgs/lib/systems/parse.nix new file mode 100644 index 000000000000..7db09fc550e2 --- /dev/null +++ b/nixpkgs/lib/systems/parse.nix @@ -0,0 +1,352 @@ +# Define the list of system with their properties. +# +# See https://clang.llvm.org/docs/CrossCompilation.html and +# http://llvm.org/docs/doxygen/html/Triple_8cpp_source.html especially +# Triple::normalize. Parsing should essentially act as a more conservative +# version of that last function. +# +# Most of the types below come in "open" and "closed" pairs. The open ones +# specify what information we need to know about systems in general, and the +# closed ones are sub-types representing the whitelist of systems we support in +# practice. +# +# Code in the remainder of nixpkgs shouldn't rely on the closed ones in +# e.g. exhaustive cases. Its more a sanity check to make sure nobody defines +# systems that overlap with existing ones and won't notice something amiss. +# +{ lib }: +with lib.lists; +with lib.types; +with lib.attrsets; +with lib.strings; +with (import ./inspect.nix { inherit lib; }).predicates; + +let + inherit (lib.options) mergeOneOption; + + setTypes = type: + mapAttrs (name: value: + assert type.check value; + setType type.name ({ inherit name; } // value)); + +in + +rec { + + ################################################################################ + + types.openSignificantByte = mkOptionType { + name = "significant-byte"; + description = "Endianness"; + merge = mergeOneOption; + }; + + types.significantByte = enum (attrValues significantBytes); + + significantBytes = setTypes types.openSignificantByte { + bigEndian = {}; + littleEndian = {}; + }; + + ################################################################################ + + # Reasonable power of 2 + types.bitWidth = enum [ 8 16 32 64 128 ]; + + ################################################################################ + + types.openCpuType = mkOptionType { + name = "cpu-type"; + description = "instruction set architecture name and information"; + merge = mergeOneOption; + check = x: types.bitWidth.check x.bits + && (if 8 < x.bits + then types.significantByte.check x.significantByte + else !(x ? significantByte)); + }; + + types.cpuType = enum (attrValues cpuTypes); + + cpuTypes = with significantBytes; setTypes types.openCpuType { + arm = { bits = 32; significantByte = littleEndian; family = "arm"; }; + armv5tel = { bits = 32; significantByte = littleEndian; family = "arm"; version = "5"; }; + armv6m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "6"; }; + armv6l = { bits = 32; significantByte = littleEndian; family = "arm"; version = "6"; }; + armv7a = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; }; + armv7r = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; }; + armv7m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; }; + armv7l = { bits = 32; significantByte = littleEndian; family = "arm"; version = "7"; }; + armv8a = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; }; + armv8r = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; }; + armv8m = { bits = 32; significantByte = littleEndian; family = "arm"; version = "8"; }; + aarch64 = { bits = 64; significantByte = littleEndian; family = "arm"; version = "8"; }; + aarch64_be = { bits = 64; significantByte = bigEndian; family = "arm"; version = "8"; }; + + i386 = { bits = 32; significantByte = littleEndian; family = "x86"; }; + i486 = { bits = 32; significantByte = littleEndian; family = "x86"; }; + i586 = { bits = 32; significantByte = littleEndian; family = "x86"; }; + i686 = { bits = 32; significantByte = littleEndian; family = "x86"; }; + x86_64 = { bits = 64; significantByte = littleEndian; family = "x86"; }; + + mips = { bits = 32; significantByte = bigEndian; family = "mips"; }; + mipsel = { bits = 32; significantByte = littleEndian; family = "mips"; }; + mips64 = { bits = 64; significantByte = bigEndian; family = "mips"; }; + mips64el = { bits = 64; significantByte = littleEndian; family = "mips"; }; + + powerpc = { bits = 32; significantByte = bigEndian; family = "power"; }; + powerpc64 = { bits = 64; significantByte = bigEndian; family = "power"; }; + powerpc64le = { bits = 64; significantByte = littleEndian; family = "power"; }; + powerpcle = { bits = 32; significantByte = littleEndian; family = "power"; }; + + riscv32 = { bits = 32; significantByte = littleEndian; family = "riscv"; }; + riscv64 = { bits = 64; significantByte = littleEndian; family = "riscv"; }; + + sparc = { bits = 32; significantByte = bigEndian; family = "sparc"; }; + sparc64 = { bits = 64; significantByte = bigEndian; family = "sparc"; }; + + wasm32 = { bits = 32; significantByte = littleEndian; family = "wasm"; }; + wasm64 = { bits = 64; significantByte = littleEndian; family = "wasm"; }; + + alpha = { bits = 64; significantByte = littleEndian; family = "alpha"; }; + + avr = { bits = 8; family = "avr"; }; + }; + + ################################################################################ + + types.openVendor = mkOptionType { + name = "vendor"; + description = "vendor for the platform"; + merge = mergeOneOption; + }; + + types.vendor = enum (attrValues vendors); + + vendors = setTypes types.openVendor { + apple = {}; + pc = {}; + + none = {}; + unknown = {}; + }; + + ################################################################################ + + types.openExecFormat = mkOptionType { + name = "exec-format"; + description = "executable container used by the kernel"; + merge = mergeOneOption; + }; + + types.execFormat = enum (attrValues execFormats); + + execFormats = setTypes types.openExecFormat { + aout = {}; # a.out + elf = {}; + macho = {}; + pe = {}; + + unknown = {}; + }; + + ################################################################################ + + types.openKernelFamily = mkOptionType { + name = "exec-format"; + description = "executable container used by the kernel"; + merge = mergeOneOption; + }; + + types.kernelFamily = enum (attrValues kernelFamilies); + + kernelFamilies = setTypes types.openKernelFamily { + bsd = {}; + darwin = {}; + }; + + ################################################################################ + + types.openKernel = mkOptionType { + name = "kernel"; + description = "kernel name and information"; + merge = mergeOneOption; + check = x: types.execFormat.check x.execFormat + && all types.kernelFamily.check (attrValues x.families); + }; + + types.kernel = enum (attrValues kernels); + + kernels = with execFormats; with kernelFamilies; setTypes types.openKernel { + # TODO(@Ericson2314): Don't want to mass-rebuild yet to keeping 'darwin' as + # the nnormalized name for macOS. + macos = { execFormat = macho; families = { inherit darwin; }; name = "darwin"; }; + ios = { execFormat = macho; families = { inherit darwin; }; }; + freebsd = { execFormat = elf; families = { inherit bsd; }; }; + linux = { execFormat = elf; families = { }; }; + netbsd = { execFormat = elf; families = { inherit bsd; }; }; + none = { execFormat = unknown; families = { }; }; + openbsd = { execFormat = elf; families = { inherit bsd; }; }; + solaris = { execFormat = elf; families = { }; }; + windows = { execFormat = pe; families = { }; }; + } // { # aliases + # 'darwin' is the kernel for all of them. We choose macOS by default. + darwin = kernels.macos; + watchos = kernels.ios; + tvos = kernels.ios; + win32 = kernels.windows; + }; + + ################################################################################ + + types.openAbi = mkOptionType { + name = "abi"; + description = "binary interface for compiled code and syscalls"; + merge = mergeOneOption; + }; + + types.abi = enum (attrValues abis); + + abis = setTypes types.openAbi { + cygnus = {}; + msvc = {}; + + # Note: eabi is specific to ARM and PowerPC. + # On PowerPC, this corresponds to PPCEABI. + # On ARM, this corresponds to ARMEABI. + eabi = { float = "soft"; }; + eabihf = { float = "hard"; }; + + # Other architectures should use ELF in embedded situations. + elf = {}; + + androideabi = {}; + android = { + assertions = [ + { assertion = platform: !platform.isAarch32; + message = '' + The "android" ABI is not for 32-bit ARM. Use "androideabi" instead. + ''; + } + ]; + }; + + gnueabi = { float = "soft"; }; + gnueabihf = { float = "hard"; }; + gnu = { + assertions = [ + { assertion = platform: !platform.isAarch32; + message = '' + The "gnu" ABI is ambiguous on 32-bit ARM. Use "gnueabi" or "gnueabihf" instead. + ''; + } + ]; + }; + + musleabi = { float = "soft"; }; + musleabihf = { float = "hard"; }; + musl = {}; + + uclibceabihf = { float = "soft"; }; + uclibceabi = { float = "hard"; }; + uclibc = {}; + + unknown = {}; + }; + + ################################################################################ + + types.parsedPlatform = mkOptionType { + name = "system"; + description = "fully parsed representation of llvm- or nix-style platform tuple"; + merge = mergeOneOption; + check = { cpu, vendor, kernel, abi }: + types.cpuType.check cpu + && types.vendor.check vendor + && types.kernel.check kernel + && types.abi.check abi; + }; + + isSystem = isType "system"; + + mkSystem = components: + assert types.parsedPlatform.check components; + setType "system" components; + + mkSkeletonFromList = l: { + "1" = if elemAt l 0 == "avr" + then { cpu = elemAt l 0; kernel = "none"; abi = "unknown"; } + else throw "Target specification with 1 components is ambiguous"; + "2" = # We only do 2-part hacks for things Nix already supports + if elemAt l 1 == "cygwin" + then { cpu = elemAt l 0; kernel = "windows"; abi = "cygnus"; } + else if (elemAt l 1) == "elf" + then { cpu = elemAt l 0; vendor = "unknown"; kernel = "none"; abi = elemAt l 1; } + else { cpu = elemAt l 0; kernel = elemAt l 1; }; + "3" = # Awkwards hacks, beware! + if elemAt l 1 == "apple" + then { cpu = elemAt l 0; vendor = "apple"; kernel = elemAt l 2; } + else if (elemAt l 1 == "linux") || (elemAt l 2 == "gnu") + then { cpu = elemAt l 0; kernel = elemAt l 1; abi = elemAt l 2; } + else if (elemAt l 2 == "mingw32") # autotools breaks on -gnu for window + then { cpu = elemAt l 0; vendor = elemAt l 1; kernel = "windows"; abi = "gnu"; } + else if hasPrefix "netbsd" (elemAt l 2) + then { cpu = elemAt l 0; vendor = elemAt l 1; kernel = elemAt l 2; } + else if (elem (elemAt l 2) ["eabi" "eabihf" "elf"]) + then { cpu = elemAt l 0; vendor = "unknown"; kernel = elemAt l 1; abi = elemAt l 2; } + else throw "Target specification with 3 components is ambiguous"; + "4" = { cpu = elemAt l 0; vendor = elemAt l 1; kernel = elemAt l 2; abi = elemAt l 3; }; + }.${toString (length l)} + or (throw "system string has invalid number of hyphen-separated components"); + + # This should revert the job done by config.guess from the gcc compiler. + mkSystemFromSkeleton = { cpu + , # Optional, but fallback too complex for here. + # Inferred below instead. + vendor ? assert false; null + , kernel + , # Also inferred below + abi ? assert false; null + } @ args: let + getCpu = name: cpuTypes.${name} or (throw "Unknown CPU type: ${name}"); + getVendor = name: vendors.${name} or (throw "Unknown vendor: ${name}"); + getKernel = name: kernels.${name} or (throw "Unknown kernel: ${name}"); + getAbi = name: abis.${name} or (throw "Unknown ABI: ${name}"); + + parsed = rec { + cpu = getCpu args.cpu; + vendor = + /**/ if args ? vendor then getVendor args.vendor + else if isDarwin parsed then vendors.apple + else if isWindows parsed then vendors.pc + else vendors.unknown; + kernel = if hasPrefix "darwin" args.kernel then getKernel "darwin" + else if hasPrefix "netbsd" args.kernel then getKernel "netbsd" + else getKernel args.kernel; + abi = + /**/ if args ? abi then getAbi args.abi + else if isLinux parsed then + if isAarch32 parsed then + if lib.versionAtLeast (parsed.cpu.version or "0") "6" + then abis.gnueabihf + else abis.gnueabi + else abis.gnu + else if isWindows parsed then abis.gnu + else abis.unknown; + }; + + in mkSystem parsed; + + mkSystemFromString = s: mkSystemFromSkeleton (mkSkeletonFromList (lib.splitString "-" s)); + + doubleFromSystem = { cpu, vendor, kernel, abi, ... }: + /**/ if abi == abis.cygnus then "${cpu.name}-cygwin" + else if kernel.families ? darwin then "${cpu.name}-darwin" + else "${cpu.name}-${kernel.name}"; + + tripleFromSystem = { cpu, vendor, kernel, abi, ... } @ sys: assert isSystem sys; let + optAbi = lib.optionalString (abi != abis.unknown) "-${abi.name}"; + in "${cpu.name}-${vendor.name}-${kernel.name}${optAbi}"; + + ################################################################################ + +} diff --git a/nixpkgs/lib/systems/platforms.nix b/nixpkgs/lib/systems/platforms.nix new file mode 100644 index 000000000000..1ed072e94645 --- /dev/null +++ b/nixpkgs/lib/systems/platforms.nix @@ -0,0 +1,480 @@ +{ lib }: +rec { + pcBase = { + name = "pc"; + kernelBaseConfig = "defconfig"; + # Build whatever possible as a module, if not stated in the extra config. + kernelAutoModules = true; + kernelTarget = "bzImage"; + }; + + pc64 = pcBase // { kernelArch = "x86_64"; }; + + pc32 = pcBase // { kernelArch = "i386"; }; + + pc32_simplekernel = pc32 // { + kernelAutoModules = false; + }; + + pc64_simplekernel = pc64 // { + kernelAutoModules = false; + }; + + powernv = { + name = "PowerNV"; + kernelArch = "powerpc"; + kernelBaseConfig = "powernv_defconfig"; + kernelTarget = "zImage"; + kernelInstallTarget = "install"; + kernelFile = "vmlinux"; + kernelAutoModules = true; + # avoid driver/FS trouble arising from unusual page size + kernelExtraConfig = '' + PPC_64K_PAGES n + PPC_4K_PAGES y + IPV6 y + ''; + }; + + ## + ## ARM + ## + + pogoplug4 = { + name = "pogoplug4"; + + gcc = { + arch = "armv5te"; + }; + + kernelMajor = "2.6"; + kernelBaseConfig = "multi_v5_defconfig"; + kernelArch = "arm"; + kernelAutoModules = false; + kernelExtraConfig = + '' + # Ubi for the mtd + MTD_UBI y + UBIFS_FS y + UBIFS_FS_XATTR y + UBIFS_FS_ADVANCED_COMPR y + UBIFS_FS_LZO y + UBIFS_FS_ZLIB y + UBIFS_FS_DEBUG n + ''; + kernelMakeFlags = [ "LOADADDR=0x8000" ]; + kernelTarget = "uImage"; + # TODO reenable once manual-config's config actually builds a .dtb and this is checked to be working + #kernelDTB = true; + }; + + sheevaplug = { + name = "sheevaplug"; + kernelMajor = "2.6"; + kernelBaseConfig = "multi_v5_defconfig"; + kernelArch = "arm"; + kernelAutoModules = false; + kernelExtraConfig = '' + BLK_DEV_RAM y + BLK_DEV_INITRD y + BLK_DEV_CRYPTOLOOP m + BLK_DEV_DM m + DM_CRYPT m + MD y + REISERFS_FS m + BTRFS_FS m + XFS_FS m + JFS_FS m + EXT4_FS m + USB_STORAGE_CYPRESS_ATACB m + + # mv cesa requires this sw fallback, for mv-sha1 + CRYPTO_SHA1 y + # Fast crypto + CRYPTO_TWOFISH y + CRYPTO_TWOFISH_COMMON y + CRYPTO_BLOWFISH y + CRYPTO_BLOWFISH_COMMON y + + IP_PNP y + IP_PNP_DHCP y + NFS_FS y + ROOT_NFS y + TUN m + NFS_V4 y + NFS_V4_1 y + NFS_FSCACHE y + NFSD m + NFSD_V2_ACL y + NFSD_V3 y + NFSD_V3_ACL y + NFSD_V4 y + NETFILTER y + IP_NF_IPTABLES y + IP_NF_FILTER y + IP_NF_MATCH_ADDRTYPE y + IP_NF_TARGET_LOG y + IP_NF_MANGLE y + IPV6 m + VLAN_8021Q m + + CIFS y + CIFS_XATTR y + CIFS_POSIX y + CIFS_FSCACHE y + CIFS_ACL y + + WATCHDOG y + WATCHDOG_CORE y + ORION_WATCHDOG m + + ZRAM m + NETCONSOLE m + + # Disable OABI to have seccomp_filter (required for systemd) + # https://github.com/raspberrypi/firmware/issues/651 + OABI_COMPAT n + + # Fail to build + DRM n + SCSI_ADVANSYS n + USB_ISP1362_HCD n + SND_SOC n + SND_ALI5451 n + FB_SAVAGE n + SCSI_NSP32 n + ATA_SFF n + SUNGEM n + IRDA n + ATM_HE n + SCSI_ACARD n + BLK_DEV_CMD640_ENHANCED n + + FUSE_FS m + + # systemd uses cgroups + CGROUPS y + + # Latencytop + LATENCYTOP y + + # Ubi for the mtd + MTD_UBI y + UBIFS_FS y + UBIFS_FS_XATTR y + UBIFS_FS_ADVANCED_COMPR y + UBIFS_FS_LZO y + UBIFS_FS_ZLIB y + UBIFS_FS_DEBUG n + + # Kdb, for kernel troubles + KGDB y + KGDB_SERIAL_CONSOLE y + KGDB_KDB y + ''; + kernelMakeFlags = [ "LOADADDR=0x0200000" ]; + kernelTarget = "uImage"; + kernelDTB = true; # Beyond 3.10 + gcc = { + arch = "armv5te"; + }; + }; + + raspberrypi = { + name = "raspberrypi"; + kernelMajor = "2.6"; + kernelBaseConfig = "bcm2835_defconfig"; + kernelDTB = true; + kernelArch = "arm"; + kernelAutoModules = true; + kernelPreferBuiltin = true; + kernelExtraConfig = '' + # Disable OABI to have seccomp_filter (required for systemd) + # https://github.com/raspberrypi/firmware/issues/651 + OABI_COMPAT n + ''; + kernelTarget = "zImage"; + gcc = { + arch = "armv6"; + fpu = "vfp"; + }; + }; + + # Legacy attribute, for compatibility with existing configs only. + raspberrypi2 = armv7l-hf-multiplatform; + + scaleway-c1 = armv7l-hf-multiplatform // { + gcc = { + cpu = "cortex-a9"; + fpu = "vfpv3"; + }; + }; + + utilite = { + name = "utilite"; + kernelMajor = "2.6"; + kernelBaseConfig = "multi_v7_defconfig"; + kernelArch = "arm"; + kernelAutoModules = false; + kernelExtraConfig = + '' + # Ubi for the mtd + MTD_UBI y + UBIFS_FS y + UBIFS_FS_XATTR y + UBIFS_FS_ADVANCED_COMPR y + UBIFS_FS_LZO y + UBIFS_FS_ZLIB y + UBIFS_FS_DEBUG n + ''; + kernelMakeFlags = [ "LOADADDR=0x10800000" ]; + kernelTarget = "uImage"; + kernelDTB = true; + gcc = { + cpu = "cortex-a9"; + fpu = "neon"; + }; + }; + + guruplug = sheevaplug // { + # Define `CONFIG_MACH_GURUPLUG' (see + # <http://kerneltrap.org/mailarchive/git-commits-head/2010/5/19/33618>) + # and other GuruPlug-specific things. Requires the `guruplug-defconfig' + # patch. + + kernelBaseConfig = "guruplug_defconfig"; + }; + + beaglebone = armv7l-hf-multiplatform // { + name = "beaglebone"; + kernelBaseConfig = "bb.org_defconfig"; + kernelAutoModules = false; + kernelExtraConfig = ""; # TBD kernel config + kernelTarget = "zImage"; + }; + + # https://developer.android.com/ndk/guides/abis#armeabi + armv5te-android = { + name = "armeabi"; + gcc = { + arch = "armv5te"; + float = "soft"; + float-abi = "soft"; + }; + }; + + # https://developer.android.com/ndk/guides/abis#v7a + armv7a-android = { + name = "armeabi-v7a"; + gcc = { + arch = "armv7-a"; + float = "hard"; + float-abi = "softfp"; + fpu = "vfpv3-d16"; + }; + }; + + armv7l-hf-multiplatform = { + name = "armv7l-hf-multiplatform"; + kernelMajor = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc. + kernelBaseConfig = "multi_v7_defconfig"; + kernelArch = "arm"; + kernelDTB = true; + kernelAutoModules = true; + kernelPreferBuiltin = true; + kernelTarget = "zImage"; + kernelExtraConfig = '' + # Serial port for Raspberry Pi 3. Upstream forgot to add it to the ARMv7 defconfig. + SERIAL_8250_BCM2835AUX y + SERIAL_8250_EXTENDED y + SERIAL_8250_SHARE_IRQ y + + # Fix broken sunxi-sid nvmem driver. + TI_CPTS y + + # Hangs ODROID-XU4 + ARM_BIG_LITTLE_CPUIDLE n + + # Disable OABI to have seccomp_filter (required for systemd) + # https://github.com/raspberrypi/firmware/issues/651 + OABI_COMPAT n + ''; + gcc = { + # Some table about fpu flags: + # http://community.arm.com/servlet/JiveServlet/showImage/38-1981-3827/blogentry-103749-004812900+1365712953_thumb.png + # Cortex-A5: -mfpu=neon-fp16 + # Cortex-A7 (rpi2): -mfpu=neon-vfpv4 + # Cortex-A8 (beaglebone): -mfpu=neon + # Cortex-A9: -mfpu=neon-fp16 + # Cortex-A15: -mfpu=neon-vfpv4 + + # More about FPU: + # https://wiki.debian.org/ArmHardFloatPort/VfpComparison + + # vfpv3-d16 is what Debian uses and seems to be the best compromise: NEON is not supported in e.g. Scaleway or Tegra 2, + # and the above page suggests NEON is only an improvement with hand-written assembly. + arch = "armv7-a"; + fpu = "vfpv3-d16"; + + # For Raspberry Pi the 2 the best would be: + # cpu = "cortex-a7"; + # fpu = "neon-vfpv4"; + }; + }; + + aarch64-multiplatform = { + name = "aarch64-multiplatform"; + kernelMajor = "2.6"; # Using "2.6" enables 2.6 kernel syscalls in glibc. + kernelBaseConfig = "defconfig"; + kernelArch = "arm64"; + kernelDTB = true; + kernelAutoModules = true; + kernelPreferBuiltin = true; + kernelExtraConfig = '' + # Raspberry Pi 3 stuff. Not needed for kernels >= 4.10. + ARCH_BCM2835 y + BCM2835_MBOX y + BCM2835_WDT y + RASPBERRYPI_FIRMWARE y + RASPBERRYPI_POWER y + SERIAL_8250_BCM2835AUX y + SERIAL_8250_EXTENDED y + SERIAL_8250_SHARE_IRQ y + + # Cavium ThunderX stuff. + PCI_HOST_THUNDER_ECAM y + + # Nvidia Tegra stuff. + PCI_TEGRA y + + # The default (=y) forces us to have the XHCI firmware available in initrd, + # which our initrd builder can't currently do easily. + USB_XHCI_TEGRA m + ''; + kernelTarget = "Image"; + gcc = { + arch = "armv8-a"; + }; + }; + + ## + ## MIPS + ## + + ben_nanonote = { + name = "ben_nanonote"; + kernelMajor = "2.6"; + kernelArch = "mips"; + gcc = { + arch = "mips32"; + float = "soft"; + }; + }; + + fuloong2f_n32 = { + name = "fuloong2f_n32"; + kernelMajor = "2.6"; + kernelBaseConfig = "lemote2f_defconfig"; + kernelArch = "mips"; + kernelAutoModules = false; + kernelExtraConfig = '' + MIGRATION n + COMPACTION n + + # nixos mounts some cgroup + CGROUPS y + + BLK_DEV_RAM y + BLK_DEV_INITRD y + BLK_DEV_CRYPTOLOOP m + BLK_DEV_DM m + DM_CRYPT m + MD y + REISERFS_FS m + EXT4_FS m + USB_STORAGE_CYPRESS_ATACB m + + IP_PNP y + IP_PNP_DHCP y + IP_PNP_BOOTP y + NFS_FS y + ROOT_NFS y + TUN m + NFS_V4 y + NFS_V4_1 y + NFS_FSCACHE y + NFSD m + NFSD_V2_ACL y + NFSD_V3 y + NFSD_V3_ACL y + NFSD_V4 y + + # Fail to build + DRM n + SCSI_ADVANSYS n + USB_ISP1362_HCD n + SND_SOC n + SND_ALI5451 n + FB_SAVAGE n + SCSI_NSP32 n + ATA_SFF n + SUNGEM n + IRDA n + ATM_HE n + SCSI_ACARD n + BLK_DEV_CMD640_ENHANCED n + + FUSE_FS m + + # Needed for udev >= 150 + SYSFS_DEPRECATED_V2 n + + VGA_CONSOLE n + VT_HW_CONSOLE_BINDING y + SERIAL_8250_CONSOLE y + FRAMEBUFFER_CONSOLE y + EXT2_FS y + EXT3_FS y + REISERFS_FS y + MAGIC_SYSRQ y + + # The kernel doesn't boot at all, with FTRACE + FTRACE n + ''; + kernelTarget = "vmlinux"; + gcc = { + arch = "loongson2f"; + float = "hard"; + abi = "n32"; + }; + }; + + ## + ## Other + ## + + riscv-multiplatform = bits: { + name = "riscv-multiplatform"; + kernelArch = "riscv"; + bfdEmulation = "elf${bits}lriscv"; + kernelTarget = "vmlinux"; + kernelAutoModules = true; + kernelBaseConfig = "defconfig"; + kernelExtraConfig = '' + FTRACE n + SERIAL_OF_PLATFORM y + ''; + }; + + selectBySystem = system: { + "i686-linux" = pc32; + "x86_64-linux" = pc64; + "armv5tel-linux" = sheevaplug; + "armv6l-linux" = raspberrypi; + "armv7a-linux" = armv7l-hf-multiplatform; + "armv7l-linux" = armv7l-hf-multiplatform; + "aarch64-linux" = aarch64-multiplatform; + "mipsel-linux" = fuloong2f_n32; + "powerpc64le-linux" = powernv; + }.${system} or pcBase; +} diff --git a/nixpkgs/lib/tests/check-eval.nix b/nixpkgs/lib/tests/check-eval.nix new file mode 100644 index 000000000000..8bd7b605a39b --- /dev/null +++ b/nixpkgs/lib/tests/check-eval.nix @@ -0,0 +1,7 @@ +# Throws an error if any of our lib tests fail. + +let tests = [ "misc" "systems" ]; + all = builtins.concatLists (map (f: import (./. + "/${f}.nix")) tests); +in if all == [] + then null + else throw (builtins.toJSON all) diff --git a/nixpkgs/lib/tests/misc.nix b/nixpkgs/lib/tests/misc.nix new file mode 100644 index 000000000000..1604fbb39cbb --- /dev/null +++ b/nixpkgs/lib/tests/misc.nix @@ -0,0 +1,442 @@ +# to run these tests: +# nix-instantiate --eval --strict nixpkgs/lib/tests/misc.nix +# if the resulting list is empty, all tests passed +with import ../default.nix; + +runTests { + + +# TRIVIAL + + 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";}; + }; + + testComposeExtensions = { + expr = let obj = makeExtensible (self: { foo = self.bar; }); + f = self: super: { bar = false; baz = true; }; + g = self: super: { bar = super.baz or false; }; + f_o_g = composeExtensions f g; + composed = obj.extend f_o_g; + in composed.foo; + expected = true; + }; + + testBitAnd = { + expr = (bitAnd 3 10); + expected = 2; + }; + + testBitOr = { + expr = (bitOr 3 10); + expected = 11; + }; + + testBitXor = { + expr = (bitXor 3 10); + expected = 9; + }; + +# STRINGS + + testConcatMapStrings = { + expr = concatMapStrings (x: x + ";") ["a" "b" "c"]; + expected = "a;b;c;"; + }; + + testConcatStringsSep = { + expr = concatStringsSep "," ["a" "b" "c"]; + expected = "a,b,c"; + }; + + testSplitStringsSimple = { + expr = strings.splitString "." "a.b.c.d"; + expected = [ "a" "b" "c" "d" ]; + }; + + testSplitStringsEmpty = { + expr = strings.splitString "." "a..b"; + expected = [ "a" "" "b" ]; + }; + + testSplitStringsOne = { + expr = strings.splitString ":" "a.b"; + expected = [ "a.b" ]; + }; + + testSplitStringsNone = { + expr = strings.splitString "." ""; + expected = [ "" ]; + }; + + testSplitStringsFirstEmpty = { + expr = strings.splitString "/" "/a/b/c"; + expected = [ "" "a" "b" "c" ]; + }; + + testSplitStringsLastEmpty = { + expr = strings.splitString ":" "2001:db8:0:0042::8a2e:370:"; + expected = [ "2001" "db8" "0" "0042" "" "8a2e" "370" "" ]; + }; + + testIsStorePath = { + expr = + let goodPath = + "${builtins.storeDir}/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"; + in { + storePath = isStorePath goodPath; + storePathDerivation = isStorePath (import ../.. {}).hello; + storePathAppendix = isStorePath + "${goodPath}/bin/python"; + nonAbsolute = isStorePath (concatStrings (tail (stringToCharacters goodPath))); + asPath = isStorePath (/. + goodPath); + otherPath = isStorePath "/something/else"; + otherVals = { + attrset = isStorePath {}; + list = isStorePath []; + int = isStorePath 42; + }; + }; + expected = { + storePath = true; + storePathDerivation = true; + storePathAppendix = false; + nonAbsolute = false; + asPath = true; + otherPath = false; + otherVals = { + attrset = false; + list = false; + int = false; + }; + }; + }; + +# LISTS + + testFilter = { + expr = filter (x: x != "a") ["a" "b" "c" "a"]; + expected = ["b" "c"]; + }; + + testFold = + let + f = op: fold: fold op 0 (range 0 100); + # fold with associative operator + assoc = f builtins.add; + # fold with non-associative operator + nonAssoc = f builtins.sub; + in { + expr = { + assocRight = assoc foldr; + # right fold with assoc operator is same as left fold + assocRightIsLeft = assoc foldr == assoc foldl; + nonAssocRight = nonAssoc foldr; + nonAssocLeft = nonAssoc foldl; + # with non-assoc operator the fold results are not the same + nonAssocRightIsNotLeft = nonAssoc foldl != nonAssoc foldr; + # fold is an alias for foldr + foldIsRight = nonAssoc fold == nonAssoc foldr; + }; + expected = { + assocRight = 5050; + assocRightIsLeft = true; + nonAssocRight = 50; + nonAssocLeft = (-5050); + nonAssocRightIsNotLeft = true; + foldIsRight = true; + }; + }; + + 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];}; + }; + + testSort = { + expr = sort builtins.lessThan [ 40 2 30 42 ]; + expected = [2 30 40 42]; + }; + + testToIntShouldConvertStringToInt = { + expr = toInt "27"; + expected = 27; + }; + + testToIntShouldThrowErrorIfItCouldNotConvertToInt = { + expr = builtins.tryEval (toInt "\"foo\""); + expected = { success = false; value = false; }; + }; + + testHasAttrByPathTrue = { + expr = hasAttrByPath ["a" "b"] { a = { b = "yey"; }; }; + expected = true; + }; + + testHasAttrByPathFalse = { + expr = hasAttrByPath ["a" "b"] { a = { c = "yey"; }; }; + expected = false; + }; + + +# ATTRSETS + + # code from the example + testRecursiveUpdateUntil = { + expr = 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; + }; + expected = { + 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 + }; + }; + + testOverrideExistingEmpty = { + expr = overrideExisting {} { a = 1; }; + expected = {}; + }; + + testOverrideExistingDisjoint = { + expr = overrideExisting { b = 2; } { a = 1; }; + expected = { b = 2; }; + }; + + testOverrideExistingOverride = { + expr = overrideExisting { a = 3; b = 2; } { a = 1; }; + expected = { a = 1; b = 2; }; + }; + +# GENERATORS +# these tests assume attributes are converted to lists +# in alphabetical order + + testMkKeyValueDefault = { + expr = generators.mkKeyValueDefault {} ":" "f:oo" "bar"; + expected = ''f\:oo:bar''; + }; + + testMkValueString = { + expr = let + vals = { + int = 42; + string = ''fo"o''; + bool = true; + bool2 = false; + null = null; + # float = 42.23; # floats are strange + }; + in mapAttrs + (const (generators.mkValueStringDefault {})) + vals; + expected = { + int = "42"; + string = ''fo"o''; + bool = "true"; + bool2 = "false"; + null = "null"; + # float = "42.23" true false [ "bar" ] ]''; + }; + }; + + testToKeyValue = { + expr = generators.toKeyValue {} { + key = "value"; + "other=key" = "baz"; + }; + expected = '' + key=value + other\=key=baz + ''; + }; + + testToINIEmpty = { + expr = generators.toINI {} {}; + expected = ""; + }; + + testToINIEmptySection = { + expr = generators.toINI {} { foo = {}; bar = {}; }; + expected = '' + [bar] + + [foo] + ''; + }; + + testToINIDefaultEscapes = { + expr = generators.toINI {} { + "no [ and ] allowed unescaped" = { + "and also no = in keys" = 42; + }; + }; + expected = '' + [no \[ and \] allowed unescaped] + and also no \= in keys=42 + ''; + }; + + testToINIDefaultFull = { + expr = generators.toINI {} { + "section 1" = { + attribute1 = 5; + x = "Me-se JarJar Binx"; + # booleans are converted verbatim by default + boolean = false; + }; + "foo[]" = { + "he\\h=he" = "this is okay"; + }; + }; + expected = '' + [foo\[\]] + he\h\=he=this is okay + + [section 1] + attribute1=5 + boolean=false + x=Me-se JarJar Binx + ''; + }; + + /* right now only invocation check */ + testToJSONSimple = + let val = { + foobar = [ "baz" 1 2 3 ]; + }; + in { + expr = generators.toJSON {} val; + # trivial implementation + expected = builtins.toJSON val; + }; + + /* right now only invocation check */ + testToYAMLSimple = + let val = { + list = [ { one = 1; } { two = 2; } ]; + all = 42; + }; + in { + expr = generators.toYAML {} val; + # trivial implementation + expected = builtins.toJSON val; + }; + + testToPretty = { + expr = mapAttrs (const (generators.toPretty {})) rec { + int = 42; + float = 0.1337; + bool = true; + string = ''fno"rd''; + path = /. + "/foo"; + null_ = null; + function = x: x; + functionArgs = { arg ? 4, foo }: arg; + list = [ 3 4 function [ false ] ]; + attrs = { foo = null; "foo bar" = "baz"; }; + drv = derivation { name = "test"; system = builtins.currentSystem; }; + }; + expected = rec { + int = "42"; + float = "~0.133700"; + bool = "true"; + string = ''"fno\"rd"''; + path = "/foo"; + null_ = "null"; + function = "<λ>"; + functionArgs = "<λ:{(arg),foo}>"; + list = "[ 3 4 ${function} [ false ] ]"; + attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }"; + drv = "<δ:test>"; + }; + }; + + testToPrettyAllowPrettyValues = { + expr = generators.toPretty { allowPrettyValues = true; } + { __pretty = v: "«" + v + "»"; val = "foo"; }; + expected = "«foo»"; + }; + + +# MISC + + 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 = builtins.add x.a 3; }); + res6 = let x = defaultOverridableDelayableArgs id { a = 7; mergeAttrBy = { a = builtins.add; }; }; + y = x.merge {}; + in (y.merge) { a = 10; }; + + resRem7 = res6.replace (a: removeAttrs a ["a"]); + + # 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; + }; + +} diff --git a/nixpkgs/lib/tests/modules.sh b/nixpkgs/lib/tests/modules.sh new file mode 100755 index 000000000000..b83e1eb7d82d --- /dev/null +++ b/nixpkgs/lib/tests/modules.sh @@ -0,0 +1,161 @@ +#!/bin/sh +# +# This script is used to test that the module system is working as expected. +# By default it test the version of nixpkgs which is defined in the NIX_PATH. + +cd ./modules + +pass=0 +fail=0 + +evalConfig() { + local attr=$1 + shift; + local script="import ./default.nix { modules = [ $@ ];}" + nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace +} + +reportFailure() { + local attr=$1 + shift; + local script="import ./default.nix { modules = [ $@ ];}" + echo 2>&1 "$ nix-instantiate -E '$script' -A '$attr' --eval-only" + evalConfig "$attr" "$@" + fail=$((fail + 1)) +} + +checkConfigOutput() { + local outputContains=$1 + shift; + if evalConfig "$@" 2>/dev/null | grep --silent "$outputContains" ; then + pass=$((pass + 1)) + return 0; + else + echo 2>&1 "error: Expected result matching '$outputContains', while evaluating" + reportFailure "$@" + return 1 + fi +} + +checkConfigError() { + local errorContains=$1 + local err="" + shift; + if err==$(evalConfig "$@" 2>&1 >/dev/null); then + echo 2>&1 "error: Expected error code, got exit code 0, while evaluating" + reportFailure "$@" + return 1 + else + if echo "$err" | grep --silent "$errorContains" ; then + pass=$((pass + 1)) + return 0; + else + echo 2>&1 "error: Expected error matching '$errorContains', while evaluating" + reportFailure "$@" + return 1 + fi + fi +} + +# Check boolean option. +checkConfigOutput "false" config.enable ./declare-enable.nix +checkConfigError 'The option .* defined in .* does not exist.' config.enable ./define-enable.nix + +# Check integer types. +# unsigned +checkConfigOutput "42" config.value ./declare-int-unsigned-value.nix ./define-value-int-positive.nix +checkConfigError 'The option value .* in .* is not of type.*unsigned integer.*' config.value ./declare-int-unsigned-value.nix ./define-value-int-negative.nix +# positive +checkConfigError 'The option value .* in .* is not of type.*positive integer.*' config.value ./declare-int-positive-value.nix ./define-value-int-zero.nix +# between +checkConfigOutput "42" config.value ./declare-int-between-value.nix ./define-value-int-positive.nix +checkConfigError 'The option value .* in .* is not of type.*between.*-21 and 43.*inclusive.*' config.value ./declare-int-between-value.nix ./define-value-int-negative.nix + +# Check mkForce without submodules. +set -- config.enable ./declare-enable.nix ./define-enable.nix +checkConfigOutput "true" "$@" +checkConfigOutput "false" "$@" ./define-force-enable.nix +checkConfigOutput "false" "$@" ./define-enable-force.nix + +# Check mkForce with option and submodules. +checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix +checkConfigOutput 'false' config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix +set -- config.loaOfSub.foo.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo-enable.nix +checkConfigOutput 'true' "$@" +checkConfigOutput 'false' "$@" ./define-force-loaOfSub-foo-enable.nix +checkConfigOutput 'false' "$@" ./define-loaOfSub-force-foo-enable.nix +checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-force-enable.nix +checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-force.nix + +# Check overriding effect of mkForce on submodule definitions. +checkConfigError 'attribute .*bar.* .* not found' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix +checkConfigOutput 'false' config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar.nix +set -- config.loaOfSub.bar.enable ./declare-loaOfSub-any-enable.nix ./define-loaOfSub-foo.nix ./define-loaOfSub-bar-enable.nix +checkConfigOutput 'true' "$@" +checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-force-loaOfSub-foo-enable.nix +checkConfigError 'attribute .*bar.* .* not found' "$@" ./define-loaOfSub-force-foo-enable.nix +checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-force-enable.nix +checkConfigOutput 'true' "$@" ./define-loaOfSub-foo-enable-force.nix + +# Check mkIf with submodules. +checkConfigError 'attribute .*foo.* .* not found' config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix +set -- config.loaOfSub.foo.enable ./declare-enable.nix ./declare-loaOfSub-any-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-if-loaOfSub-foo-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-if-foo-enable.nix +checkConfigError 'attribute .*foo.* .* not found' "$@" ./define-loaOfSub-foo-if-enable.nix +checkConfigOutput 'false' "$@" ./define-loaOfSub-foo-enable-if.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-if-loaOfSub-foo-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-if-foo-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-if-enable.nix +checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-enable-if.nix + +# Check disabledModules with config definitions and option declarations. +set -- config.enable ./define-enable.nix ./declare-enable.nix +checkConfigOutput "true" "$@" +checkConfigOutput "false" "$@" ./disable-define-enable.nix +checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix +checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix +checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix + +# Check _module.args. +set -- config.enable ./declare-enable.nix ./define-enable-with-custom-arg.nix +checkConfigError 'while evaluating the module argument .*custom.* in .*define-enable-with-custom-arg.nix.*:' "$@" +checkConfigOutput "true" "$@" ./define-_module-args-custom.nix + +# Check that using _module.args on imports cause infinite recursions, with +# the proper error context. +set -- "$@" ./define-_module-args-custom.nix ./import-custom-arg.nix +checkConfigError 'while evaluating the module argument .*custom.* in .*import-custom-arg.nix.*:' "$@" +checkConfigError 'infinite recursion encountered' "$@" + +# Check _module.check. +set -- config.enable ./declare-enable.nix ./define-enable.nix ./define-loaOfSub-foo.nix +checkConfigError 'The option .* defined in .* does not exist.' "$@" +checkConfigOutput "true" "$@" ./define-module-check.nix + +# Check coerced value. +checkConfigOutput "\"42\"" config.value ./declare-coerced-value.nix +checkConfigOutput "\"24\"" config.value ./declare-coerced-value.nix ./define-value-string.nix +checkConfigError 'The option value .* in .* is not.*string or signed integer convertible to it' config.value ./declare-coerced-value.nix ./define-value-list.nix + +# Check coerced value with unsound coercion +checkConfigOutput "12" config.value ./declare-coerced-value-unsound.nix +checkConfigError 'The option value .* in .* is not.*8 bit signed integer.* or string convertible to it' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix +checkConfigError 'unrecognised JSON value' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix + +# Check loaOf with long list. +checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-long-list.nix + +# Check loaOf with many merges of lists. +checkConfigOutput "1 2 3 4 5 6 7 8 9 10" config.result ./loaOf-with-many-list-merges.nix + +cat <<EOF +====== module tests ====== +$pass Pass +$fail Fail +EOF + +if test $fail -ne 0; then + exit 1 +fi +exit 0 diff --git a/nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix b/nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix new file mode 100644 index 000000000000..7a017f24e77d --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-coerced-value-unsound.nix @@ -0,0 +1,10 @@ +{ lib, ... }: + +{ + options = { + value = lib.mkOption { + default = "12"; + type = lib.types.coercedTo lib.types.str lib.toInt lib.types.ints.s8; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-coerced-value.nix b/nixpkgs/lib/tests/modules/declare-coerced-value.nix new file mode 100644 index 000000000000..76b12ad53f00 --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-coerced-value.nix @@ -0,0 +1,10 @@ +{ lib, ... }: + +{ + options = { + value = lib.mkOption { + default = 42; + type = lib.types.coercedTo lib.types.int builtins.toString lib.types.str; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-enable.nix b/nixpkgs/lib/tests/modules/declare-enable.nix new file mode 100644 index 000000000000..ebee243c7568 --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-enable.nix @@ -0,0 +1,14 @@ +{ lib, ... }: + +{ + options = { + enable = lib.mkOption { + default = false; + example = true; + type = lib.types.bool; + description = '' + Some descriptive text + ''; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-int-between-value.nix b/nixpkgs/lib/tests/modules/declare-int-between-value.nix new file mode 100644 index 000000000000..8b2624cc5d65 --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-int-between-value.nix @@ -0,0 +1,9 @@ +{ lib, ... }: + +{ + options = { + value = lib.mkOption { + type = lib.types.ints.between (-21) 43; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-int-positive-value.nix b/nixpkgs/lib/tests/modules/declare-int-positive-value.nix new file mode 100644 index 000000000000..6e48c6ac8feb --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-int-positive-value.nix @@ -0,0 +1,9 @@ +{ lib, ... }: + +{ + options = { + value = lib.mkOption { + type = lib.types.ints.positive; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix b/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix new file mode 100644 index 000000000000..05d0eff01c94 --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-int-unsigned-value.nix @@ -0,0 +1,9 @@ +{ lib, ... }: + +{ + options = { + value = lib.mkOption { + type = lib.types.ints.unsigned; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/declare-loaOfSub-any-enable.nix b/nixpkgs/lib/tests/modules/declare-loaOfSub-any-enable.nix new file mode 100644 index 000000000000..71dad1c91359 --- /dev/null +++ b/nixpkgs/lib/tests/modules/declare-loaOfSub-any-enable.nix @@ -0,0 +1,29 @@ +{ lib, ... }: + +let + submod = { ... }: { + options = { + enable = lib.mkOption { + default = false; + example = true; + type = lib.types.bool; + description = '' + Some descriptive text + ''; + }; + }; + }; +in + +{ + options = { + loaOfSub = lib.mkOption { + default = {}; + example = {}; + type = lib.types.loaOf (lib.types.submodule [ submod ]); + description = '' + Some descriptive text + ''; + }; + }; +} diff --git a/nixpkgs/lib/tests/modules/default.nix b/nixpkgs/lib/tests/modules/default.nix new file mode 100644 index 000000000000..5b0947104198 --- /dev/null +++ b/nixpkgs/lib/tests/modules/default.nix @@ -0,0 +1,8 @@ +{ lib ? import ../.., modules ? [] }: + +{ + inherit (lib.evalModules { + inherit modules; + specialArgs.modulesPath = ./.; + }) config options; +} diff --git a/nixpkgs/lib/tests/modules/define-_module-args-custom.nix b/nixpkgs/lib/tests/modules/define-_module-args-custom.nix new file mode 100644 index 000000000000..e565fd215a57 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-_module-args-custom.nix @@ -0,0 +1,7 @@ +{ lib, ... }: + +{ + config = { + _module.args.custom = true; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-enable-force.nix b/nixpkgs/lib/tests/modules/define-enable-force.nix new file mode 100644 index 000000000000..f4990a328631 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-enable-force.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + enable = lib.mkForce false; +} diff --git a/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix b/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix new file mode 100644 index 000000000000..7da74671d148 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-enable-with-custom-arg.nix @@ -0,0 +1,7 @@ +{ lib, custom, ... }: + +{ + config = { + enable = custom; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-enable.nix b/nixpkgs/lib/tests/modules/define-enable.nix new file mode 100644 index 000000000000..7dc26010ae59 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-enable.nix @@ -0,0 +1,3 @@ +{ + enable = true; +} diff --git a/nixpkgs/lib/tests/modules/define-force-enable.nix b/nixpkgs/lib/tests/modules/define-force-enable.nix new file mode 100644 index 000000000000..978caa2a8c07 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-force-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +lib.mkForce { + enable = false; +} diff --git a/nixpkgs/lib/tests/modules/define-force-loaOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-force-loaOfSub-foo-enable.nix new file mode 100644 index 000000000000..bfd8e084b590 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-force-loaOfSub-foo-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +lib.mkForce { + loaOfSub.foo.enable = false; +} diff --git a/nixpkgs/lib/tests/modules/define-if-loaOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-if-loaOfSub-foo-enable.nix new file mode 100644 index 000000000000..4288d74dec00 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-if-loaOfSub-foo-enable.nix @@ -0,0 +1,5 @@ +{ config, lib, ... }: + +lib.mkIf config.enable { + loaOfSub.foo.enable = true; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-bar-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-bar-enable.nix new file mode 100644 index 000000000000..422bb0a600bf --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-bar-enable.nix @@ -0,0 +1,3 @@ +{ + loaOfSub.bar.enable = true; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-bar.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-bar.nix new file mode 100644 index 000000000000..c24315e09b6e --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-bar.nix @@ -0,0 +1,3 @@ +{ + loaOfSub.bar = {}; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-force.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-force.nix new file mode 100644 index 000000000000..c1d7b198be54 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-force.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + loaOfSub.foo.enable = lib.mkForce false; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-if.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-if.nix new file mode 100644 index 000000000000..44b2c96cd021 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable-if.nix @@ -0,0 +1,5 @@ +{ config, lib, ... }: + +{ + loaOfSub.foo.enable = lib.mkIf config.enable true; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable.nix new file mode 100644 index 000000000000..822425c71bb9 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-enable.nix @@ -0,0 +1,3 @@ +{ + loaOfSub.foo.enable = true; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo-force-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-force-enable.nix new file mode 100644 index 000000000000..dce0ef547b31 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-force-enable.nix @@ -0,0 +1,7 @@ +{ lib, ... }: + +{ + loaOfSub.foo = lib.mkForce { + enable = false; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo-if-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-if-enable.nix new file mode 100644 index 000000000000..236b2840ee51 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo-if-enable.nix @@ -0,0 +1,7 @@ +{ config, lib, ... }: + +{ + loaOfSub.foo = lib.mkIf config.enable { + enable = true; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-foo.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-foo.nix new file mode 100644 index 000000000000..e9b2e631f2ec --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-foo.nix @@ -0,0 +1,3 @@ +{ + loaOfSub.foo = {}; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-force-foo-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-force-foo-enable.nix new file mode 100644 index 000000000000..df5722274ee5 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-force-foo-enable.nix @@ -0,0 +1,7 @@ +{ lib, ... }: + +{ + loaOfSub = lib.mkForce { + foo.enable = false; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-loaOfSub-if-foo-enable.nix b/nixpkgs/lib/tests/modules/define-loaOfSub-if-foo-enable.nix new file mode 100644 index 000000000000..bd2d068d31a2 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-loaOfSub-if-foo-enable.nix @@ -0,0 +1,7 @@ +{ config, lib, ... }: + +{ + loaOfSub = lib.mkIf config.enable { + foo.enable = true; + }; +} diff --git a/nixpkgs/lib/tests/modules/define-module-check.nix b/nixpkgs/lib/tests/modules/define-module-check.nix new file mode 100644 index 000000000000..5a0707c975fa --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-module-check.nix @@ -0,0 +1,3 @@ +{ + _module.check = false; +} diff --git a/nixpkgs/lib/tests/modules/define-value-int-negative.nix b/nixpkgs/lib/tests/modules/define-value-int-negative.nix new file mode 100644 index 000000000000..a041222987ad --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-int-negative.nix @@ -0,0 +1,3 @@ +{ + value = -23; +} diff --git a/nixpkgs/lib/tests/modules/define-value-int-positive.nix b/nixpkgs/lib/tests/modules/define-value-int-positive.nix new file mode 100644 index 000000000000..5803de172636 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-int-positive.nix @@ -0,0 +1,3 @@ +{ + value = 42; +} diff --git a/nixpkgs/lib/tests/modules/define-value-int-zero.nix b/nixpkgs/lib/tests/modules/define-value-int-zero.nix new file mode 100644 index 000000000000..68bb9f415c3c --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-int-zero.nix @@ -0,0 +1,3 @@ +{ + value = 0; +} diff --git a/nixpkgs/lib/tests/modules/define-value-list.nix b/nixpkgs/lib/tests/modules/define-value-list.nix new file mode 100644 index 000000000000..4831c1cc09ba --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-list.nix @@ -0,0 +1,3 @@ +{ + value = []; +} diff --git a/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix b/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix new file mode 100644 index 000000000000..8e3abaf536a0 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-string-arbitrary.nix @@ -0,0 +1,3 @@ +{ + value = "foobar"; +} diff --git a/nixpkgs/lib/tests/modules/define-value-string-bigint.nix b/nixpkgs/lib/tests/modules/define-value-string-bigint.nix new file mode 100644 index 000000000000..f27e31985c92 --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-string-bigint.nix @@ -0,0 +1,3 @@ +{ + value = "1000"; +} diff --git a/nixpkgs/lib/tests/modules/define-value-string.nix b/nixpkgs/lib/tests/modules/define-value-string.nix new file mode 100644 index 000000000000..e7a166965a7a --- /dev/null +++ b/nixpkgs/lib/tests/modules/define-value-string.nix @@ -0,0 +1,3 @@ +{ + value = "24"; +} diff --git a/nixpkgs/lib/tests/modules/disable-declare-enable.nix b/nixpkgs/lib/tests/modules/disable-declare-enable.nix new file mode 100644 index 000000000000..a373ee7e550e --- /dev/null +++ b/nixpkgs/lib/tests/modules/disable-declare-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ ./declare-enable.nix ]; +} diff --git a/nixpkgs/lib/tests/modules/disable-define-enable.nix b/nixpkgs/lib/tests/modules/disable-define-enable.nix new file mode 100644 index 000000000000..0d84a7c3cb6c --- /dev/null +++ b/nixpkgs/lib/tests/modules/disable-define-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ ./define-enable.nix ]; +} diff --git a/nixpkgs/lib/tests/modules/disable-enable-modules.nix b/nixpkgs/lib/tests/modules/disable-enable-modules.nix new file mode 100644 index 000000000000..c325f4e07431 --- /dev/null +++ b/nixpkgs/lib/tests/modules/disable-enable-modules.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ "define-enable.nix" "declare-enable.nix" ]; +} diff --git a/nixpkgs/lib/tests/modules/import-custom-arg.nix b/nixpkgs/lib/tests/modules/import-custom-arg.nix new file mode 100644 index 000000000000..3e687b661c16 --- /dev/null +++ b/nixpkgs/lib/tests/modules/import-custom-arg.nix @@ -0,0 +1,6 @@ +{ lib, custom, ... }: + +{ + imports = [] + ++ lib.optional custom ./define-enable-force.nix; +} diff --git a/nixpkgs/lib/tests/modules/loaOf-with-long-list.nix b/nixpkgs/lib/tests/modules/loaOf-with-long-list.nix new file mode 100644 index 000000000000..f30903c47e50 --- /dev/null +++ b/nixpkgs/lib/tests/modules/loaOf-with-long-list.nix @@ -0,0 +1,19 @@ +{ config, lib, ... }: + +{ + options = { + loaOfInt = lib.mkOption { + type = lib.types.loaOf lib.types.int; + }; + + result = lib.mkOption { + type = lib.types.str; + }; + }; + + config = { + loaOfInt = [ 1 2 3 4 5 6 7 8 9 10 ]; + + result = toString (lib.attrValues config.loaOfInt); + }; +} diff --git a/nixpkgs/lib/tests/modules/loaOf-with-many-list-merges.nix b/nixpkgs/lib/tests/modules/loaOf-with-many-list-merges.nix new file mode 100644 index 000000000000..f8f8a8da82bc --- /dev/null +++ b/nixpkgs/lib/tests/modules/loaOf-with-many-list-merges.nix @@ -0,0 +1,19 @@ +{ config, lib, ... }: + +{ + options = { + loaOfInt = lib.mkOption { + type = lib.types.loaOf lib.types.int; + }; + + result = lib.mkOption { + type = lib.types.str; + }; + }; + + config = { + loaOfInt = lib.mkMerge (map lib.singleton [ 1 2 3 4 5 6 7 8 9 10 ]); + + result = toString (lib.attrValues config.loaOfInt); + }; +} diff --git a/nixpkgs/lib/tests/release.nix b/nixpkgs/lib/tests/release.nix new file mode 100644 index 000000000000..d9a8a0067253 --- /dev/null +++ b/nixpkgs/lib/tests/release.nix @@ -0,0 +1,31 @@ +{ pkgs ? import ((import ../.).cleanSource ../..) {} }: + +pkgs.stdenv.mkDerivation { + name = "nixpkgs-lib-tests"; + buildInputs = [ pkgs.nix ]; + NIX_PATH="nixpkgs=${pkgs.path}"; + + buildCommand = '' + datadir="${pkgs.nix}/share" + export TEST_ROOT=$(pwd)/test-tmp + export NIX_BUILD_HOOK= + export NIX_CONF_DIR=$TEST_ROOT/etc + export NIX_DB_DIR=$TEST_ROOT/db + export NIX_LOCALSTATE_DIR=$TEST_ROOT/var + export NIX_LOG_DIR=$TEST_ROOT/var/log/nix + export NIX_STATE_DIR=$TEST_ROOT/var/nix + export NIX_STORE_DIR=$TEST_ROOT/store + export PAGER=cat + cacheDir=$TEST_ROOT/binary-cache + nix-store --init + + cd ${pkgs.path}/lib/tests + bash ./modules.sh + + [[ "$(nix-instantiate --eval --strict misc.nix)" == "[ ]" ]] + + [[ "$(nix-instantiate --eval --strict systems.nix)" == "[ ]" ]] + + touch $out + ''; +} diff --git a/nixpkgs/lib/tests/systems.nix b/nixpkgs/lib/tests/systems.nix new file mode 100644 index 000000000000..5e1293658215 --- /dev/null +++ b/nixpkgs/lib/tests/systems.nix @@ -0,0 +1,32 @@ +# We assert that the new algorithmic way of generating these lists matches the +# way they were hard-coded before. +# +# One might think "if we exhaustively test, what's the point of procedurally +# calculating the lists anyway?". The answer is one can mindlessly update these +# tests as new platforms become supported, and then just give the diff a quick +# sanity check before committing :). +let + lib = import ../default.nix; + mseteq = x: y: { + expr = lib.sort lib.lessThan x; + expected = lib.sort lib.lessThan y; + }; +in with lib.systems.doubles; lib.runTests { + testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ windows); + + testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv7l-linux" ]; + testi686 = mseteq i686 [ "i686-linux" "i686-freebsd" "i686-netbsd" "i686-openbsd" "i686-cygwin" "i686-windows" ]; + testmips = mseteq mips [ "mipsel-linux" ]; + testx86_64 = mseteq x86_64 [ "x86_64-linux" "x86_64-darwin" "x86_64-freebsd" "x86_64-openbsd" "x86_64-netbsd" "x86_64-cygwin" "x86_64-solaris" "x86_64-windows" ]; + + testcygwin = mseteq cygwin [ "i686-cygwin" "x86_64-cygwin" ]; + testdarwin = mseteq darwin [ "x86_64-darwin" ]; + testfreebsd = mseteq freebsd [ "i686-freebsd" "x86_64-freebsd" ]; + testgnu = mseteq gnu (linux /* ++ kfreebsd ++ ... */); + testillumos = mseteq illumos [ "x86_64-solaris" ]; + testlinux = mseteq linux [ "i686-linux" "x86_64-linux" "armv5tel-linux" "armv6l-linux" "armv7l-linux" "aarch64-linux" "mipsel-linux" ]; + testnetbsd = mseteq netbsd [ "i686-netbsd" "x86_64-netbsd" ]; + testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ]; + testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "i686-windows" "x86_64-windows" ]; + testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin); +} diff --git a/nixpkgs/lib/trivial.nix b/nixpkgs/lib/trivial.nix new file mode 100644 index 000000000000..17489311236d --- /dev/null +++ b/nixpkgs/lib/trivial.nix @@ -0,0 +1,297 @@ +{ lib }: + +rec { + + ## Simple (higher order) functions + + /* The identity function + For when you need a function that does “nothing”. + + Type: id :: a -> a + */ + id = + # The value to return + x: x; + + /* The constant function + + Ignores the second argument. If called with only one argument, + constructs a function that always returns a static value. + + Type: const :: a -> b -> a + Example: + let f = const 5; in f 10 + => 5 + */ + const = + # Value to return + x: + # Value to ignore + y: x; + + + ## Named versions corresponding to some builtin operators. + + /* Concatenate two lists + + Type: concat :: [a] -> [a] -> [a] + + Example: + concat [ 1 2 ] [ 3 4 ] + => [ 1 2 3 4 ] + */ + concat = x: y: x ++ y; + + /* boolean “or” */ + or = x: y: x || y; + + /* boolean “and” */ + and = x: y: x && y; + + /* bitwise “and” */ + bitAnd = builtins.bitAnd + or (import ./zip-int-bits.nix + (a: b: if a==1 && b==1 then 1 else 0)); + + /* bitwise “or” */ + bitOr = builtins.bitOr + or (import ./zip-int-bits.nix + (a: b: if a==1 || b==1 then 1 else 0)); + + /* bitwise “xor” */ + bitXor = builtins.bitXor + or (import ./zip-int-bits.nix + (a: b: if a!=b then 1 else 0)); + + /* bitwise “not” */ + bitNot = builtins.sub (-1); + + /* Convert a boolean to a string. + + This function uses the strings "true" and "false" to represent + boolean values. Calling `toString` on a bool instead returns "1" + and "" (sic!). + + Type: boolToString :: bool -> string + */ + boolToString = b: if b then "true" else "false"; + + /* Merge two attribute sets shallowly, right side trumps left + + mergeAttrs :: attrs -> attrs -> attrs + + Example: + mergeAttrs { a = 1; b = 2; } { b = 3; c = 4; } + => { a = 1; b = 3; c = 4; } + */ + mergeAttrs = + # Left attribute set + x: + # Right attribute set (higher precedence for equal keys) + y: x // y; + + /* Flip the order of the arguments of a binary function. + + Type: flip :: (a -> b -> c) -> (b -> a -> c) + + Example: + flip concat [1] [2] + => [ 2 1 ] + */ + flip = f: a: b: f b a; + + /* Apply function if the supplied argument is non-null. + + Example: + mapNullable (x: x+1) null + => null + mapNullable (x: x+1) 22 + => 23 + */ + mapNullable = + # Function to call + f: + # Argument to check for null before passing it to `f` + a: if isNull a then a else f a; + + # Pull in some builtins not included elsewhere. + inherit (builtins) + pathExists readFile isBool + isInt isFloat add sub lessThan + seq deepSeq genericClosure; + + + ## nixpks version strings + + /* Returns the current full nixpkgs version number. */ + version = release + versionSuffix; + + /* Returns the current nixpkgs release number as string. */ + release = lib.strings.fileContents ../.version; + + /* Returns the current nixpkgs release code name. + + On each release the first letter is bumped and a new animal is chosen + starting with that new letter. + */ + codeName = "Koi"; + + /* Returns the current nixpkgs version suffix as string. */ + versionSuffix = + let suffixFile = ../.version-suffix; + in if pathExists suffixFile + then lib.strings.fileContents suffixFile + else "pre-git"; + + /* Attempts to return the the current revision of nixpkgs and + returns the supplied default value otherwise. + + Type: revisionWithDefault :: string -> string + */ + revisionWithDefault = + # Default value to return if revision can not be determined + default: + let + revisionFile = "${toString ./..}/.git-revision"; + gitRepo = "${toString ./..}/.git"; + in if lib.pathIsDirectory gitRepo + then lib.commitIdFromGitRepo gitRepo + else if lib.pathExists revisionFile then lib.fileContents revisionFile + else default; + + nixpkgsVersion = builtins.trace "`lib.nixpkgsVersion` is deprecated, use `lib.version` instead!" version; + + /* Determine whether the function is being called from inside a Nix + shell. + + Type: inNixShell :: bool + */ + inNixShell = builtins.getEnv "IN_NIX_SHELL" != ""; + + + ## Integer operations + + /* Return minimum of two numbers. */ + min = x: y: if x < y then x else y; + + /* Return maximum of two numbers. */ + max = x: y: if x > y then x else y; + + /* Integer modulus + + Example: + mod 11 10 + => 1 + mod 1 10 + => 1 + */ + mod = base: int: base - (int * (builtins.div base int)); + + + ## Comparisons + + /* C-style comparisons + + a < b, compare a b => -1 + a == b, compare a b => 0 + a > b, compare a b => 1 + */ + compare = a: b: + if a < b + then -1 + else if a > b + then 1 + else 0; + + /* Split type into two subtypes by predicate `p`, take all elements + of the first subtype to be less than all the elements of the + second subtype, compare elements of a single subtype with `yes` + and `no` respectively. + + Type: (a -> bool) -> (a -> a -> int) -> (a -> a -> int) -> (a -> a -> int) + + Example: + let cmp = splitByAndCompare (hasPrefix "foo") compare compare; in + + cmp "a" "z" => -1 + cmp "fooa" "fooz" => -1 + + cmp "f" "a" => 1 + cmp "fooa" "a" => -1 + # while + compare "fooa" "a" => 1 + */ + splitByAndCompare = + # Predicate + p: + # Comparison function if predicate holds for both values + yes: + # Comparison function if predicate holds for neither value + no: + # First value to compare + a: + # Second value to compare + b: + if p a + then if p b then yes a b else -1 + else if p b then 1 else no a b; + + + /* Reads a JSON file. + + Type :: path -> any + */ + importJSON = path: + builtins.fromJSON (builtins.readFile path); + + + ## Warnings + + # See https://github.com/NixOS/nix/issues/749. Eventually we'd like these + # to expand to Nix builtins that carry metadata so that Nix can filter out + # the INFO messages without parsing the message string. + # + # Usage: + # { + # foo = lib.warn "foo is deprecated" oldFoo; + # } + # + # TODO: figure out a clever way to integrate location information from + # something like __unsafeGetAttrPos. + + warn = msg: builtins.trace "WARNING: ${msg}"; + info = msg: builtins.trace "INFO: ${msg}"; + + + ## Function annotations + + /* Add metadata about expected function arguments to a function. + The metadata should match the format given by + builtins.functionArgs, i.e. a set from expected argument to a bool + representing whether that argument has a default or not. + setFunctionArgs : (a → b) → Map String Bool → (a → b) + + This function is necessary because you can't dynamically create a + function of the { a, b ? foo, ... }: format, but some facilities + like callPackage expect to be able to query expected arguments. + */ + setFunctionArgs = f: args: + { # TODO: Should we add call-time "type" checking like built in? + __functor = self: f; + __functionArgs = args; + }; + + /* Extract the expected function arguments from a function. + This works both with nix-native { a, b ? foo, ... }: style + functions and functions with args set with 'setFunctionArgs'. It + has the same return type and semantics as builtins.functionArgs. + setFunctionArgs : (a → b) → Map String Bool. + */ + functionArgs = f: f.__functionArgs or (builtins.functionArgs f); + + /* Check whether something is a function or something + annotated with function args. + */ + isFunction = f: builtins.isFunction f || + (f ? __functor && isFunction (f.__functor f)); +} diff --git a/nixpkgs/lib/types.nix b/nixpkgs/lib/types.nix new file mode 100644 index 000000000000..d1ece2402ad7 --- /dev/null +++ b/nixpkgs/lib/types.nix @@ -0,0 +1,484 @@ +# Definitions related to run-time type checking. Used in particular +# to type-check NixOS configurations. +{ lib }: +with lib.lists; +with lib.attrsets; +with lib.options; +with lib.trivial; +with lib.strings; +let + + inherit (lib.modules) mergeDefinitions; + outer_types = +rec { + isType = type: x: (x._type or "") == type; + + setType = typeName: value: value // { + _type = typeName; + }; + + + # Default type merging function + # takes two type functors and return the merged type + defaultTypeMerge = f: f': + let wrapped = f.wrapped.typeMerge f'.wrapped.functor; + payload = f.binOp f.payload f'.payload; + in + # cannot merge different types + if f.name != f'.name + then null + # simple types + else if (f.wrapped == null && f'.wrapped == null) + && (f.payload == null && f'.payload == null) + then f.type + # composed types + else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null) + then f.type wrapped + # value types + else if (f.payload != null && f'.payload != null) && (payload != null) + then f.type payload + else null; + + # Default type functor + defaultFunctor = name: { + inherit name; + type = types."${name}" or null; + wrapped = null; + payload = null; + binOp = a: b: null; + }; + + isOptionType = isType "option-type"; + mkOptionType = + { # Human-readable representation of the type, should be equivalent to + # the type function name. + name + , # Description of the type, defined recursively by embedding the wrapped type if any. + description ? null + , # Function applied to each definition that should return true if + # its type-correct, false otherwise. + check ? (x: true) + , # Merge a list of definitions together into a single value. + # This function is called with two arguments: the location of + # the option in the configuration as a list of strings + # (e.g. ["boot" "loader "grub" "enable"]), and a list of + # definition values and locations (e.g. [ { file = "/foo.nix"; + # value = 1; } { file = "/bar.nix"; value = 2 } ]). + merge ? mergeDefaultOption + , # Return a flat list of sub-options. Used to generate + # documentation. + getSubOptions ? prefix: {} + , # List of modules if any, or null if none. + getSubModules ? null + , # Function for building the same option type with a different list of + # modules. + substSubModules ? m: null + , # Function that merge type declarations. + # internal, takes a functor as argument and returns the merged type. + # returning null means the type is not mergeable + typeMerge ? defaultTypeMerge functor + , # The type functor. + # internal, representation of the type as an attribute set. + # name: name of the type + # type: type function. + # wrapped: the type wrapped in case of compound types. + # payload: values of the type, two payloads of the same type must be + # combinable with the binOp binary operation. + # binOp: binary operation that merge two payloads of the same type. + functor ? defaultFunctor name + }: + { _type = "option-type"; + inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor; + description = if description == null then name else description; + }; + + + # When adding new types don't forget to document them in + # nixos/doc/manual/development/option-types.xml! + types = rec { + unspecified = mkOptionType { + name = "unspecified"; + }; + + bool = mkOptionType { + name = "bool"; + description = "boolean"; + check = isBool; + merge = mergeEqualOption; + }; + + int = mkOptionType rec { + name = "int"; + description = "signed integer"; + check = isInt; + merge = mergeOneOption; + }; + + # Specialized subdomains of int + ints = + let + betweenDesc = lowest: highest: + "${toString lowest} and ${toString highest} (both inclusive)"; + between = lowest: highest: + assert lib.assertMsg (lowest <= highest) + "ints.between: lowest must be smaller than highest"; + addCheck int (x: x >= lowest && x <= highest) // { + name = "intBetween"; + description = "integer between ${betweenDesc lowest highest}"; + }; + ign = lowest: highest: name: docStart: + between lowest highest // { + inherit name; + description = docStart + "; between ${betweenDesc lowest highest}"; + }; + unsign = bit: range: ign 0 (range - 1) + "unsignedInt${toString bit}" "${toString bit} bit unsigned integer"; + sign = bit: range: ign (0 - (range / 2)) (range / 2 - 1) + "signedInt${toString bit}" "${toString bit} bit signed integer"; + + in rec { + /* An int with a fixed range. + * + * Example: + * (ints.between 0 100).check (-1) + * => false + * (ints.between 0 100).check (101) + * => false + * (ints.between 0 0).check 0 + * => true + */ + inherit between; + + unsigned = addCheck types.int (x: x >= 0) // { + name = "unsignedInt"; + description = "unsigned integer, meaning >=0"; + }; + positive = addCheck types.int (x: x > 0) // { + name = "positiveInt"; + description = "positive integer, meaning >0"; + }; + u8 = unsign 8 256; + u16 = unsign 16 65536; + # the biggest int a 64-bit Nix accepts is 2^63 - 1 (9223372036854775808), for a 32-bit Nix it is 2^31 - 1 (2147483647) + # the smallest int a 64-bit Nix accepts is -2^63 (-9223372036854775807), for a 32-bit Nix it is -2^31 (-2147483648) + # u32 = unsign 32 4294967296; + # u64 = unsign 64 18446744073709551616; + + s8 = sign 8 256; + s16 = sign 16 65536; + # s32 = sign 32 4294967296; + }; + + # Alias of u16 for a port number + port = ints.u16; + + float = mkOptionType rec { + name = "float"; + description = "floating point number"; + check = isFloat; + merge = mergeOneOption; + }; + + str = mkOptionType { + name = "str"; + description = "string"; + check = isString; + merge = mergeOneOption; + }; + + strMatching = pattern: mkOptionType { + name = "strMatching ${escapeNixString pattern}"; + description = "string matching the pattern ${pattern}"; + check = x: str.check x && builtins.match pattern x != null; + inherit (str) merge; + }; + + # Merge multiple definitions by concatenating them (with the given + # separator between the values). + separatedString = sep: mkOptionType rec { + name = "separatedString"; + description = if sep == "" + then "Concatenated string" # for types.string. + else "strings concatenated with ${builtins.toJSON sep}" + ; + check = isString; + merge = loc: defs: concatStringsSep sep (getValues defs); + functor = (defaultFunctor name) // { + payload = sep; + binOp = sepLhs: sepRhs: + if sepLhs == sepRhs then sepLhs + else null; + }; + }; + + lines = separatedString "\n"; + commas = separatedString ","; + envVar = separatedString ":"; + + # Deprecated; should not be used because it quietly concatenates + # strings, which is usually not what you want. + string = separatedString ""; + + attrs = mkOptionType { + name = "attrs"; + description = "attribute set"; + check = isAttrs; + merge = loc: foldl' (res: def: mergeAttrs res def.value) {}; + }; + + # derivation is a reserved keyword. + package = mkOptionType { + name = "package"; + check = x: isDerivation x || isStorePath x; + merge = loc: defs: + let res = mergeOneOption loc defs; + in if isDerivation res then res else toDerivation res; + }; + + shellPackage = package // { + check = x: (package.check x) && (hasAttr "shellPath" x); + }; + + path = mkOptionType { + name = "path"; + # Hacky: there is no ‘isPath’ primop. + check = x: builtins.substring 0 1 (toString x) == "/"; + merge = mergeOneOption; + }; + + # drop this in the future: + list = builtins.trace "`types.list` is deprecated; use `types.listOf` instead" types.listOf; + + listOf = elemType: mkOptionType rec { + name = "listOf"; + description = "list of ${elemType.description}s"; + check = isList; + merge = loc: defs: + map (x: x.value) (filter (x: x ? value) (concatLists (imap1 (n: def: + if isList def.value then + imap1 (m: def': + (mergeDefinitions + (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) + elemType + [{ inherit (def) file; value = def'; }] + ).optionalValue + ) def.value + else + throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs))); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]); + getSubModules = elemType.getSubModules; + substSubModules = m: listOf (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + nonEmptyListOf = elemType: + let list = addCheck (types.listOf elemType) (l: l != []); + in list // { description = "non-empty " + list.description; }; + + attrsOf = elemType: mkOptionType rec { + name = "attrsOf"; + description = "attribute set of ${elemType.description}s"; + check = isAttrs; + merge = loc: defs: + mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: + (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue + ) + # Push down position info. + (map (def: listToAttrs (mapAttrsToList (n: def': + { name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs))); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]); + getSubModules = elemType.getSubModules; + substSubModules = m: attrsOf (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + # List or attribute set of ... + loaOf = elemType: + let + convertAllLists = defs: + let + padWidth = stringLength (toString (length defs)); + unnamedPrefix = i: "unnamed-" + fixedWidthNumber padWidth i + "."; + in + imap1 (i: convertIfList (unnamedPrefix i)) defs; + + convertIfList = unnamedPrefix: def: + if isList def.value then + let + padWidth = stringLength (toString (length def.value)); + unnamed = i: unnamedPrefix + fixedWidthNumber padWidth i; + in + { inherit (def) file; + value = listToAttrs ( + imap1 (elemIdx: elem: + { name = elem.name or (unnamed elemIdx); + value = elem; + }) def.value); + } + else + def; + attrOnly = attrsOf elemType; + in mkOptionType rec { + name = "loaOf"; + description = "list or attribute set of ${elemType.description}s"; + check = x: isList x || isAttrs x; + merge = loc: defs: attrOnly.merge loc (convertAllLists defs); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]); + getSubModules = elemType.getSubModules; + substSubModules = m: loaOf (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + # Value of given type but with no merging (i.e. `uniq list`s are not concatenated). + uniq = elemType: mkOptionType rec { + name = "uniq"; + inherit (elemType) description check; + merge = mergeOneOption; + getSubOptions = elemType.getSubOptions; + getSubModules = elemType.getSubModules; + substSubModules = m: uniq (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + # Null or value of ... + nullOr = elemType: mkOptionType rec { + name = "nullOr"; + description = "null or ${elemType.description}"; + check = x: x == null || elemType.check x; + merge = loc: defs: + let nrNulls = count (def: def.value == null) defs; in + if nrNulls == length defs then null + else if nrNulls != 0 then + throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}." + else elemType.merge loc defs; + getSubOptions = elemType.getSubOptions; + getSubModules = elemType.getSubModules; + substSubModules = m: nullOr (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + # A submodule (like typed attribute set). See NixOS manual. + submodule = opts: + let + opts' = toList opts; + inherit (lib.modules) evalModules; + in + mkOptionType rec { + name = "submodule"; + check = x: isAttrs x || isFunction x; + merge = loc: defs: + let + coerce = def: if isFunction def then def else { config = def; }; + modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs; + in (evalModules { + inherit modules; + args.name = last loc; + prefix = loc; + }).config; + getSubOptions = prefix: (evalModules + { modules = opts'; inherit prefix; + # This is a work-around due to the fact that some sub-modules, + # such as the one included in an attribute set, expects a "args" + # attribute to be given to the sub-module. As the option + # evaluation does not have any specific attribute name, we + # provide a default one for the documentation. + # + # This is mandatory as some option declaration might use the + # "name" attribute given as argument of the submodule and use it + # as the default of option declarations. + # + # Using lookalike unicode single angle quotation marks because + # of the docbook transformation the options receive. In all uses + # > and < wouldn't be encoded correctly so the encoded values + # would be used, and use of `<` and `>` would break the XML document. + # It shouldn't cause an issue since this is cosmetic for the manual. + args.name = "‹name›"; + }).options; + getSubModules = opts'; + substSubModules = m: submodule m; + functor = (defaultFunctor name) // { + # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate + # each submodule with its location. + payload = []; + binOp = lhs: rhs: []; + }; + }; + + # A value from a set of allowed ones. + enum = values: + let + show = v: + if builtins.isString v then ''"${v}"'' + else if builtins.isInt v then builtins.toString v + else ''<${builtins.typeOf v}>''; + in + mkOptionType rec { + name = "enum"; + description = "one of ${concatMapStringsSep ", " show values}"; + check = flip elem values; + merge = mergeOneOption; + functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); }; + }; + + # Either value of type `t1` or `t2`. + either = t1: t2: mkOptionType rec { + name = "either"; + description = "${t1.description} or ${t2.description}"; + check = x: t1.check x || t2.check x; + merge = loc: defs: + let + defList = map (d: d.value) defs; + in + if all (x: t1.check x) defList + then t1.merge loc defs + else if all (x: t2.check x) defList + then t2.merge loc defs + else mergeOneOption loc defs; + typeMerge = f': + let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor; + mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor; + in + if (name == f'.name) && (mt1 != null) && (mt2 != null) + then functor.type mt1 mt2 + else null; + functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; }; + }; + + # Either value of type `finalType` or `coercedType`, the latter is + # converted to `finalType` using `coerceFunc`. + coercedTo = coercedType: coerceFunc: finalType: + assert lib.assertMsg (coercedType.getSubModules == null) + "coercedTo: coercedType must not have submodules (it’s a ${ + coercedType.description})"; + mkOptionType rec { + name = "coercedTo"; + description = "${finalType.description} or ${coercedType.description} convertible to it"; + check = x: finalType.check x || (coercedType.check x && finalType.check (coerceFunc x)); + merge = loc: defs: + let + coerceVal = val: + if finalType.check val then val + else coerceFunc val; + in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); + getSubOptions = finalType.getSubOptions; + getSubModules = finalType.getSubModules; + substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); + typeMerge = t1: t2: null; + functor = (defaultFunctor name) // { wrapped = finalType; }; + }; + + # Obsolete alternative to configOf. It takes its option + # declarations from the ‘options’ attribute of containing option + # declaration. + optionSet = mkOptionType { + name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet"; + description = "option set"; + }; + + # Augment the given type with an additional type check function. + addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; }; + + }; +}; + +in outer_types // outer_types.types diff --git a/nixpkgs/lib/versions.nix b/nixpkgs/lib/versions.nix new file mode 100644 index 000000000000..8f7f98ff5e1e --- /dev/null +++ b/nixpkgs/lib/versions.nix @@ -0,0 +1,47 @@ +/* Version string functions. */ +{ lib }: + +let + + splitVersion = builtins.splitVersion or (lib.splitString "."); + +in + +rec { + + /* Get the major version string from a string. + + Example: + major "1.2.3" + => "1" + */ + major = v: builtins.elemAt (splitVersion v) 0; + + /* Get the minor version string from a string. + + Example: + minor "1.2.3" + => "2" + */ + minor = v: builtins.elemAt (splitVersion v) 1; + + /* Get the patch version string from a string. + + Example: + patch "1.2.3" + => "3" + */ + patch = v: builtins.elemAt (splitVersion v) 2; + + /* Get string of the first two parts (major and minor) + of a version string. + + Example: + majorMinor "1.2.3" + => "1.2" + */ + majorMinor = v: + builtins.concatStringsSep "." + (lib.take 2 (splitVersion v)); + +} diff --git a/nixpkgs/lib/zip-int-bits.nix b/nixpkgs/lib/zip-int-bits.nix new file mode 100644 index 000000000000..edbcdfe1e682 --- /dev/null +++ b/nixpkgs/lib/zip-int-bits.nix @@ -0,0 +1,39 @@ +/* Helper function to implement a fallback for the bit operators + `bitAnd`, `bitOr` and `bitXOr` on older nix version. + See ./trivial.nix +*/ +f: x: y: + let + # (intToBits 6) -> [ 0 1 1 ] + intToBits = x: + if x == 0 || x == -1 then + [] + else + let + headbit = if (x / 2) * 2 != x then 1 else 0; # x & 1 + tailbits = if x < 0 then ((x + 1) / 2) - 1 else x / 2; # x >> 1 + in + [headbit] ++ (intToBits tailbits); + + # (bitsToInt [ 0 1 1 ] 0) -> 6 + # (bitsToInt [ 0 1 0 ] 1) -> -6 + bitsToInt = l: signum: + if l == [] then + (if signum == 0 then 0 else -1) + else + (builtins.head l) + (2 * (bitsToInt (builtins.tail l) signum)); + + xsignum = if x < 0 then 1 else 0; + ysignum = if y < 0 then 1 else 0; + zipListsWith' = fst: snd: + if fst==[] && snd==[] then + [] + else if fst==[] then + [(f xsignum (builtins.head snd))] ++ (zipListsWith' [] (builtins.tail snd)) + else if snd==[] then + [(f (builtins.head fst) ysignum )] ++ (zipListsWith' (builtins.tail fst) [] ) + else + [(f (builtins.head fst) (builtins.head snd))] ++ (zipListsWith' (builtins.tail fst) (builtins.tail snd)); + in + assert (builtins.isInt x) && (builtins.isInt y); + bitsToInt (zipListsWith' (intToBits x) (intToBits y)) (f xsignum ysignum) |