diff --git a/lib/default.nix b/lib/default.nix index cbe0a0ba21b59..79cb4f7492fe3 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -31,6 +31,9 @@ let options = callLibs ./options.nix; types = callLibs ./types.nix; + # type checking plain nix values + types-simple = callLibs ./types-simple.nix; + # constants licenses = callLibs ./licenses.nix; systems = callLibs ./systems; diff --git a/lib/generators.nix b/lib/generators.nix index 73017f2c67968..28c8a96b7c83a 100644 --- a/lib/generators.nix +++ b/lib/generators.nix @@ -126,7 +126,8 @@ rec { 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 "<δ>" + else if v ? type && v.type == "derivation" then + "<δ:${v.name}>" else "{ " + libStr.concatStringsSep " " (libAttr.mapAttrsToList (name: value: diff --git a/lib/licenses.nix b/lib/licenses.nix index ba575c27052d4..3cb0397f55d16 100644 --- a/lib/licenses.nix +++ b/lib/licenses.nix @@ -7,7 +7,7 @@ let in -lib.mapAttrs (n: v: v // { shortName = n; }) rec { +lib.mapAttrs (n: v: v // { shortName = n; free = v.free or true; }) 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. diff --git a/lib/tests/README.md b/lib/tests/README.md new file mode 100644 index 0000000000000..538e679b1e960 --- /dev/null +++ b/lib/tests/README.md @@ -0,0 +1,11 @@ +# lib tests + +Here you can find tests for the library. + +* Tests for the module system are defined in `./modules.sh`. +* Tests for most other library functions are defined in `./misc.sh`. + +You can build everything by running `nix build -f ./release.nix`, though calling +test suites directly is considerably faster. Consult the files listed above to +find out how to do that. + diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index e10aea48e48e7..275d9b2e56f8e 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -3,7 +3,11 @@ # if the resulting list is empty, all tests passed with import ../default.nix; -runTests { +# types-simple tests +import ./types-simple.nix + +# misc tests +++ (runTests { # TRIVIAL @@ -308,7 +312,7 @@ runTests { functionArgs = "<λ:{(arg),foo}>"; list = "[ 3 4 ${function} [ false ] ]"; attrs = "{ \"foo\" = null; \"foo bar\" = \"baz\"; }"; - drv = "<δ>"; + drv = "<δ:test>"; }; }; @@ -360,4 +364,4 @@ runTests { expected = true; }; -} +}) diff --git a/lib/tests/types-simple.nix b/lib/tests/types-simple.nix new file mode 100644 index 0000000000000..390331b7654b6 --- /dev/null +++ b/lib/tests/types-simple.nix @@ -0,0 +1,270 @@ +# tests for /lib/types-simple +let lib = import ../default.nix; +in with lib.types-simple; + +let + + # Generate a type checker error. + # expectedType is the type expected at that position + # val is the value that was badly typed + err = expectedType: val: { + inherit val; + should = expectedType.description; + }; + # a successful type check + ok = {}; + + # Test the checkType function results. + # type is the type to check for + # val is the value that should be of type type + # result is the expected checkType result + test = type: val: result: { + expr = checkType type val; + expected = result; + }; + + testDef = type: result: { + expr = defaults type; + expected = result; + }; + + # TODO test the return type of checkType to be + # nested attrs (product { should = string; val = any; }) + +in lib.runTests ({ + + + # -- Scalars -- + + testVoid = test void 23 (err void 23); + + testAnyInt = test any 42 ok; + testAnyString = test any "foo" ok; + testAnyList = test any [ 3 "45" { dont = "do this"; } "ever" ] ok; + + testUnitOk = test unit {} ok; + testUnitFoo = test unit "foo" (err unit "foo"); + + testBoolOk = test bool true ok; + testBoolFoo = test bool 23 (err bool 23); + + testStringOk = test string "foo" ok; + testStringFoo = test string false (err string false); + + testPathOk = test path /. ok; + testPathFoo = test path "/nope" (err path "/nope"); + + testIntOk = test int 42 ok; + testIntFoo = test int {} (err int {}); + + testFloatOk = test float 3.14 ok; + testFloatOkStrange = test float 23. ok; + testFloatNotInt = test float 23 (err float 23); + testFloatFoo = test float [ "nope" ] (err float [ "nope" ]); + + + # -- Recursives -- + + testListEmpty = test (list void) [] ok; + testListIntOk = test (list int) [ 1 2 3 ] ok; + testListPosFoo = test (list int) [ 1 "ahh" 3 true ] { + "1" = (err int "ahh"); + "3" = (err int true); + }; + testListOfListUnitOk = test (list (list unit)) [ [] [{}] [{} {} {}] ] ok; + testListOfListUnitFoo = test (list (list unit)) [ {} [ {} "ups" ] [[]] ] { + "0" = err (list unit) {}; + "1"."1" = err unit "ups"; + "2"."0" = err unit []; + }; + + testAttrsEmpty = test (attrs void) {} ok; + testAttrsIntOk = test (attrs int) { foo = 1; bar = 2; } ok; + testAttrsIntListFoo = test (attrs int) [] (err (attrs int) []); + testAttrsIntFoo = test (attrs int) { foo.bar = 1; baz = 2; quux = true; } { + foo = err int { bar = 1; }; + quux = err int true; + }; + testAttrsOfAttrsOk = test (attrs (attrs unit)) { foo.bar = {}; baz.quux = {}; } ok; + testAttrsOfAttrsEmptyOk = test (attrs (attrs unit)) {} ok; + testAttrsOfAttrsFoo = test (attrs (attrs unit)) { a = []; b.c.d.e = false; } { + a = err (attrs unit) []; + b.c = err unit { d.e = false; }; + }; + + testListOfAttrsOk1 = test (list (attrs unit)) [] ok; + testListOfAttrsOk2 = test (list (attrs unit)) [ { a = {}; } { b = {}; } ] ok; + testListOfAttrsFoo = test (list (attrs unit)) + [ 42 { a = {}; b.c.d = "x"; } { x = []; } {} ] + { + "0" = err (attrs unit) 42; + "1".b = err unit { c.d = "x"; }; + "2".x = err unit []; + }; + + + # -- Products -- + + testProductOk = test (product { name = string; age = int; }) + { name = "hans"; age = 42; } ok; + testProductWrongTypes = test (product { name = string; age = int; }) + { name = true; age = 23.5; } + { + name = err string true; + age = err int 23.5; + }; + testProductWrongField = test (product { foo = bool; }) + { bar = "foo"; } + (err (product { foo = bool; }) { bar = "foo"; }); + testProductTooManyFields = test (product { a = int; b = int; }) + { a = 1; b = 2; c = "hello"; } + (err (product { a = int; b = int; }) { a = 1; b = 2; c = "hello"; }); + testProductEmptyOk = test (product {}) {} ok; + testProductEmptyFoo = test (product {}) { name = "hans"; } + (err (product {}) { name = "hans"; }); + + testProductOptOk = test + (productOpt { req = { a = unit; }; opt = { b = unit; }; }) + { a = {}; b = {}; } ok; + testProductOptNoOptOk = test + (productOpt { req = { a = unit; }; opt = { b = unit; }; }) + { a = {}; } ok; + testProductOnlyOptNoReq = test + (productOpt { req = { a = unit; }; opt = { b = unit; }; }) + { b = {}; } + (err (productOpt { req = { a = unit; }; opt = { b = unit; }; }) { b = {}; }) ; + testProductOnlyOptOk = test + (productOpt { req = {}; opt = { x = unit; y = unit; }; }) + { y = {}; } ok; + testProductInProductOpt = test + (productOpt { req = {}; opt = { p = product { x = int; y = bool; }; }; }) + { p = { x = 23; }; } # missing the required p.y + { p = (err (product { x = int; y = bool; }) { x = 23; }); }; + + testProductOptOpenOk = test + (productOpt { req = { a = unit; }; opt = {}; open = true; }) + { a = {}; rest1 = 12; rest2 = "foo"; } ok; + testProductOptOpenIgnoreRest = test + (productOpt { req = { a = unit; }; opt = { b = int; }; open = true; }) + { a = 23; b = false; rest = "foo"; x = {}; y = 23; } + { a = err unit 23; b = err int false; }; + + + # -- Sums -- + + testSumLeftOk = test (sum { left = string; right = unit; }) + { left = "errör!"; } ok; + testSumRightOk = test (sum { left = string; right = unit; }) + { right = {}; } ok; + testSumWrongField = test (sum { a = int; b = bool; }) + { c = "ups"; } + (err (sum { a = int; b = bool; }) { c = "ups"; }); + testSumIsNotUnion = test (sum { a = string; b = int; }) + 42 + (err (sum { a = string; b = int; }) 42); + testSumTooManyFields = test (sum { a = int; b = unit; }) + { a = 21; b = {}; } + (err (sum { a = int; b = unit; }) { a = 21; b = {}; }); + + + # -- Unions -- + + testUnionOk1 = test (union [ int string (list unit) ]) 23 ok; + testUnionOk2 = test (union [ int string (list unit) ]) "foo" ok; + testUnionOk3 = test (union [ int string (list unit) ]) [{}{}] ok; + testUnionWrongType = test (union [ int string ]) true + (err (union [ int string ]) true); + testUnionOne = test (union [ int ]) 23 ok; + testUnionSimilar = test (union [ (list string) (attrs string) ]) + { foo = "string"; } ok; + +} // + + + # -- Restrictions -- + +(let + even = restrict { + type = int; + check = v: lib.mod v 2 == 0; + description = "even integer"; + }; + + intBetween = min: max: restrict { + type = int; + check = v: v >= min && v <= max; + description = "int between ${toString min} ${toString max}"; + }; + + thirdElementIsListOf23 = restrict { + type = list (list int); + check = v: builtins.length v >= 3 && builtins.elemAt v 2 == [23]; + description = "third element is [23]"; + }; + + enum = t: xs: restrict { + type = t; + check = v: lib.any (x: x == v) xs; + description = "one of values [ " + + lib.concatMapStringsSep ", " (lib.generators.toPretty {}) xs + + " ]"; + }; + + transitive = restrict { + description = "neither a nor b"; + check = v: v != "b"; + type = restrict { + description = "not a"; + check = v: v != "a"; + type = string; + }; + }; + +in { + testRestrictEvenOk = test (list even) + [ 2 4 128 42 ] ok; + testRestrictEvenFoo = test (list even) + [ 42 23 ] + { "1" = err even 23; }; + + testRestrictTypeCheckFirst = + let t = restrict { + type = void; + # will crash if "23" is checked here + # before the check for its type + check = v: (v + 19) == 42; + description = "is worthy"; + }; + # we actually want it to always give the type description + # of the restricted type when the general type check fails + # e.g. 23 should return "even integer" instead of "integer" + # when checking for the int restricted to even integers + in test t "23" (err t "23"); + + testDeepRestriction = test thirdElementIsListOf23 + [ [1] ["foo"] [23] ] + { "1"."0" = err int "foo"; }; + testDeepRestrictionFoo = test thirdElementIsListOf23 + [ [] [] [24] [] [] ] + (err thirdElementIsListOf23 [ [] [] [24] [] [] ]); + + testRestrictTransitiveOk = test transitive "c" ok; + testRestrictTransitiveA = test transitive "a" + (err transitive "a"); + testRestrictTransitiveB = test transitive "b" + (err transitive "b"); + + testRestrictIntBetweenOk = test (list (intBetween (-2) 3)) + [ (-2) (-1) 0 1 2 3 ] ok; + testRestrictIntBetweenFoo = test (list (intBetween 0 0)) + [ (-23) 42 ] + { "0" = err (intBetween 0 0) (-23); + "1" = err (intBetween 0 0) 42; }; + + testRestrictEnumOk = test (list (enum string [ "a" "b" "c" ])) + [ "b" "c" "a" ] ok; + testRestrictEnumFoo = test (list (enum string [ "a" "b" "c" ])) + [ "b" "d" "a" ] + { "1" = err (enum string [ "a" "b" "c" ]) "d"; }; + +})) diff --git a/lib/types-simple.nix b/lib/types-simple.nix new file mode 100644 index 0000000000000..a34627671ddfc --- /dev/null +++ b/lib/types-simple.nix @@ -0,0 +1,452 @@ +# A simple type system to check plain nix values +# and get detailed error messages on type mismatch. +# +# Contains support for +# scalars (simple values) +# recursive types (lists of t and attrs of t) +# products (attribute sets with named fields) +# sums (tagged unions where you can match on the different cases) +# unions (untagged unions of the specified types) +# We can’t type functions (lambda expressions). Maybe in the future. +# +# What is the difference to `./types.nix`? / Why another type system? +# +# `./types.nix` is deeply entangled with the module system, +# in order to use it on plain nix values, you have to invoke the +# module system, which is pretty heavyweight and hard/verbose to do. +# Contrary to popular opinion, the `check` functions on module types +# does *not* do a recursive check for complex types/values. +# Plus, it is not possible to catch a type error, since the module +# system always instantly aborts nix evaluation on type error. +# The `checkType` function in this module returns a detailed, +# structured # error for each part of the substructure that +# does not match the given expected type. +# Concerning expressibility, an attrset with fixed fields can +# be given as easy as `product { field1 = type; … }`, whereas +# in `./types.nix` you need to use the complictated `submodule` +# mechanism. We also support tagged unions (`./types.nix` does not) +# and untagged unions of an arbitrary set of types (can be emulated +# with nested `either`s in `./types.nix`). +# +# In short: if you want to check a module option, use `./types.nix`. +# If you want to check a plain (possibly complex) nix value, +# use this module. +# +# The main function is `checkType`. +# Tests can be found in './tests/types-simple.nix`. + +{ lib }: +let + + # The type functor. + # t is the recursion “not yet inserted”. + # + # data Type t + # = Scalar + # | Recursive (Rec t) + # | Sum (Map String t) + # | Product (Map String t) + # | Union (List t) + # deriving (Functor) + # + # Fix Type is every t replaced with Type, recursively. + + # The alternatives above are tagged manually, by this variant enum: + variants = { + scalar = 0; + recursive = 1; + sum = 2; + product = 3; + union = 4; + }; + + ## -- HELPERS -- + + unreachable = abort "should not be reached"; + + # Functor instance of Type + # fmap :: (a -> b) -> (Type a) -> (Type b) + # it just applies a function over the “holes” in Type variants + fmap = f: t: + if t.variant == variants.scalar then t + else if t.variant == variants.recursive then + t // { nested = f t.nested; } + else if t.variant == variants.sum then + t // { alts = lib.mapAttrs (lib.const f) t.alts; } + else if t.variant == variants.product then + t // { opt = lib.mapAttrs (lib.const f) t.opt; + req = lib.mapAttrs (lib.const f) t.req; } + else if t.variant == variants.union then + t // { altList = map f t.altList; } + else unreachable; + + # cata :: (Type a -> a) -> Fix Type -> a + # collapses the structure Fix Type (nested types) into an a, + # by collapsing one layer at a time with the function (/algebra) + # alg :: (Type a -> a) + cata = alg: t: alg (fmap (cata alg) t); + + + ## -- MAIN -- + + # Main type checking function. + # Example: + # > checkType (list string) [ "foo" "bar" ] + # { } + # > checkType (list string) [ "foo" 42 ] + # { "1" = { should = "string"; val = 42; }; } + # + # checkType :: Fix Type -> Value -> (Nested Attrs) Errors + # + # where { } means no error (the given value is of the given type) + # and { should : String, val : Value } denotes a type mismatch. + checkType = + let + # the type check suceeded + ok = {}; + # filters out non-error messages + mapAndFilter = f: vals: + lib.filterAttrs (_: v: v != {}) (lib.mapAttrs f vals); + # alg :: Type (Value -> Errors) -> (Value -> Errors) + alg = t: v: + # the main type check on each “level” + # the cases further down handle the differences + # between the variants (poor man’s pattern matching) + # TODO: some errors should throw some more context. + # e.g. putting more than one field in a sum value + if !(t.check v) then { should = t.description; val = v; } + # scalars have just one level (already checked above) + else if t.variant == variants.scalar then ok + # grab all child values and type check them one by one + else if t.variant == variants.recursive then + mapAndFilter (_: el: t.nested el) (t.each v) + # there’s exactly one tagged value, so check that + else if t.variant == variants.sum then + # we already tested length == 1 in .check + let alt = builtins.head (builtins.attrNames v); + in t.alts.${alt} v.${alt} + # check each field according to its type + # optional missing fields of course always pass the check + else if t.variant == variants.product then + mapAndFilter (n: f: if v ? ${n} then f v.${n} else ok) + (t.req // t.opt) + # if the value fails the check for each type it can have, + # we throw an error; if one check succeeds the union is satisfied + else if t.variant == variants.union then + # unions are awkward, the type checker can’t do much here + if lib.all (res: res != ok) (map (f: f v) t.altList) + then { should = t.description; val = v; } + else ok + else unreachable; + # cata only has “two arguments”, giving it a Value as third + # argument “morphs” the `a` in alg to (Value -> Errors); + # of course we could curry t and v away, + # but just `cata alg` would be very confusing ;) + in t: v: cata alg t v; + + + ## -- TYPE SETUP STUFF -- + + mkBaseType = { + # the (displayable) type description + description, + # a function to check the outermost type, given a value (Val -> Bool) + check, + # the variant of this type + variant, + # extra fields belonging to the variant + extraFields + }: { inherit description check variant; } // extraFields; + + mkScalar = { description, check }: mkBaseType { + inherit description check; + variant = variants.scalar; + extraFields = {}; + }; + + mkRecursive = { description, check, + # return all children for a value of this type T t, + # give each child (of type t) a displayable name. + # (T t -> Map Name t) + each, + # The nested value t of the type functor + nested + }: mkBaseType { + inherit description check; + variant = variants.recursive; + extraFields = { inherit each nested; }; + }; + + + ## -- TYPES -- + + # the type with no inhabitants (kind of useless …) + void = mkScalar { + description = "void"; + # there are no values of type void + check = lib.const false; + }; + + # the any type, every value is an inhabitant + # it basically turns off the type system, use with care + any = mkScalar { + description = "any type"; + check = lib.const true; + }; + + # the type with exactly one inhabitant + unit = mkScalar { + description = "unit"; + # there is exactly one unit value, we represent it with {} + # Q: why not `null`? + # A: `null` has strong connotations as the “always existing” + # alternative value; of course in a unityped language like + # nix this is moot, but here we take the chance to throw out + # this harmful idea (the “million dollar mistake”). + check = v: v == {}; + }; + + # the type with two inhabitants + bool = mkScalar { + description = "boolean"; + check = builtins.isBool; + }; + + # a nix string + string = mkScalar { + description = "string"; + check = builtins.isString; + }; + + # a nix path + path = mkScalar { + description = "path"; + # there is no `isPath` predicate, + # but `typeOf` exists since 1.6.1 + check = v: builtins.typeOf v == "path"; + }; + + # a signed nix integer + int = mkScalar { + description = "integer"; + check = builtins.isInt; + }; + + # a nix floating point number + float = mkScalar { + description = "float"; + check = builtins.isFloat; + }; + + # helper for descriptions of recursive types + # TODO: descriptions need to assume t is a type, + # which is only true for Fix Type. How to make nice? + describe = t: t.description or ""; + + # list with children of type t + # list bool: + # [ true false false ] + # list (attrs unit): + # [ { a = {}; } { b = {}; } ] + # [] + list = t: mkRecursive { + description = "list of ${describe t}"; + check = builtins.isList; + # each child gets named by its index, starting from 0 + each = l: builtins.listToAttrs + (lib.imap0 (i: v: lib.nameValuePair (toString i) v) l); + nested = t; + }; + + # attrset with children of type t + # attrs int: { foo = 23; bar = 42; } + # attrs (attrs string): + # { foo.bar = "hello"; baz.quux = "x"; } + # { x = { y = "wow"; }; } + attrs = t: mkRecursive { + description = "attrset of ${describe t}"; + check = builtins.isAttrs; + each = lib.id; + nested = t; + }; + + # TODO: nonempty list and attrs + + # product type with fields of the specified types + # product { x = int; y = unit; }: + # { x = 23; y = {}; } + # { x = 42; y = {}; } + # product {}: <- yeah, that’s isomorphic to unit + # { } + # product { foo = void; }: + # just kidding. :) + product = fields: productOpt { req = fields; opt = {}; }; + + # product type with the possibility of optional fields + # actually the more generic type of product, BUT: + # code with a fixed number of fields is less brittle. + # choose wisely. + # productOpt { req = {}; opt = { a = unit; b = int; }: + # { } + # { a = {}; } + # { a = {}; b = 23; } + # if a product is `open`, any fields that are not + # given a type in either `req` or `opt` will default + # to type `any` (that is they typecheck by default). + # productOpt { req = { a = int; }; opt = {}; open = true; } + # { a = 23; } + # { a = 42; b = "foo"; c = false; } + productOpt = { req, opt, open ? false }: + let reqfs = builtins.attrNames req; + optfs = builtins.attrNames opt; in + # opt and rec fields must not contain the same fields + assert (lib.intersectLists reqfs optfs == []); + mkBaseType { + description = "{ " + + lib.concatStringsSep ", " + ( lib.mapAttrsToList (n: t: "${n}: ${describe t}") req + ++ lib.mapAttrsToList (n: t: "[${n}: ${describe t}]") opt + # TODO: maybe but this at the beginning: [ …, + # so that it’s easier to see that an attrset is open + ++ lib.optional open "…") + + " }"; + check = v: + let vfs = builtins.attrNames v; in + lib.foldl lib.and (builtins.isAttrs v) [ + # if there’s only required fields, this is an optimization + (opt == {} && !open -> reqfs == vfs) + # all required fields have to exist in the value + # reqfs - vfs + (lib.subtractLists vfs reqfs == []) + # whithout req, and if the product is not open + # only opt fields must be in the value + # (vfs - reqfs) - otfs + (!open -> [] == lib.subtractLists optfs + (lib.subtractLists reqfs vfs)) + ]; + variant = variants.product; + extraFields = { + inherit opt req open; + }; + }; + + # sum type with alternatives of the specified types + # sum { left = string; right = bool; }: + # { left = "work it"; } + # { right = false; } + # sum { true = unit; false = unit; } <- that’s isomorphic to bool + # { true = {}; } + # { false = {}; } + # sum { X = product { name = string; age = int; }; Y = list unit; } + # { X = { name = "peter shaw"; age = 22; }; } + # { Y = [ {} {} {} {} {} {} {} {} ]; } + sum = alts: assert alts != {}; mkBaseType { + description = "< " + + lib.concatStringsSep " | " + (lib.mapAttrsToList (n: t: "${n}: ${describe t}") alts) + + " >"; + check = v: + let alt = builtins.attrNames v; + in builtins.isAttrs v + # exactly one of the alts has to be used by the value + && builtins.length alt == 1 + # the alt tag of the value should of course be a possibility + && alts ? ${lib.head alt}; + variant = variants.sum; + extraFields = { + inherit alts; + }; + }; + + # untagged union type + # ATTENTION: this leads to *bad* type checker errors in practice, + # you also can’t do pattern matching; use sum if possible. + # union [ bool int ] + # 3 + # true + # list (union [ int string ]) + # [ "foo" 34 "bar" ] + # please don’t use this. + union = altList: assert altList != []; mkBaseType { + description = "one of [ " + + lib.concatMapStringsSep ", " describe altList + + " ]"; + # any type that checks out is fine + check = v: lib.any (t: t.check v) altList; + variant = variants.union; + extraFields = { + inherit altList; + }; + }; + + # restrict applies a further check to values of type + # the idea is simple, but some crazy things are possible, like + # * even integers + # * integers between 23 and 42 + # * enumerations + # * lists with exactly three elements where the second is the string "bla" + # type errors from the base type checks are retained. + # + # restrict { type = int; check = isEven; … }: + # 2 + # 42 + # see tests for further examples + restrict = { + # type that should be restricted + type, + # takes a value of type + # return true for values of type that are valid + check, + # the (displayable) restricted type description + description + }: type // { + inherit description; + # first the general type is checked, + # then the restriction check is tried + # this way the restriction check can assume the correct type + check = v: type.check v && check v; + }; + + # TODO: should scalars be allowed as nest types? + # TODO: how to implement? + # nested = nest: t: mkBaseType { + # description = "nested ${describe nest} of ${describe t}"; + # check = nest.check ; + # variant = nest.variant; + # extraFields = { + + + ## -- FUNCTIONS -- + + # TODO: pattern match function + # match = + + # Feed it the output of checkType (after testing for success (== {}) + # and it returns a more or less pretty string of errors. + prettyPrintErrors = + let + isLeaf = v: {} == checkType (product { should = string; val = any; }) v; + recurse = path: errs: + if isLeaf errs + then [{ inherit path; inherit (errs) should val; }] + else builtins.concatLists (lib.mapAttrsToList + (p: errs': recurse (path ++ [p]) errs') errs); + pretty = { path, should, val }: + "${lib.concatStringsSep "." path} should be: ${ + should}\nbut is: ${lib.generators.toPretty {} val}"; + in errs: lib.concatMapStringsSep "\n" pretty (recurse [] errs); + +in { + # The type of nix types, as non-recursive functor. + # fmap and cata are specialized to Type. + Type = { inherit variants fmap cata; }; + # Constructor functions for types. + # Their internal structure/fields are an *implementation detail*. + inherit void any unit bool string path int float + list attrs product productOpt sum union + restrict; + # Type checking. + inherit checkType; + # Functions. + inherit prettyPrintErrors; +} diff --git a/pkgs/applications/misc/yate/default.nix b/pkgs/applications/misc/yate/default.nix index 61c7b11f20848..a5fb841b910cc 100644 --- a/pkgs/applications/misc/yate/default.nix +++ b/pkgs/applications/misc/yate/default.nix @@ -34,7 +34,7 @@ stdenv.mkDerivation rec { homepage = http://yate.null.ro/; # Yate's license is GPL with an exception for linking with # OpenH323 and PWlib (licensed under MPL). - license = ["GPL" "MPL"]; + license = lib.licenses.gpl2; maintainers = [ lib.maintainers.marcweber ]; platforms = [ "i686-linux" "x86_64-linux" ]; }; diff --git a/pkgs/applications/networking/browsers/firefox-bin/default.nix b/pkgs/applications/networking/browsers/firefox-bin/default.nix index 3a6dd626ac69e..d67db19d2f113 100644 --- a/pkgs/applications/networking/browsers/firefox-bin/default.nix +++ b/pkgs/applications/networking/browsers/firefox-bin/default.nix @@ -184,8 +184,7 @@ stdenv.mkDerivation { meta = with stdenv.lib; { description = "Mozilla Firefox, free web browser (binary package)"; homepage = http://www.mozilla.org/firefox/; - license = { - free = false; + license = licenses.unfree // { url = http://www.mozilla.org/en-US/foundation/trademarks/policy/; }; platforms = builtins.attrNames mozillaPlatforms; diff --git a/pkgs/applications/networking/instant-messengers/teamspeak/client.nix b/pkgs/applications/networking/instant-messengers/teamspeak/client.nix index ea2030e964b9a..e37c14112cb33 100644 --- a/pkgs/applications/networking/instant-messengers/teamspeak/client.nix +++ b/pkgs/applications/networking/instant-messengers/teamspeak/client.nix @@ -102,6 +102,7 @@ stdenv.mkDerivation rec { description = "The TeamSpeak voice communication tool"; homepage = http://teamspeak.com/; license = { + shortName = "teamspeak-client-license"; fullName = "Teamspeak client license"; url = http://sales.teamspeakusa.com/licensing.php; free = false; diff --git a/pkgs/applications/networking/mailreaders/thunderbird-bin/default.nix b/pkgs/applications/networking/mailreaders/thunderbird-bin/default.nix index 9dbc99cac7d0e..6f9b8e6a37d03 100644 --- a/pkgs/applications/networking/mailreaders/thunderbird-bin/default.nix +++ b/pkgs/applications/networking/mailreaders/thunderbird-bin/default.nix @@ -171,6 +171,8 @@ stdenv.mkDerivation { description = "Mozilla Thunderbird, a full-featured email client (binary package)"; homepage = http://www.mozilla.org/thunderbird/; license = { + shortName = "thunderbird-binary-license"; + fullName = "Thunderbird Binary License Unfree"; free = false; url = http://www.mozilla.org/en-US/foundation/trademarks/policy/; }; diff --git a/pkgs/applications/science/biology/diamond/default.nix b/pkgs/applications/science/biology/diamond/default.nix index a45e5df2f5284..36ed85e1c6ba2 100644 --- a/pkgs/applications/science/biology/diamond/default.nix +++ b/pkgs/applications/science/biology/diamond/default.nix @@ -34,7 +34,9 @@ stdenv.mkDerivation rec { homepage = https://github.com/bbuchfink/diamond; license = { fullName = "University of Tuebingen, Benjamin Buchfink"; + shortName = "uni-tuebingen-buchfink"; url = https://raw.githubusercontent.com/bbuchfink/diamond/master/src/COPYING; + free = false; }; maintainers = [ maintainers.metabar ]; }; diff --git a/pkgs/applications/science/chemistry/molden/default.nix b/pkgs/applications/science/chemistry/molden/default.nix index 1472b4b2af9e8..021e15ecc7ea8 100644 --- a/pkgs/applications/science/chemistry/molden/default.nix +++ b/pkgs/applications/science/chemistry/molden/default.nix @@ -31,6 +31,7 @@ stdenv.mkDerivation rec { description = "Display and manipulate molecular structures"; homepage = http://www.cmbi.ru.nl/molden/; license = { + shortName = "molden-unfree"; fullName = "Free for academic/non-profit use"; url = http://www.cmbi.ru.nl/molden/CopyRight.html; free = false; diff --git a/pkgs/applications/science/electronics/ngspice/default.nix b/pkgs/applications/science/electronics/ngspice/default.nix index 114a2eea85f53..0935602ff0de1 100644 --- a/pkgs/applications/science/electronics/ngspice/default.nix +++ b/pkgs/applications/science/electronics/ngspice/default.nix @@ -15,7 +15,7 @@ stdenv.mkDerivation { meta = with stdenv.lib; { description = "The Next Generation Spice (Electronic Circuit Simulator)"; homepage = http://ngspice.sourceforge.net; - license = with licenses; [ "BSD" gpl2 ]; + license = with licenses; [ bsd3 gpl2 ]; maintainers = with maintainers; [ viric rongcuid ]; platforms = platforms.linux; }; diff --git a/pkgs/data/fonts/tewi/default.nix b/pkgs/data/fonts/tewi/default.nix index 32d859bf53bd4..3f9a1f566895c 100644 --- a/pkgs/data/fonts/tewi/default.nix +++ b/pkgs/data/fonts/tewi/default.nix @@ -42,8 +42,10 @@ stdenv.mkDerivation rec { ''; homepage = https://github.com/lucy/tewi-font; license = { + shortName = "gpl3-with-font-exception"; fullName = "GNU General Public License with a font exception"; url = "https://www.gnu.org/licenses/gpl-faq.html#FontException"; + free = true; }; maintainers = [ maintainers.fro_ozen ]; platforms = platforms.unix; diff --git a/pkgs/desktops/maxx/default.nix b/pkgs/desktops/maxx/default.nix index 69d16c9b20a1d..d9007e80bd87d 100644 --- a/pkgs/desktops/maxx/default.nix +++ b/pkgs/desktops/maxx/default.nix @@ -83,6 +83,7 @@ in stdenv.mkDerivation { description = "A replica of IRIX Interactive Desktop"; homepage = http://www.maxxinteractive.com; license = { + shortName = "maxx-interactive-desktop-linux-agreement"; fullName = "The MaXX Interactive Desktop for Linux License Agreement"; url = http://www.maxxinteractive.com/site/?page_id=97; free = false; # redistribution is only allowed to *some* hardware, etc. diff --git a/pkgs/development/libraries/goocanvas/default.nix b/pkgs/development/libraries/goocanvas/default.nix index 0f9b3f85fcd89..d4053624af8c2 100644 --- a/pkgs/development/libraries/goocanvas/default.nix +++ b/pkgs/development/libraries/goocanvas/default.nix @@ -13,10 +13,10 @@ stdenv.mkDerivation rec { nativeBuildInputs = [ pkgconfig ]; buildInputs = [ gtk2 cairo glib ]; - meta = { + meta = { description = "Canvas widget for GTK+ based on the the Cairo 2D library"; homepage = http://goocanvas.sourceforge.net/; - license = ["GPL" "LGPL"]; + license = with stdenv.lib.licenses; [ gpl2 lgpl2 ]; platforms = stdenv.lib.platforms.unix; }; } diff --git a/pkgs/development/libraries/mpich2/default.nix b/pkgs/development/libraries/mpich2/default.nix index 4b8e2b651e454..f3589b4fed890 100644 --- a/pkgs/development/libraries/mpich2/default.nix +++ b/pkgs/development/libraries/mpich2/default.nix @@ -39,8 +39,10 @@ stdenv.mkDerivation rec { ''; homepage = http://www.mcs.anl.gov/mpi/mpich2/; license = { + shortName = "mpich-license-permissive"; url = http://git.mpich.org/mpich.git/blob/a385d6d0d55e83c3709ae851967ce613e892cd21:/COPYRIGHT; fullName = "MPICH license (permissive)"; + free = false; }; maintainers = [ maintainers.markuskowa ]; platforms = platforms.unix; diff --git a/pkgs/development/tools/build-managers/buildbot/plugins.nix b/pkgs/development/tools/build-managers/buildbot/plugins.nix index 1d1b963df9935..35c8ed8c71cd9 100644 --- a/pkgs/development/tools/build-managers/buildbot/plugins.nix +++ b/pkgs/development/tools/build-managers/buildbot/plugins.nix @@ -97,7 +97,7 @@ meta = with stdenv.lib; { homepage = http://buildbot.net/; description = "Buildbot WSGI dashboards Plugin"; - maintainers = with maintainers; [ akazakov ]; + maintainers = with maintainers; [ ]; license = licenses.gpl2; }; }; diff --git a/pkgs/os-specific/linux/beegfs/default.nix b/pkgs/os-specific/linux/beegfs/default.nix index f17bc9492c1b7..8949cfade0b86 100644 --- a/pkgs/os-specific/linux/beegfs/default.nix +++ b/pkgs/os-specific/linux/beegfs/default.nix @@ -145,6 +145,7 @@ in stdenv.mkDerivation rec { homepage = "https://www.beegfs.io"; platforms = [ "i686-linux" "x86_64-linux" ]; license = { + shortName = "beegfs-eula"; fullName = "BeeGFS_EULA"; url = "https://www.beegfs.io/docs/BeeGFS_EULA.txt"; free = false; diff --git a/pkgs/servers/firebird/default.nix b/pkgs/servers/firebird/default.nix index da54936092b3c..91b8194089091 100644 --- a/pkgs/servers/firebird/default.nix +++ b/pkgs/servers/firebird/default.nix @@ -83,7 +83,20 @@ stdenv.mkDerivation rec { meta = { description = "SQL relational database management system"; homepage = http://www.firebirdnews.org; - license = ["IDPL" "Interbase-1.0"]; + license = [ + { + shortName = "IDPL"; + fullName = "Initial Developer Public License"; + url = "https://firebirdsql.org/en/initial-developer-s-public-license-version-1-0/"; + free = false; + } + { + shortName = "Interbase-1.0"; + fullName = "InterBase Public License"; + url = "https://firebirdsql.org/en/interbase-public-license/"; + free = false; + } + ]; maintainers = [stdenv.lib.maintainers.marcweber]; platforms = stdenv.lib.platforms.linux; }; diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index 4774d7602437e..d4dc9d0668f55 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -133,45 +133,74 @@ let else throw; in handler msg; - - metaTypes = with lib.types; rec { - # These keys are documented - description = str; - longDescription = str; - branch = str; - homepage = either (listOf str) str; - downloadPage = str; - license = either (listOf lib.types.attrs) (either lib.types.attrs str); - maintainers = listOf (attrsOf str); - priority = int; - platforms = listOf (either str lib.systems.parsed.types.system); - hydraPlatforms = listOf str; - broken = bool; - - # Weirder stuff that doesn't appear in the documentation? - knownVulnerabilities = listOf str; - name = str; - version = str; - tag = str; - updateWalker = bool; - executables = listOf str; - outputsToInstall = listOf str; - position = str; - available = bool; - repositories = attrsOf str; - isBuildPythonPackage = platforms; - schedulingPriority = int; - downloadURLRegexp = str; - isFcitxEngine = bool; - isIbusEngine = bool; - isGutenprint = bool; - }; - - checkMetaAttr = k: v: - if metaTypes?${k} then - if metaTypes.${k}.check v then null else "key '${k}' has a value ${toString v} of an invalid type ${builtins.typeOf v}; expected ${metaTypes.${k}.description}" - else "key '${k}' is unrecognized; expected one of: \n\t [${lib.concatMapStringsSep ", " (x: "'${x}'") (lib.attrNames metaTypes)}]"; - checkMeta = meta: if shouldCheckMeta then lib.remove null (lib.mapAttrsToList checkMetaAttr meta) else []; + metaType = with lib.types-simple; + let + # TODO: use the types from lib/systems/parse.nix + # any should be lib.systems.parsed.types.system + system = any; + platform = union [ string any ]; + + in productOpt { + req = {}; + opt = { + # These keys are documented + description = string; + homepage = union [ (list string) string ]; + longDescription = string; + branch = string; + downloadPage = string; + license = + let + licenseT = productOpt { + req = { + shortName = string; + fullName = string; + free = bool; + }; + opt = { + spdxId = string; + url = string; + }; + }; + in union [ licenseT (list licenseT) string ]; + maintainers = list (productOpt { + req = { + name = string; + email = string; + }; + opt = { + github = string; + }; + }); + priority = int; + platforms = list platform; + hydraPlatforms = list platform; + broken = bool; + + # Weirder stuff that doesn't appear in the documentation? + knownVulnerabilities = list string; + name = string; + version = string; + tag = string; + updateWalker = bool; + executables = list string; + outputsToInstall = list string; + position = string; + available = bool; + repositories = attrs string; + isBuildPythonPackage = list platform; + schedulingPriority = int; + downloadURLRegexp = string; + isFcitxEngine = bool; + isIbusEngine = bool; + isGutenprint = bool; + }; + }; + + checkMeta = meta: + if shouldCheckMeta + then lib.types-simple.checkType metaType meta + else {}; checkPlatform = attrs: let raw = attrs.meta.platforms; @@ -195,8 +224,11 @@ let { valid = false; reason = "broken"; errormsg = "is not supported on ‘${hostPlatform.config}’"; } else if !(hasAllowedInsecure attrs) then { valid = false; reason = "insecure"; errormsg = "is marked as insecure"; } - else let res = checkMeta (attrs.meta or {}); in if res != [] then - { valid = false; reason = "unknown-meta"; errormsg = "has an invalid meta attrset:${lib.concatMapStrings (x: "\n\t - " + x) res}"; } + else let res = checkMeta (attrs.meta or {}); in if res != {} then + { valid = false; reason = "unknown-meta"; errormsg = '' + has an invalid meta attrset: + ${lib.types-simple.prettyPrintErrors res} + ''; } else { valid = true; }; assertValidity = attrs: let diff --git a/pkgs/tools/misc/sl/default.nix b/pkgs/tools/misc/sl/default.nix index 7a5ceb204496b..082dbcf7d1e70 100644 --- a/pkgs/tools/misc/sl/default.nix +++ b/pkgs/tools/misc/sl/default.nix @@ -27,6 +27,7 @@ stdenv.mkDerivation rec { shortName = "Toyoda Masashi's free software license"; fullName = shortName; url = https://github.com/mtoyoda/sl/blob/master/LICENSE; + free = false; }; description = "Steam Locomotive runs across your terminal when you type 'sl'"; platforms = with stdenv.lib.platforms; unix; diff --git a/pkgs/tools/networking/p2p/tahoe-lafs/default.nix b/pkgs/tools/networking/p2p/tahoe-lafs/default.nix index 4bdc630efd796..cd70472f22728 100644 --- a/pkgs/tools/networking/p2p/tahoe-lafs/default.nix +++ b/pkgs/tools/networking/p2p/tahoe-lafs/default.nix @@ -60,7 +60,13 @@ pythonPackages.buildPythonApplication rec { are unavailable, malfunctioning, or malicious. ''; homepage = http://tahoe-lafs.org/; - license = [ lib.licenses.gpl2Plus /* or */ "TGPPLv1+" ]; + license = [ + lib.licenses.gpl2Plus /* or */ + { shortName = "TGPPLv1+"; + fullName = "Transitive Grace Period Public Licence v. 1.0 or later"; + url = "https://github.com/zooko/tgppl/blob/master/COPYING.TGPPL-v1.rst"; + free = false; } + ]; maintainers = with lib.maintainers; [ MostAwesomeDude ]; platforms = lib.platforms.gnu; # arbitrary choice }; diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 8e4da97d3d43c..1f4cf2ab4b06f 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -10381,10 +10381,10 @@ in { license = with licenses; [ asl20 # third-party program licenses (embedded in the sources) - "LGPL" # Crystal_Clear + lgpl2Plus # Crystal_Clear free # dns asl20 # graphy - "BSD" # jinja2 + bsd3 # jinja2 ]; longDescription = '' It hunts down the fastest DNS servers available for your computer to