From 0dbd4638e07d224e642b52c1dd0468c3752e2479 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 15 Sep 2009 13:01:46 +0000 Subject: [PATCH] * Two primops: builtins.intersectAttrs and builtins.functionArgs. intersectAttrs returns the (right-biased) intersection between two attribute sets, e.g. every attribute from the second set that also exists in the first. functionArgs returns the set of attributes expected by a function. The main goal of these is to allow the elimination of most of all-packages.nix. Most package instantiations in all-packages.nix have this form: foo = import ./foo.nix { inherit a b c; }; With intersectAttrs and functionArgs, this can be written as: foo = callPackage (import ./foo.nix) { }; where callPackage = f: args: f ((builtins.intersectAttrs (builtins.functionArgs f) pkgs) // args); I.e., foo.nix is called with all attributes from "pkgs" that it actually needs (e.g., pkgs.a, pkgs.b and pkgs.c). (callPackage can do any other generic package-level stuff we might want, such as applying makeOverridable.) Of course, the automatically supplied arguments can be overriden if needed, e.g. foo = callPackage (import ./foo.nix) { c = c_version_2; }; but for the vast majority of packages, this won't be needed. The advantages are to reduce the amount of typing needed to add a dependency (from three sites to two), and to reduce the number of trivial commits to all-packages.nix. For the former, there have been two previous attempts: - Use "args: with args;" in the package's function definition. This however obscures the actual expected arguments of a function, which is very bad. - Use "{ arg1, arg2, ... }:" in the package's function definition (i.e. use the ellipis "..." to allow arbitrary additional arguments), and then call the function with all of "pkgs" as an argument. But this inhibits error detection if you call it with an misspelled (or obsolete) argument. --- src/libexpr/primops.cc | 66 +++++++++++++++++++ tests/lang/eval-okay-functionargs.exp.xml | 15 +++++ tests/lang/eval-okay-functionargs.nix | 80 +++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 tests/lang/eval-okay-functionargs.exp.xml create mode 100644 tests/lang/eval-okay-functionargs.nix diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 783d26c4..6408ca95 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -812,6 +812,70 @@ static Expr prim_isAttrs(EvalState & state, const ATermVector & args) } +/* Return the right-biased intersection of two attribute sets as1 and + as2, i.e. a set that contains every attribute from as2 that is also + a member of as1. */ +static Expr prim_intersectAttrs(EvalState & state, const ATermVector & args) +{ + ATermMap as1, as2; + queryAllAttrs(evalExpr(state, args[0]), as1, true); + queryAllAttrs(evalExpr(state, args[1]), as2, true); + + ATermMap res; + foreach (ATermMap::const_iterator, i, as2) + if (as1[i->key]) res.set(i->key, i->value); + + return makeAttrs(res); +} + + +static void attrsInPattern(ATermMap & map, Pattern pat) +{ + ATerm name; + ATermList formals; + Pattern pat1, pat2; + ATermBool ellipsis; + if (matchAttrsPat(pat, formals, ellipsis)) { + for (ATermIterator i(formals); i; ++i) { + ATerm def; + if (!matchFormal(*i, name, def)) abort(); + map.set(name, makeAttrRHS(makeBool(def != constNoDefaultValue), makeNoPos())); + } + } + else if (matchAtPat(pat, pat1, pat2)) { + attrsInPattern(map, pat1); + attrsInPattern(map, pat2); + } +} + + +/* Return a set containing the names of the formal arguments expected + by the function `f'. The value of each attribute is a Boolean + denoting whether has a default value. For instance, + + functionArgs ({ x, y ? 123}: ...) + => { x = false; y = true; } + + "Formal argument" here refers to the attributes pattern-matched by + the function. Plain lambdas are not included, e.g. + + functionArgs (x: ...) + => { } +*/ +static Expr prim_functionArgs(EvalState & state, const ATermVector & args) +{ + Expr f = evalExpr(state, args[0]); + ATerm pat, body, pos; + if (!matchFunction(f, pat, body, pos)) + throw TypeError("`functionArgs' required a function"); + + ATermMap as; + attrsInPattern(as, pat); + + return makeAttrs(as); +} + + /************************************************************* * Lists *************************************************************/ @@ -1070,6 +1134,8 @@ void EvalState::addPrimOps() addPrimOp("__isAttrs", 1, prim_isAttrs); addPrimOp("removeAttrs", 2, prim_removeAttrs); addPrimOp("__listToAttrs", 1, prim_listToAttrs); + addPrimOp("__intersectAttrs", 2, prim_intersectAttrs); + addPrimOp("__functionArgs", 1, prim_functionArgs); // Lists addPrimOp("__isList", 1, prim_isList); diff --git a/tests/lang/eval-okay-functionargs.exp.xml b/tests/lang/eval-okay-functionargs.exp.xml new file mode 100644 index 00000000..651f54c3 --- /dev/null +++ b/tests/lang/eval-okay-functionargs.exp.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/tests/lang/eval-okay-functionargs.nix b/tests/lang/eval-okay-functionargs.nix new file mode 100644 index 00000000..68dca62e --- /dev/null +++ b/tests/lang/eval-okay-functionargs.nix @@ -0,0 +1,80 @@ +let + + stdenvFun = { }: { name = "stdenv"; }; + stdenv2Fun = { }: { name = "stdenv2"; }; + fetchurlFun = { stdenv }: assert stdenv.name == "stdenv"; { name = "fetchurl"; }; + atermFun = { stdenv, fetchurl }: { name = "aterm-${stdenv.name}"; }; + aterm2Fun = { stdenv, fetchurl }: { name = "aterm2-${stdenv.name}"; }; + nixFun = { stdenv, fetchurl, aterm }: { name = "nix-${stdenv.name}-${aterm.name}"; }; + + mplayerFun = + { stdenv, fetchurl, enableX11 ? false, xorg ? null, enableFoo ? true, foo ? null }: + assert stdenv.name == "stdenv2"; + assert enableX11 -> xorg.libXv.name == "libXv"; + assert enableFoo -> foo != null; + { name = "mplayer-${stdenv.name}.${xorg.libXv.name}-${xorg.libX11.name}"; }; + + makeOverridable = f: origArgs: f origArgs // + { override = newArgs: + makeOverridable f (origArgs // (if builtins.isFunction newArgs then newArgs origArgs else newArgs)); + }; + + callPackage_ = pkgs: f: args: + makeOverridable f ((builtins.intersectAttrs (builtins.functionArgs f) pkgs) // args); + + allPackages = + { overrides ? (pkgs: pkgsPrev: { }) }: + let + callPackage = callPackage_ pkgs; + pkgs = pkgsStd // (overrides pkgs pkgsStd); + pkgsStd = { + inherit pkgs; + stdenv = callPackage stdenvFun { }; + stdenv2 = callPackage stdenv2Fun { }; + fetchurl = callPackage fetchurlFun { }; + aterm = callPackage atermFun { }; + xorg = callPackage xorgFun { }; + mplayer = callPackage mplayerFun { stdenv = pkgs.stdenv2; enableFoo = false; }; + nix = callPackage nixFun { }; + }; + in pkgs; + + libX11Fun = { stdenv, fetchurl }: { name = "libX11"; }; + libX11_2Fun = { stdenv, fetchurl }: { name = "libX11_2"; }; + libXvFun = { stdenv, fetchurl, libX11 }: { name = "libXv"; }; + + xorgFun = + { pkgs }: + let callPackage = callPackage_ (pkgs // pkgs.xorg); in + { + libX11 = callPackage libX11Fun { }; + libXv = callPackage libXvFun { }; + }; + +in + +let + + pkgs = allPackages { }; + + pkgs2 = allPackages { + overrides = pkgs: pkgsPrev: { + stdenv = pkgs.stdenv2; + nix = pkgsPrev.nix.override { aterm = aterm2Fun { inherit (pkgs) stdenv fetchurl; }; }; + xorg = pkgsPrev.xorg // { libX11 = libX11_2Fun { inherit (pkgs) stdenv fetchurl; }; }; + }; + }; + +in + + [ pkgs.stdenv.name + pkgs.fetchurl.name + pkgs.aterm.name + pkgs2.aterm.name + pkgs.xorg.libX11.name + pkgs.xorg.libXv.name + pkgs.mplayer.name + pkgs2.mplayer.name + pkgs.nix.name + pkgs2.nix.name + ]