diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2020-08-24 14:31:10 +0200 |
---|---|---|
committer | Eelco Dolstra <edolstra@gmail.com> | 2020-08-24 14:31:10 +0200 |
commit | a990f063ff7afc6028ab430170ad23e1285d1a6d (patch) | |
tree | fe58e282a6a402c95ce2d023c2fa884915ecf287 /src | |
parent | 33b1679d75f2a3a5dac053431a41897ebf96a3f3 (diff) |
Move primop docs inline
This makes them available to 'nix repl'.
Diffstat (limited to 'src')
-rw-r--r-- | src/libexpr/primops.cc | 1178 | ||||
-rw-r--r-- | src/libexpr/primops/fetchTree.cc | 175 |
2 files changed, 1235 insertions, 118 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 902a37e6b..78892053a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -275,6 +275,16 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu mkString(v, state.symbols.create(t)); } +static RegisterPrimOp primop_typeOf({ + .name = "__typeOf", + .args = {"e"}, + .doc = R"( + Return a string representing the type of the value *e*, namely + `"int"`, `"bool"`, `"string"`, `"path"`, `"null"`, `"set"`, + `"list"`, `"lambda"` or `"float"`. + )", + .fun = prim_typeOf, +}); /* Determine whether the argument is the null value. */ static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -283,6 +293,18 @@ static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Valu mkBool(v, args[0]->type == tNull); } +static RegisterPrimOp primop_isNull({ + .name = "isNull", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to `null`, and `false` otherwise. + + > **Warning** + > + > This function is *deprecated*; just write `e == null` instead. + )", + .fun = prim_isNull, +}); /* Determine whether the argument is a function. */ static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -302,6 +324,14 @@ static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, mkBool(v, res); } +static RegisterPrimOp primop_isFunction({ + .name = "__isFunction", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a function, and `false` otherwise. + )", + .fun = prim_isFunction, +}); /* Determine whether the argument is an integer. */ static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -310,6 +340,15 @@ static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value mkBool(v, args[0]->type == tInt); } +static RegisterPrimOp primop_isInt({ + .name = "__isInt", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to an integer, and `false` otherwise. + )", + .fun = prim_isInt, +}); + /* Determine whether the argument is a float. */ static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -317,6 +356,15 @@ static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Val mkBool(v, args[0]->type == tFloat); } +static RegisterPrimOp primop_isFloat({ + .name = "__isFloat", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a float, and `false` otherwise. + )", + .fun = prim_isFloat, +}); + /* Determine whether the argument is a string. */ static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -324,6 +372,14 @@ static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Va mkBool(v, args[0]->type == tString); } +static RegisterPrimOp primop_isString({ + .name = "__isString", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a string, and `false` otherwise. + )", + .fun = prim_isString, +}); /* Determine whether the argument is a Boolean. */ static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -332,6 +388,15 @@ static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Valu mkBool(v, args[0]->type == tBool); } +static RegisterPrimOp primop_isBool({ + .name = "__isBool", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a bool, and `false` otherwise. + )", + .fun = prim_isBool, +}); + /* Determine whether the argument is a path. */ static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -339,6 +404,15 @@ static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Valu mkBool(v, args[0]->type == tPath); } +static RegisterPrimOp primop_isPath({ + .name = "__isPath", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a path, and `false` otherwise. + )", + .fun = prim_isPath, +}); + struct CompareValues { bool operator () (const Value * v1, const Value * v2) const @@ -459,14 +533,23 @@ static RegisterPrimOp primop_abort({ } }); - -static void prim_throw(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - throw ThrownError(s); -} - +static RegisterPrimOp primop_throw({ + .name = "throw", + .args = {"s"}, + .doc = R"( + Throw an error message *s*. This usually aborts Nix expression + evaluation, but in `nix-env -qa` and other commands that try to + evaluate a set of derivations to get information about those + derivations, a derivation that throws an error is silently skipped + (which is not the case for `abort`). + )", + .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + throw ThrownError(s); + } +}); static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -497,6 +580,22 @@ static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Val v.attrs->sort(); } +static RegisterPrimOp primop_tryEval({ + .name = "__tryEval", + .args = {"e"}, + .doc = R"( + Try to shallowly evaluate *e*. Return a set containing the + attributes `success` (`true` if *e* evaluated successfully, + `false` if an error was thrown) and `value`, equalling *e* if + successful and `false` otherwise. Note that this doesn't evaluate + *e* deeply, so ` let e = { x = throw ""; }; in (builtins.tryEval + e).success ` will be `true`. Using ` builtins.deepSeq ` one can + get the expected result: `let e = { x = throw ""; }; in + (builtins.tryEval (builtins.deepSeq e e)).success` will be + `false`. + )", + .fun = prim_tryEval, +}); /* Return an environment variable. Use with care. */ static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -505,6 +604,22 @@ static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Valu mkString(v, evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or("")); } +static RegisterPrimOp primop_getEnv({ + .name = "__getEnv", + .args = {"s"}, + .doc = R"( + `getEnv` returns the value of the environment variable *s*, or an + empty string if the variable doesn’t exist. This function should be + used with care, as it can introduce all sorts of nasty environment + dependencies in your Nix expression. + + `getEnv` is used in Nix Packages to locate the file + `~/.nixpkgs/config.nix`, which contains user-local settings for Nix + Packages. (That is, it does a `getEnv "HOME"` to locate the user’s + home directory.) + )", + .fun = prim_getEnv, +}); /* Evaluate the first argument, then return the second argument. */ static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -514,6 +629,15 @@ static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v = *args[1]; } +static RegisterPrimOp primop_seq({ + .name = "__seq", + .args = {"e1", "e2"}, + .doc = R"( + Evaluate *e1*, then evaluate and return *e2*. This ensures that a + computation is strict in the value of *e1*. + )", + .fun = prim_seq, +}); /* Evaluate the first argument deeply (i.e. recursing into lists and attrsets), then return the second argument. */ @@ -524,6 +648,16 @@ static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Val v = *args[1]; } +static RegisterPrimOp primop_deepSeq({ + .name = "__deepSeq", + .args = {"e1", "e2"}, + .doc = R"( + This is like `seq e1 e2`, except that *e1* is evaluated *deeply*: + if it’s a list or set, its elements or attributes are also + evaluated recursively. + )", + .fun = prim_deepSeq, +}); /* Evaluate the first expression and print it on standard error. Then return the second expression. Useful for debugging. */ @@ -538,6 +672,17 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value v = *args[1]; } +static RegisterPrimOp primop_trace({ + .name = "__trace", + .args = {"e1", "e2"}, + .doc = R"( + Evaluate *e1* and print its abstract syntax representation on + standard error. Then return *e2*. This function is useful for + debugging. + )", + .fun = prim_trace, +}); + /************************************************************* * Derivations @@ -879,6 +1024,17 @@ static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); } +static RegisterPrimOp primop_placeholder({ + .name = "placeholder", + .args = {"output"}, + .doc = R"( + Return a placeholder string for the specified *output* that will be + substituted by the corresponding output path at build time. Typical + outputs would be `"out"`, `"bin"` or `"dev"`. + )", + .fun = prim_placeholder, +}); + /************************************************************* * Paths @@ -893,6 +1049,15 @@ static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Valu mkString(v, canonPath(path), context); } +static RegisterPrimOp primop_toPath({ + .name = "__toPath", + .args = {"s"}, + .doc = R"( + DEPRECATED. Use `/. + "/path"` to convert a string into an absolute + path. For relative paths, use `./. + "/path"`. + )", + .fun = prim_toPath, +}); /* Allow a valid store path to be used in an expression. This is useful in some generated expressions such as in nix-push, which @@ -904,6 +1069,9 @@ static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Valu corner cases. */ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v) { + if (evalSettings.pureEval) + throw EvalError("builtins.storePath' is not allowed in pure evaluation mode"); + PathSet context; Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context)); /* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink @@ -949,6 +1117,15 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, } } +static RegisterPrimOp primop_pathExists({ + .name = "__pathExists", + .args = {"path"}, + .doc = R"( + Return `true` if the path *path* exists at evaluation time, and + `false` otherwise. + )", + .fun = prim_pathExists, +}); /* Return the base name of the given string, i.e., everything following the last slash. */ @@ -958,6 +1135,16 @@ static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), context); } +static RegisterPrimOp primop_baseNameOf({ + .name = "baseNameOf", + .args = {"s"}, + .doc = R"( + Return the *base name* of the string *s*, that is, everything + following the final slash in the string. This is similar to the GNU + `basename` command. + )", + .fun = prim_baseNameOf, +}); /* Return the directory of the given path, i.e., everything before the last slash. Return either a path or a string depending on the type @@ -969,6 +1156,16 @@ static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value if (args[0]->type == tPath) mkPath(v, dir.c_str()); else mkString(v, dir, context); } +static RegisterPrimOp primop_dirOf({ + .name = "dirOf", + .args = {"s"}, + .doc = R"( + Return the directory part of the string *s*, that is, everything + before the final slash in the string. This is similar to the GNU + `dirname` command. + )", + .fun = prim_dirOf, +}); /* Return the contents of a file as a string. */ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -989,6 +1186,14 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va mkString(v, s.c_str()); } +static RegisterPrimOp primop_readFile({ + .name = "__readFile", + .args = {"path"}, + .doc = R"( + Return the contents of the file *path* as a string. + )", + .fun = prim_readFile, +}); /* Find a file in the Nix search path. Used to implement <x> paths, which are desugared to 'findFile __nixPath "x"'. */ @@ -1051,6 +1256,17 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va mkString(v, hashFile(*ht, state.checkSourcePath(p)).to_string(Base16, false), context); } +static RegisterPrimOp primop_hashFile({ + .name = "__hashFile", + .args = {"type", "p"}, + .doc = R"( + Return a base-16 representation of the cryptographic hash of the + file at path *p*. The hash algorithm specified by *type* must be one + of `"md5"`, `"sha1"`, `"sha256"` or `"sha512"`. + )", + .fun = prim_hashFile, +}); + /* Read a directory (without . or ..) */ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1082,6 +1298,25 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val v.attrs->sort(); } +static RegisterPrimOp primop_readDir({ + .name = "__readDir", + .args = {"path"}, + .doc = R"( + Return the contents of the directory *path* as a set mapping + directory entries to the corresponding file type. For instance, if + directory `A` contains a regular file `B` and another directory + `C`, then `builtins.readDir ./A` will return the set + + ```nix + { B = "regular"; C = "directory"; } + ``` + + The possible values for the file type are `"regular"`, + `"directory"`, `"symlink"` and `"unknown"`. + )", + .fun = prim_readDir, +}); + /************************************************************* * Creating files @@ -1099,6 +1334,102 @@ static void prim_toXML(EvalState & state, const Pos & pos, Value * * args, Value mkString(v, out.str(), context); } +static RegisterPrimOp primop_toXML({ + .name = "__toXML", + .args = {"e"}, + .doc = R"( + Return a string containing an XML representation of *e*. The main + application for `toXML` is to communicate information with the + builder in a more structured format than plain environment + variables. + + Here is an example where this is the case: + + ```nix + { stdenv, fetchurl, libxslt, jira, uberwiki }: + + stdenv.mkDerivation (rec { + name = "web-server"; + + buildInputs = [ libxslt ]; + + builder = builtins.toFile "builder.sh" " + source $stdenv/setup + mkdir $out + echo "$servlets" | xsltproc ${stylesheet} - > $out/server-conf.xml ① + "; + + stylesheet = builtins.toFile "stylesheet.xsl" ② + "<?xml version='1.0' encoding='UTF-8'?> + <xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'> + <xsl:template match='/'> + <Configure> + <xsl:for-each select='/expr/list/attrs'> + <Call name='addWebApplication'> + <Arg><xsl:value-of select=\"attr[@name = 'path']/string/@value\" /></Arg> + <Arg><xsl:value-of select=\"attr[@name = 'war']/path/@value\" /></Arg> + </Call> + </xsl:for-each> + </Configure> + </xsl:template> + </xsl:stylesheet> + "; + + servlets = builtins.toXML [ ③ + { path = "/bugtracker"; war = jira + "/lib/atlassian-jira.war"; } + { path = "/wiki"; war = uberwiki + "/uberwiki.war"; } + ]; + }) + ``` + + The builder is supposed to generate the configuration file for a + [Jetty servlet container](http://jetty.mortbay.org/). A servlet + container contains a number of servlets (`*.war` files) each + exported under a specific URI prefix. So the servlet configuration + is a list of sets containing the `path` and `war` of the servlet + (①). This kind of information is difficult to communicate with the + normal method of passing information through an environment + variable, which just concatenates everything together into a + string (which might just work in this case, but wouldn’t work if + fields are optional or contain lists themselves). Instead the Nix + expression is converted to an XML representation with `toXML`, + which is unambiguous and can easily be processed with the + appropriate tools. For instance, in the example an XSLT stylesheet + (at point ②) is applied to it (at point ①) to generate the XML + configuration file for the Jetty server. The XML representation + produced at point ③ by `toXML` is as follows: + + ```xml + <?xml version='1.0' encoding='utf-8'?> + <expr> + <list> + <attrs> + <attr name="path"> + <string value="/bugtracker" /> + </attr> + <attr name="war"> + <path value="/nix/store/d1jh9pasa7k2...-jira/lib/atlassian-jira.war" /> + </attr> + </attrs> + <attrs> + <attr name="path"> + <string value="/wiki" /> + </attr> + <attr name="war"> + <path value="/nix/store/y6423b1yi4sx...-uberwiki/uberwiki.war" /> + </attr> + </attrs> + </list> + </expr> + ``` + + Note that we used the `toFile` built-in to write the builder and + the stylesheet “inline” in the Nix expression. The path of the + stylesheet is spliced into the builder using the syntax `xsltproc + ${stylesheet}`. + )", + .fun = prim_toXML, +}); /* Convert the argument (which can be any Nix expression) to a JSON string. Not all Nix expressions can be sensibly or completely @@ -1111,6 +1442,19 @@ static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Valu mkString(v, out.str(), context); } +static RegisterPrimOp primop_toJSON({ + .name = "__toJSON", + .args = {"e"}, + .doc = R"( + Return a string containing a JSON representation of *e*. Strings, + integers, floats, booleans, nulls and lists are mapped to their JSON + equivalents. Sets (except derivations) are represented as objects. + Derivations are translated to a JSON string containing the + derivation’s output path. Paths are copied to the store and + represented as a JSON string of the resulting store path. + )", + .fun = prim_toJSON, +}); /* Parse a JSON string to a value. */ static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1119,6 +1463,20 @@ static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Va parseJSON(state, s, v); } +static RegisterPrimOp primop_fromJSON({ + .name = "__fromJSON", + .args = {"e"}, + .doc = R"( + Convert a JSON string to a Nix value. For example, + + ```nix + builtins.fromJSON ''{"x": [1, 2, 3], "y": null}'' + ``` + + returns the value `{ x = [ 1 2 3 ]; y = null; }`. + )", + .fun = prim_fromJSON, +}); /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ @@ -1153,6 +1511,83 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu mkString(v, storePath, {storePath}); } +static RegisterPrimOp primop_toFile({ + .name = "__toFile", + .args = {"name", "s"}, + .doc = R"( + Store the string *s* in a file in the Nix store and return its + path. The file has suffix *name*. This file can be used as an + input to derivations. One application is to write builders + “inline”. For instance, the following Nix expression combines the + [Nix expression for GNU Hello](expression-syntax.md) and its + [build script](build-script.md) into one file: + + ```nix + { stdenv, fetchurl, perl }: + + stdenv.mkDerivation { + name = "hello-2.1.1"; + + builder = builtins.toFile "builder.sh" " + source $stdenv/setup + + PATH=$perl/bin:$PATH + + tar xvfz $src + cd hello-* + ./configure --prefix=$out + make + make install + "; + + src = fetchurl { + url = "http://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz"; + sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465"; + }; + inherit perl; + } + ``` + + It is even possible for one file to refer to another, e.g., + + ```nix + builder = let + configFile = builtins.toFile "foo.conf" " + # This is some dummy configuration file. + ... + "; + in builtins.toFile "builder.sh" " + source $stdenv/setup + ... + cp ${configFile} $out/etc/foo.conf + "; + ``` + + Note that `${configFile}` is an + [antiquotation](language-values.md), so the result of the + expression `configFile` + (i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be + spliced into the resulting string. + + It is however *not* allowed to have files mutually referring to each + other, like so: + + ```nix + let + foo = builtins.toFile "foo" "...${bar}..."; + bar = builtins.toFile "bar" "...${foo}..."; + in foo + ``` + + This is not allowed because it would cause a cyclic dependency in + the computation of the cryptographic hashes for `foo` and `bar`. + + It is also not possible to reference the result of a derivation. If + you are using Nixpkgs, the `writeTextFile` function is able to do + that. + )", + .fun = prim_toFile, +}); static void addPath(EvalState & state, const Pos & pos, const string & name, const Path & path_, Value * filterFun, FileIngestionMethod method, const std::optional<Hash> expectedHash, Value & v) @@ -1223,6 +1658,48 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v); } +static RegisterPrimOp primop_filterSource({ + .name = "__filterSource", + .args = {"e1", "e2"}, + .doc = R"( + This function allows you to copy sources into the Nix store while + filtering certain files. For instance, suppose that you want to use + the directory `source-dir` as an input to a Nix expression, e.g. + + ```nix + stdenv.mkDerivation { + ... + src = ./source-dir; + } + ``` + + However, if `source-dir` is a Subversion working copy, then all + those annoying `.svn` subdirectories will also be copied to the + store. Worse, the contents of those directories may change a lot, + causing lots of spurious rebuilds. With `filterSource` you can + filter out the `.svn` directories: + + ```nix + src = builtins.filterSource + (path: type: type != "directory" || baseNameOf path != ".svn") + ./source-dir; + ``` + + Thus, the first argument *e1* must be a predicate function that is + called for each regular file, directory or symlink in the source + tree *e2*. If the function returns `true`, the file is copied to the + Nix store, otherwise it is omitted. The function is called with two + arguments. The first is the full path of the file. The second is a + string that identifies the type of the file, which is either + `"regular"`, `"directory"`, `"symlink"` or `"unknown"` (for other + kinds of files such as device nodes or fifos — but note that those + cannot be copied to the Nix store, so if the predicate returns + `true` for them, the copy will fail). If you exclude a directory, + the entire corresponding subtree of *e2* will be excluded. + )", + .fun = prim_filterSource, +}); + static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -1268,6 +1745,41 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value addPath(state, pos, name, path, filterFun, method, expectedHash, v); } +static RegisterPrimOp primop_path({ + .name = "__path", + .args = {"args"}, + .doc = R"( + An enrichment of the built-in path type, based on the attributes + present in *args*. All are optional except `path`: + + - path + The underlying path. + + - name + The name of the path when added to the store. This can used to + reference paths that have nix-illegal characters in their names, + like `@`. + + - filter + A function of the type expected by `builtins.filterSource`, + with the same semantics. + + - recursive + When `false`, when `path` is added to the store it is with a + flat hash, rather than a hash of the NAR serialization of the + file. Thus, `path` must refer to a regular file, not a + directory. This allows similar behavior to `fetchurl`. Defaults + to `true`. + + - sha256 + When provided, this is the expected hash of the file at the + path. Evaluation will fail if the hash is incorrect, and + providing a hash allows `builtins.path` to be used even when the + `pure-eval` nix config option is on. + )", + .fun = prim_path, +}); + /************************************************************* * Sets @@ -1290,6 +1802,16 @@ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, V [](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; }); } +static RegisterPrimOp primop_attrNames({ + .name = "__attrNames", + .args = {"set"}, + .doc = R"( + Return the names of the attributes in the set *set* in an + alphabetically sorted list. For instance, `builtins.attrNames { y + = 1; x = "foo"; }` evaluates to `[ "x" "y" ]`. + )", + .fun = prim_attrNames, +}); /* Return the values of the attributes in a set as a list, in the same order as attrNames. */ @@ -1310,6 +1832,15 @@ static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, v.listElems()[i] = ((Attr *) v.listElems()[i])->value; } +static RegisterPrimOp primop_attrValues({ + .name = "__attrValues", + .args = {"set"}, + .doc = R"( + Return the values of the attributes in the set *set* in the order + corresponding to the sorted attribute names. + )", + .fun = prim_attrValues, +}); /* Dynamic version of the `.' operator. */ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1329,9 +1860,20 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) v = *i->value; } +static RegisterPrimOp primop_getAttr({ + .name = "__getAttr", + .args = {"s", "set"}, + .doc = R"( + `getAttr` returns the attribute named *s* from *set*. Evaluation + aborts if the attribute doesn’t exist. This is a dynamic version of + the `.` operator, since *s* is an expression rather than an + identifier. + )", + .fun = prim_getAttr, +}); /* Return position information of the specified attribute. */ -void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) { string attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -1342,7 +1884,6 @@ void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, V state.mkPos(v, i->pos); } - /* Dynamic version of the `?' operator. */ static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1351,6 +1892,16 @@ static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Val mkBool(v, args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end()); } +static RegisterPrimOp primop_hasAttr({ + .name = "__hasAttr", + .args = {"s", "set"}, + .doc = R"( + `hasAttr` returns `true` if *set* has an attribute named *s*, and + `false` otherwise. This is a dynamic version of the `?` operator, + since *s* is an expression rather than an identifier. + )", + .fun = prim_hasAttr, +}); /* Determine whether the argument is a set. */ static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1359,6 +1910,14 @@ static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Val mkBool(v, args[0]->type == tAttrs); } +static RegisterPrimOp primop_isAttrs({ + .name = "__isAttrs", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a set, and `false` otherwise. + )", + .fun = prim_isAttrs, +}); static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1382,6 +1941,21 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, } } +static RegisterPrimOp primop_removeAttrs({ + .name = "removeAttrs", + .args = {"set", "list"}, + .doc = R"( + Remove the attributes listed in *list* from *set*. The attributes + don’t have to exist in *set*. For instance, + + ```nix + removeAttrs { x = 1; y = 2; z = 3; } [ "a" "x" "z" ] + ``` + + evaluates to `{ y = 2; }`. + )", + .fun = prim_removeAttrs, +}); /* Builds a set from a list specifying (name, value) pairs. To be precise, a list [{name = "name1"; value = value1;} ... {name = @@ -1423,6 +1997,30 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, v.attrs->sort(); } +static RegisterPrimOp primop_listToAttrs({ + .name = "__listToAttrs", + .args = {"e"}, + .doc = R"( + Construct a set from a list specifying the names and values of each + attribute. Each element of the list should be a set consisting of a + string-valued attribute `name` specifying the name of the attribute, + and an attribute `value` specifying its value. Example: + + ```nix + builtins.listToAttrs + [ { name = "foo"; value = 123; } + { name = "bar"; value = 456; } + ] + ``` + + evaluates to + + ```nix + { foo = 123; bar = 456; } + ``` + )", + .fun = prim_listToAttrs, +}); /* Return the right-biased intersection of two sets as1 and as2, i.e. a set that contains every attribute from as2 that is also a @@ -1441,6 +2039,15 @@ static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * ar } } +static RegisterPrimOp primop_intersectAttrs({ + .name = "__intersectAttrs", + .args = {"e1", "e2"}, + .doc = R"( + Return a set consisting of the attributes in the set *e2* that also + exist in the set *e1*. + )", + .fun = prim_intersectAttrs, +}); /* Collect each attribute named `attr' from a list of attribute sets. Sets that don't contain the named attribute are ignored. @@ -1470,7 +2077,6 @@ static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Va v.listElems()[n] = res[n]; } - /* 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 the corresponding argument has a default value. For instance, @@ -1508,6 +2114,22 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args v.attrs->sort(); } +static RegisterPrimOp primop_functionArgs({ + .name = "__functionArgs", + .args = {"f"}, + .doc = R"( + 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 the corresponding argument 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: + ...) = { }`. + )", + .fun = prim_functionArgs, +}); /* Apply a function to every element of an attribute set. */ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1526,7 +2148,6 @@ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Va } - /************************************************************* * Lists *************************************************************/ @@ -1539,6 +2160,14 @@ static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Valu mkBool(v, args[0]->isList()); } +static RegisterPrimOp primop_isList({ + .name = "__isList", + .args = {"e"}, + .doc = R"( + Return `true` if *e* evaluates to a list, and `false` otherwise. + )", + .fun = prim_isList, +}); static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) { @@ -1552,13 +2181,21 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu v = *list.listElems()[n]; } - /* Return the n-1'th element of a list. */ static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v) { elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); } +static RegisterPrimOp primop_elemAt({ + .name = "__elemAt", + .args = {"xs", "n"}, + .doc = R"( + Return element *n* from the list *xs*. Elements are counted starting + from 0. A fatal error occurs if the index is out of bounds. + )", + .fun = prim_elemAt, +}); /* Return the first element of a list. */ static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1566,6 +2203,16 @@ static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value elemAt(state, pos, *args[0], 0, v); } +static RegisterPrimOp primop_head({ + .name = "__head", + .args = {"list"}, + .doc = R"( + Return the first element of a list; abort evaluation if the argument + isn’t a list or is an empty list. You can test whether a list is + empty by comparing it with `[]`. + )", + .fun = prim_head, +}); /* Return a list consisting of everything but the first element of a list. Warning: this function takes O(n) time, so you probably @@ -1584,6 +2231,21 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value v.listElems()[n] = args[0]->listElems()[n + 1]; } +static RegisterPrimOp primop_tail({ + .name = "__tail", + .args = {"list"}, + .doc = R"( + Return the second to last elements of a list; abort evaluation if + the argument isn’t a list or is an empty list. + + > **Warning** + > + > This function should generally be avoided since it's inefficient: + > unlike Haskell's `tail`, it takes O(n) time, so recursing over a + > list by repeatedly calling `tail` takes O(n^2) time. + )", + .fun = prim_tail, +}); /* Apply a function to every element of a list. */ static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1597,6 +2259,21 @@ static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & *args[0], *args[1]->listElems()[n]); } +static RegisterPrimOp primop_map({ + .name = "map", + .args = {"f", "list"}, + .doc = R"( + Apply the function *f* to each element in the list *list*. For + example, + + ```nix + map (x: "foo" + x) [ "bar" "bla" "abc" ] + ``` + + evaluates to `[ "foobar" "foobla" "fooabc" ]`. + )", + .fun = prim_map, +}); /* Filter a list using a predicate; that is, return a list containing every element from the list for which the predicate function @@ -1628,6 +2305,15 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu } } +static RegisterPrimOp primop_filter({ + .name = "__filter", + .args = {"f", "list"}, + .doc = R"( + Return a list consisting of the elements of *list* for which the + function *f* returns `true`. + )", + .fun = prim_filter, +}); /* Return true if a list contains a given element. */ static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1642,6 +2328,15 @@ static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value mkBool(v, res); } +static RegisterPrimOp primop_elem({ + .name = "__elem", + .args = {"x", "xs"}, + .doc = R"( + Return `true` if a value equal to *x* occurs in the list *xs*, and + `false` otherwise. + )", + .fun = prim_elem, +}); /* Concatenate a list of lists. */ static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1650,6 +2345,14 @@ static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); } +static RegisterPrimOp primop_concatLists({ + .name = "__concatLists", + .args = {"lists"}, + .doc = R"( + Concatenate a list of lists into a single list. + )", + .fun = prim_concatLists, +}); /* Return the length of a list. This is an O(1) time operation. */ static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1658,6 +2361,14 @@ static void prim_length(EvalState & state, const Pos & pos, Value * * args, Valu mkInt(v, args[0]->listSize()); } +static RegisterPrimOp primop_length({ + .name = "__length", + .args = {"e"}, + .doc = R"( + Return the length of the list *e*. + )", + .fun = prim_length, +}); /* Reduce a list by applying a binary operator, from left to right. The operator is applied strictly. */ @@ -1682,6 +2393,18 @@ static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, } } +static RegisterPrimOp primop_foldlStrict({ + .name = "__foldl'", + .args = {"op", "nul", "list"}, + .doc = R"( + Reduce a list by applying a binary operator, from left to right, + e.g. `foldl’ op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2) + ...`. The operator is applied strictly, i.e., its arguments are + evaluated first. For example, `foldl’ (x: y: x + y) 0 [1 2 3]` + evaluates to 6. + )", + .fun = prim_foldlStrict, +}); static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1707,12 +2430,30 @@ static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & anyOrAll(true, state, pos, args, v); } +static RegisterPrimOp primop_any({ + .name = "__any", + .args = {"pred", "list"}, + .doc = R"( + Return `true` if the function *pred* returns `true` for at least one + element of *list*, and `false` otherwise. + )", + .fun = prim_any, +}); static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v) { anyOrAll(false, state, pos, args, v); } +static RegisterPrimOp primop_all({ + .name = "__all", + .args = {"pred", "list"}, + .doc = R"( + Return `true` if the function *pred* returns `true` for all elements + of *list*, and `false` otherwise. + )", + .fun = prim_all, +}); static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1733,6 +2474,21 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val } } +static RegisterPrimOp primop_genList({ + .name = "__genList", + .args = {"generator", "length"}, + .doc = R"( + Generate list of size *length*, with each element *i* equal to the + value returned by *generator* `i`. For example, + + ```nix + builtins.genList (x: x * x) 5 + ``` + + returns the list `[ 0 1 4 9 16 ]`. + )", + .fun = prim_genList, +}); static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v); @@ -1768,6 +2524,26 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value std::stable_sort(v.listElems(), v.listElems() + len, comparator); } +static RegisterPrimOp primop_sort({ + .name = "__sort", + .args = {"comparator", "list"}, + .doc = R"( + Return *list* in sorted order. It repeatedly calls the function + *comparator* with two elements. The comparator should return `true` + if the first element is less than the second, and `false` otherwise. + For example, + + ```nix + builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ] + ``` + + produces the list `[ 42 77 147 249 483 526 ]`. + + This is a stable sort: it preserves the relative order of elements + deemed equal by the comparator. + )", + .fun = prim_sort, +}); static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1851,6 +2627,14 @@ static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_add({ + .name = "__add", + .args = {"e1", "e2"}, + .doc = R"( + Return the sum of the numbers *e1* and *e2*. + )", + .fun = prim_add, +}); static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1862,6 +2646,14 @@ static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_sub({ + .name = "__sub", + .args = {"e1", "e2"}, + .doc = R"( + Return the difference between the numbers *e1* and *e2*. + )", + .fun = prim_sub, +}); static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1873,6 +2665,14 @@ static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_mul({ + .name = "__mul", + .args = {"e1", "e2"}, + .doc = R"( + Return the product of the numbers *e1* and *e2*. + )", + .fun = prim_mul, +}); static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1902,21 +2702,57 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & } } +static RegisterPrimOp primop_div({ + .name = "__div", + .args = {"e1", "e2"}, + .doc = R"( + Return the quotient of the numbers *e1* and *e2*. + )", + .fun = prim_div, +}); + static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v) { mkInt(v, state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_bitAnd({ + .name = "__bitAnd", + .args = {"e1", "e2"}, + .doc = R"( + Return the bitwise AND of the integers *e1* and *e2*. + )", + .fun = prim_bitAnd, +}); + static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v) { mkInt(v, state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_bitOr({ + .name = "__bitOr", + .args = {"e1", "e2"}, + .doc = R"( + Return the bitwise OR of the integers *e1* and *e2*. + )", + .fun = prim_bitOr, +}); + static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v) { mkInt(v, state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); } +static RegisterPrimOp primop_bitXor({ + .name = "__bitXor", + .args = {"e1", "e2"}, + .doc = R"( + Return the bitwise XOR of the integers *e1* and *e2*. + )", + .fun = prim_bitXor, +}); + static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); @@ -1925,6 +2761,17 @@ static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Va mkBool(v, comp(args[0], args[1])); } +static RegisterPrimOp primop_lessThan({ + .name = "__lessThan", + .args = {"e1", "e2"}, + .doc = R"( + Return `true` if the number *e1* is less than the number *e2*, and + `false` otherwise. Evaluation aborts if either *e1* or *e2* does not + evaluate to a number. + )", + .fun = prim_lessThan, +}); + /************************************************************* * String manipulation @@ -1941,6 +2788,29 @@ static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Va mkString(v, s, context); } +static RegisterPrimOp primop_toString({ + .name = "toString", + .args = {"e"}, + .doc = R"( + Convert the expression *e* to a string. *e* can be: + + - A string (in which case the string is returned unmodified). + + - A path (e.g., `toString /foo/bar` yields `"/foo/bar"`. + + - A set containing `{ __toString = self: ...; }`. + + - An integer. + + - A list, in which case the string representations of its elements + are joined with spaces. + + - A Boolean (`false` yields `""`, `true` yields `"1"`). + + - `null`, which yields the empty string. + )", + .fun = prim_toString, +}); /* `substring start len str' returns the substring of `str' starting at character position `min(start, stringLength str)' inclusive and @@ -1962,6 +2832,25 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context); } +static RegisterPrimOp primop_substring({ + .name = "__substring", + .args = {"start", "len", "s"}, + .doc = R"( + Return the substring of *s* from character position *start* + (zero-based) up to but not including *start + len*. If *start* is + greater than the length of the string, an empty string is returned, + and if *start + len* lies beyond the end of the string, only the + substring up to the end of the string is returned. *start* must be + non-negative. For example, + + ```nix + builtins.substring 0 3 "nixos" + ``` + + evaluates to `"nix"`. + )", + .fun = prim_substring, +}); static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -1970,6 +2859,15 @@ static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args mkInt(v, s.size()); } +static RegisterPrimOp primop_stringLength({ + .name = "__stringLength", + .args = {"e"}, + .doc = R"( + Return the length of the string *e*. If *e* is not a string, + evaluation is aborted. + )", + .fun = prim_stringLength, +}); /* Return the cryptographic hash of a string in base-16. */ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1988,6 +2886,16 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, mkString(v, hashString(*ht, s).to_string(Base16, false), context); } +static RegisterPrimOp primop_hashString({ + .name = "__hashString", + .args = {"type", "s"}, + .doc = R"( + Return a base-16 representation of the cryptographic hash of string + *s*. The hash algorithm specified by *type* must be one of `"md5"`, + `"sha1"`, `"sha256"` or `"sha512"`. + )", + .fun = prim_hashString, +}); /* Match a regular expression against a string and return either ‘null’ or a list containing substring matches. */ @@ -2036,6 +2944,41 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) } } +static RegisterPrimOp primop_match({ + .name = "__match", + .args = {"regex", "str"}, + .doc = R"s( + Returns a list if the [extended POSIX regular + expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04) + *regex* matches *str* precisely, otherwise returns `null`. Each item + in the list is a regex group. + + ```nix + builtins.match "ab" "abc" + ``` + + Evaluates to `null`. + + ```nix + builtins.match "abc" "abc" + ``` + + Evaluates to `[ ]`. + + ```nix + builtins.match "a(b)(c)" "abc" + ``` + + Evaluates to `[ "b" "c" ]`. + + ```nix + builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " FOO " + ``` + + Evaluates to `[ "foo" ]`. + )s", + .fun = prim_match, +}); /* Split a string with a regular expression, and return a list of the non-matching parts interleaved by the lists of the matching groups. */ @@ -2109,8 +3052,44 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value } } +static RegisterPrimOp primop_split({ + .name = "__split", + .args = {"regex", "str"}, + .doc = R"s( + Returns a list composed of non matched strings interleaved with the + lists of the [extended POSIX regular + expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04) + *regex* matches of *str*. Each item in the lists of matched + sequences is a regex group. + + ```nix + builtins.split "(a)b" "abc" + ``` + + Evaluates to `[ "" [ "a" ] "c" ]`. + + ```nix + builtins.split "([ac])" "abc" + ``` + + Evaluates to `[ "" [ "a" ] "b" [ "c" ] "" ]`. + + ```nix + builtins.split "(a)|(c)" "abc" + ``` + + Evaluates to `[ "" [ "a" null ] "b" [ null "c" ] "" ]`. -static void prim_concatStringSep(EvalState & state, const Pos & pos, Value * * args, Value & v) + ```nix + builtins.split "([[:upper:]]+)" " FOO " + ``` + + Evaluates to `[ " " [ "FOO" ] " " ]`. + )s", + .fun = prim_split, +}); + +static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; @@ -2129,6 +3108,16 @@ static void prim_concatStringSep(EvalState & state, const Pos & pos, Value * * a mkString(v, res, context); } +static RegisterPrimOp primop_concatStringsSep({ + .name = "__concatStringsSep", + .args = {"separator", "list"}, + .doc = R"( + Concatenate a list of strings with a separator between each + element, e.g. `concatStringsSep "/" ["usr" "local" "bin"] == + "usr/local/bin"`. + )", + .fun = prim_concatStringsSep, +}); static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -2188,6 +3177,22 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar mkString(v, res, context); } +static RegisterPrimOp primop_replaceStrings({ + .name = "__replaceStrings", + .args = {"from", "to", "s"}, + .doc = R"( + Given string *s*, replace every occurrence of the strings in *from* + with the corresponding string in *to*. For example, + + ```nix + builtins.replaceStrings ["oo" "a"] ["a" "i"] "foobar" + ``` + + evaluates to `"fabir"`. + )", + .fun = prim_replaceStrings, +}); + /************************************************************* * Versions @@ -2204,6 +3209,19 @@ static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args v.attrs->sort(); } +static RegisterPrimOp primop_parseDrvName({ + .name = "__parseDrvName", + .args = {"s"}, + .doc = R"( + Split the string *s* into a package name and version. The package + name is everything up to but not including the first dash followed + by a digit, and the version is everything following that dash. The + result is returned in a set `{ name, version }`. Thus, + `builtins.parseDrvName "nix-0.12pre12876"` returns `{ name = + "nix"; version = "0.12pre12876"; }`. + )", + .fun = prim_parseDrvName, +}); static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -2212,6 +3230,18 @@ static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * a mkInt(v, compareVersions(version1, version2)); } +static RegisterPrimOp primop_compareVersions({ + .name = "__compareVersions", + .args = {"s1", "s2"}, + .doc = R"( + Compare two strings representing versions and return `-1` if + version *s1* is older than version *s2*, `0` if they are the same, + and `1` if *s1* is newer than *s2*. The version comparison + algorithm is the same as the one used by [`nix-env + -u`](../command-ref/nix-env.md#operation---upgrade). + )", + .fun = prim_compareVersions, +}); static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -2232,6 +3262,17 @@ static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args } } +static RegisterPrimOp primop_splitVersion({ + .name = "__splitVersion", + .args = {"s"}, + .doc = R"( + Split a string representing a version into its components, by the + same version splitting logic underlying the version comparison in + [`nix-env -u`](../command-ref/nix-env.md#operation---upgrade). + )", + .fun = prim_splitVersion, +}); + /************************************************************* * Primop registration @@ -2282,15 +3323,6 @@ void EvalState::createBaseEnv() mkNull(v); addConstant("null", v); - auto vThrow = addPrimOp("throw", 1, prim_throw); - - auto addPurityError = [&](const std::string & name) { - Value * v2 = allocValue(); - mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name)); - mkApp(v, *vThrow, *v2); - addConstant(name, v); - }; - if (!evalSettings.pureEval) { mkInt(v, time(0)); addConstant("__currentTime", v); @@ -2325,115 +3357,24 @@ void EvalState::createBaseEnv() addPrimOp("__importNative", 2, prim_importNative); addPrimOp("__exec", 1, prim_exec); } - addPrimOp("__typeOf", 1, prim_typeOf); - addPrimOp("isNull", 1, prim_isNull); - addPrimOp("__isFunction", 1, prim_isFunction); - addPrimOp("__isString", 1, prim_isString); - addPrimOp("__isInt", 1, prim_isInt); - addPrimOp("__isFloat", 1, prim_isFloat); - addPrimOp("__isBool", 1, prim_isBool); - addPrimOp("__isPath", 1, prim_isPath); addPrimOp("__genericClosure", 1, prim_genericClosure); addPrimOp("__addErrorContext", 2, prim_addErrorContext); - addPrimOp("__tryEval", 1, prim_tryEval); - addPrimOp("__getEnv", 1, prim_getEnv); - - // Strictness - addPrimOp("__seq", 2, prim_seq); - addPrimOp("__deepSeq", 2, prim_deepSeq); - - // Debugging - addPrimOp("__trace", 2, prim_trace); // Paths - addPrimOp("__toPath", 1, prim_toPath); - if (evalSettings.pureEval) - addPurityError("__storePath"); - else - addPrimOp("__storePath", 1, prim_storePath); - addPrimOp("__pathExists", 1, prim_pathExists); - addPrimOp("baseNameOf", 1, prim_baseNameOf); - addPrimOp("dirOf", 1, prim_dirOf); - addPrimOp("__readFile", 1, prim_readFile); - addPrimOp("__readDir", 1, prim_readDir); + addPrimOp("__storePath", 1, prim_storePath); addPrimOp("__findFile", 2, prim_findFile); - addPrimOp("__hashFile", 2, prim_hashFile); - - // Creating files - addPrimOp("__toXML", 1, prim_toXML); - addPrimOp("__toJSON", 1, prim_toJSON); - addPrimOp("__fromJSON", 1, prim_fromJSON); - addPrimOp("__toFile", 2, prim_toFile); - addPrimOp("__filterSource", 2, prim_filterSource); - addPrimOp("__path", 1, prim_path); // Sets - addPrimOp("__attrNames", 1, prim_attrNames); - addPrimOp("__attrValues", 1, prim_attrValues); - addPrimOp("__getAttr", 2, prim_getAttr); addPrimOp("__unsafeGetAttrPos", 2, prim_unsafeGetAttrPos); - addPrimOp("__hasAttr", 2, prim_hasAttr); - addPrimOp("__isAttrs", 1, prim_isAttrs); - addPrimOp("removeAttrs", 2, prim_removeAttrs); - addPrimOp("__listToAttrs", 1, prim_listToAttrs); - addPrimOp("__intersectAttrs", 2, prim_intersectAttrs); addPrimOp("__catAttrs", 2, prim_catAttrs); - addPrimOp("__functionArgs", 1, prim_functionArgs); addPrimOp("__mapAttrs", 2, prim_mapAttrs); // Lists - addPrimOp("__isList", 1, prim_isList); - addPrimOp("__elemAt", 2, prim_elemAt); - addPrimOp("__head", 1, prim_head); - addPrimOp("__tail", 1, prim_tail); - addPrimOp("map", 2, prim_map); - addPrimOp("__filter", 2, prim_filter); - addPrimOp("__elem", 2, prim_elem); - addPrimOp("__concatLists", 1, prim_concatLists); - addPrimOp("__length", 1, prim_length); - addPrimOp("__foldl'", 3, prim_foldlStrict); - addPrimOp("__any", 2, prim_any); - addPrimOp("__all", 2, prim_all); - addPrimOp("__genList", 2, prim_genList); - addPrimOp("__sort", 2, prim_sort); addPrimOp("__partition", 2, prim_partition); addPrimOp("__concatMap", 2, prim_concatMap); - // Integer arithmetic - addPrimOp("__add", 2, prim_add); - addPrimOp("__sub", 2, prim_sub); - addPrimOp("__mul", 2, prim_mul); - addPrimOp("__div", 2, prim_div); - addPrimOp("__bitAnd", 2, prim_bitAnd); - addPrimOp("__bitOr", 2, prim_bitOr); - addPrimOp("__bitXor", 2, prim_bitXor); - addPrimOp("__lessThan", 2, prim_lessThan); - - // String manipulation - addPrimOp("toString", 1, prim_toString); - addPrimOp("__substring", 3, prim_substring); - addPrimOp("__stringLength", 1, prim_stringLength); - addPrimOp("__hashString", 2, prim_hashString); - addPrimOp("__match", 2, prim_match); - addPrimOp("__split", 2, prim_split); - addPrimOp("__concatStringsSep", 2, prim_concatStringSep); - addPrimOp("__replaceStrings", 3, prim_replaceStrings); - - // Versions - addPrimOp("__parseDrvName", 1, prim_parseDrvName); - addPrimOp("__compareVersions", 2, prim_compareVersions); - addPrimOp("__splitVersion", 1, prim_splitVersion); - // Derivations addPrimOp("derivationStrict", 1, prim_derivationStrict); - addPrimOp("placeholder", 1, prim_placeholder); - - /* Add a wrapper around the derivation primop that computes the - `drvPath' and `outPath' attributes lazily. */ - string path = canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true); - sDerivationNix = symbols.create(path); - evalFile(path, v); - addConstant("derivation", v); /* Add a value containing the current Nix expression search path. */ mkList(v, searchPath.size()); @@ -2458,6 +3399,13 @@ void EvalState::createBaseEnv() .doc = primOp.doc, }); + /* Add a wrapper around the derivation primop that computes the + `drvPath' and `outPath' attributes lazily. */ + string path = canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true); + sDerivationNix = symbols.create(path); + evalFile(path, v); + addConstant("derivation", v); + /* Now that we've added all primops, sort the `builtins' set, because attribute lookups expect it to be sorted. */ baseEnv.values[0]->attrs->sort(); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 0dbf4ae1d..06e8304b8 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -226,18 +226,187 @@ static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Va fetch(state, pos, args, v, "fetchurl", false, ""); } +static RegisterPrimOp primop_fetchurl({ + .name = "__fetchurl", + .args = {"url"}, + .doc = R"( + Download the specified URL and return the path of the downloaded + file. This function is not available if [restricted evaluation + mode](../command-ref/conf-file.md) is enabled. + )", + .fun = prim_fetchurl, +}); + static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v) { fetch(state, pos, args, v, "fetchTarball", true, "source"); } +static RegisterPrimOp primop_fetchTarball({ + .name = "fetchTarball", + .args = {"args"}, + .doc = R"( + Download the specified URL, unpack it and return the path of the + unpacked tree. The file must be a tape archive (`.tar`) compressed + with `gzip`, `bzip2` or `xz`. The top-level path component of the + files in the tarball is removed, so it is best if the tarball + contains a single directory at top level. The typical use of the + function is to obtain external Nix expression dependencies, such as + a particular version of Nixpkgs, e.g. + + ```nix + with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz) {}; + + stdenv.mkDerivation { … } + ``` + + The fetched tarball is cached for a certain amount of time (1 hour + by default) in `~/.cache/nix/tarballs/`. You can change the cache + timeout either on the command line with `--option tarball-ttl number + of seconds` or in the Nix configuration file with this option: ` + number of seconds to cache `. + + Note that when obtaining the hash with ` nix-prefetch-url ` the + option `--unpack` is required. + + This function can also verify the contents against a hash. In that + case, the function takes a set instead of a URL. The set requires + the attribute `url` and the attribute `sha256`, e.g. + + ```nix + with import (fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz"; + sha256 = "1jppksrfvbk5ypiqdz4cddxdl8z6zyzdb2srq8fcffr327ld5jj2"; + }) {}; + + stdenv.mkDerivation { … } + ``` + + This function is not available if [restricted evaluation + mode](../command-ref/conf-file.md) is enabled. + )", + .fun = prim_fetchTarball, +}); + static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v) { fetchTree(state, pos, args, v, "git", true); } -static RegisterPrimOp r2("__fetchurl", 1, prim_fetchurl); -static RegisterPrimOp r3("fetchTarball", 1, prim_fetchTarball); -static RegisterPrimOp r4("fetchGit", 1, prim_fetchGit); +static RegisterPrimOp primop_fetchGit({ + .name = "fetchGit", + .args = {"args"}, + .doc = R"( + Fetch a path from git. *args* can be a URL, in which case the HEAD + of the repo at that URL is fetched. Otherwise, it can be an + attribute with the following attributes (all except `url` optional): + + - url + The URL of the repo. + + - name + The name of the directory the repo should be exported to in the + store. Defaults to the basename of the URL. + + - rev + The git revision to fetch. Defaults to the tip of `ref`. + + - ref + The git ref to look for the requested revision under. This is + often a branch or tag name. Defaults to `HEAD`. + + By default, the `ref` value is prefixed with `refs/heads/`. As + of Nix 2.3.0 Nix will not prefix `refs/heads/` if `ref` starts + with `refs/`. + + - submodules + A Boolean parameter that specifies whether submodules should be + checked out. Defaults to `false`. + + Here are some examples of how to use `fetchGit`. + + - To fetch a private repository over SSH: + + ```nix + builtins.fetchGit { + url = "git@github.com:my-secret/repository.git"; + ref = "master"; + rev = "adab8b916a45068c044658c4158d81878f9ed1c3"; + } + ``` + + - To fetch an arbitrary reference: + + ```nix + builtins.fetchGit { + url = "https://github.com/NixOS/nix.git"; + ref = "refs/heads/0.5-release"; + } + ``` + + - If the revision you're looking for is in the default branch of + the git repository you don't strictly need to specify the branch + name in the `ref` attribute. + + However, if the revision you're looking for is in a future + branch for the non-default branch you will need to specify the + the `ref` attribute as well. + + ```nix + builtins.fetchGit { + url = "https://github.com/nixos/nix.git"; + rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452"; + ref = "1.11-maintenance"; + } + ``` + + > **Note** + > + > It is nice to always specify the branch which a revision + > belongs to. Without the branch being specified, the fetcher + > might fail if the default branch changes. Additionally, it can + > be confusing to try a commit from a non-default branch and see + > the fetch fail. If the branch is specified the fault is much + > more obvious. + + - If the revision you're looking for is in the default branch of + the git repository you may omit the `ref` attribute. + + ```nix + builtins.fetchGit { + url = "https://github.com/nixos/nix.git"; + rev = "841fcbd04755c7a2865c51c1e2d3b045976b7452"; + } + ``` + + - To fetch a specific tag: + + ```nix + builtins.fetchGit { + url = "https://github.com/nixos/nix.git"; + ref = "refs/tags/1.9"; + } + ``` + + - To fetch the latest version of a remote branch: + + ```nix + builtins.fetchGit { + url = "ssh://git@github.com/nixos/nix.git"; + ref = "master"; + } + ``` + + > **Note** + > + > Nix will refetch the branch in accordance with + > the option `tarball-ttl`. + + > **Note** + > + > This behavior is disabled in *Pure evaluation mode*. + )", + .fun = prim_fetchGit, +}); } |