Add path primop.

builtins.path allows specifying the name of a path (which makes paths
with store-illegal names now addable), allows adding paths with flat
instead of recursive hashes, allows specifying a filter (so is a
generalization of filterSource), and allows specifying an expected
hash (enabling safe path adding in pure mode).
This commit is contained in:
Shea Levy 2018-01-25 07:05:57 -08:00
parent 98f3c75a0e
commit 69d82e5c58
No known key found for this signature in database
GPG key ID: 5C0BD6957D86FE27
8 changed files with 162 additions and 27 deletions

View file

@ -308,7 +308,8 @@ stdenv.mkDerivation { … }
</varlistentry> </varlistentry>
<varlistentry><term><function>builtins.filterSource</function> <varlistentry xml:id='builtin-filterSource'>
<term><function>builtins.filterSource</function>
<replaceable>e1</replaceable> <replaceable>e2</replaceable></term> <replaceable>e1</replaceable> <replaceable>e2</replaceable></term>
<listitem> <listitem>
@ -768,6 +769,75 @@ Evaluates to <literal>[ "foo" ]</literal>.
</varlistentry> </varlistentry>
<varlistentry>
<term>
<function>builtins.path</function>
<replaceable>args</replaceable>
</term>
<listitem>
<para>
An enrichment of the built-in path type, based on the attributes
present in <replaceable>args</replaceable>. All are optional
except <varname>path</varname>:
</para>
<variablelist>
<varlistentry>
<term>path</term>
<listitem>
<para>The underlying path.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>name</term>
<listitem>
<para>
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 <literal>@</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>filter</term>
<listitem>
<para>
A function of the type expected by
<link linkend="builtin-filterSource">builtins.filterSource</link>,
with the same semantics.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>recursive</term>
<listitem>
<para>
When <literal>false</literal>, when
<varname>path</varname> is added to the store it is with a
flat hash, rather than a hash of the NAR serialization of
the file. Thus, <varname>path</varname> must refer to a
regular file, not a directory. This allows similar
behavior to <literal>fetchurl</literal>. Defaults to
<literal>true</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>sha256</term>
<listitem>
<para>
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
<literal>builtins.path</literal> to be used even when the
<literal>pure-eval</literal> nix config option is on.
</para>
</listitem>
</varlistentry>
</variablelist>
</listitem>
</varlistentry>
<varlistentry><term><function>builtins.pathExists</function> <varlistentry><term><function>builtins.pathExists</function>
<replaceable>path</replaceable></term> <replaceable>path</replaceable></term>

View file

@ -1566,7 +1566,7 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
dstPath = srcToStore[path]; dstPath = srcToStore[path];
else { else {
dstPath = settings.readOnlyMode dstPath = settings.readOnlyMode
? store->computeStorePathForPath(checkSourcePath(path)).first ? store->computeStorePathForPath(baseNameOf(path), checkSourcePath(path)).first
: store->addToStore(baseNameOf(path), checkSourcePath(path), true, htSHA256, defaultPathFilter, repair); : store->addToStore(baseNameOf(path), checkSourcePath(path), true, htSHA256, defaultPathFilter, repair);
srcToStore[path] = dstPath; srcToStore[path] = dstPath;
printMsg(lvlChatty, format("copied source '%1%' -> '%2%'") printMsg(lvlChatty, format("copied source '%1%' -> '%2%'")

View file

@ -1009,20 +1009,13 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
} }
static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v) static void addPath(EvalState & state, const Pos & pos, const string & name, const Path & path_,
Value * filterFun, bool recursive, const Hash & expectedHash, Value & v)
{ {
PathSet context; const auto path = settings.pureEval && expectedHash ?
Path path = state.coerceToPath(pos, *args[1], context); path_ :
if (!context.empty()) state.checkSourcePath(path_);
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos); PathFilter filter = filterFun ? ([&](const Path & path) {
state.forceValue(*args[0]);
if (args[0]->type != tLambda)
throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos);
path = state.checkSourcePath(path);
PathFilter filter = [&](const Path & path) {
auto st = lstat(path); auto st = lstat(path);
/* Call the filter function. The first argument is the path, /* Call the filter function. The first argument is the path,
@ -1031,7 +1024,7 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
mkString(arg1, path); mkString(arg1, path);
Value fun2; Value fun2;
state.callFunction(*args[0], arg1, fun2, noPos); state.callFunction(*filterFun, arg1, fun2, noPos);
Value arg2; Value arg2;
mkString(arg2, mkString(arg2,
@ -1044,16 +1037,79 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
state.callFunction(fun2, arg2, res, noPos); state.callFunction(fun2, arg2, res, noPos);
return state.forceBool(res, pos); return state.forceBool(res, pos);
}; }) : defaultPathFilter;
Path dstPath = settings.readOnlyMode Path expectedStorePath;
? state.store->computeStorePathForPath(path, true, htSHA256, filter).first if (expectedHash) {
: state.store->addToStore(baseNameOf(path), path, true, htSHA256, filter, state.repair); expectedStorePath =
state.store->makeFixedOutputPath(recursive, expectedHash, name);
}
Path dstPath;
if (!expectedHash || !state.store->isValidPath(expectedStorePath)) {
dstPath = settings.readOnlyMode
? state.store->computeStorePathForPath(name, path, recursive, htSHA256, filter).first
: state.store->addToStore(name, path, recursive, htSHA256, filter, state.repair);
if (expectedHash && expectedStorePath != dstPath) {
throw Error(format("store path mismatch in (possibly filtered) path added from '%1%'") % path);
}
} else
dstPath = expectedStorePath;
mkString(v, dstPath, {dstPath}); mkString(v, dstPath, {dstPath});
} }
static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[1], context);
if (!context.empty())
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos);
state.forceValue(*args[0]);
if (args[0]->type != tLambda)
throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos);
addPath(state, pos, baseNameOf(path), path, args[0], true, Hash(), v);
}
static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos);
Path path;
string name;
Value * filterFun = nullptr;
auto recursive = true;
Hash expectedHash;
for (auto & attr : *args[0]->attrs) {
const string & n(attr.name);
if (n == "path") {
PathSet context;
path = state.coerceToPath(*attr.pos, *attr.value, context);
if (!context.empty())
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % *attr.pos);
} else if (attr.name == state.sName)
name = state.forceStringNoCtx(*attr.value, *attr.pos);
else if (n == "filter") {
state.forceValue(*attr.value);
filterFun = attr.value;
} else if (n == "recursive")
recursive = state.forceBool(*attr.value, *attr.pos);
else if (n == "sha256")
expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
else
throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos);
}
if (path.empty())
throw EvalError(format("'path' required, at %1%") % pos);
if (name.empty())
name = baseNameOf(path);
addPath(state, pos, name, path, filterFun, recursive, expectedHash, v);
}
/************************************************************* /*************************************************************
* Sets * Sets
*************************************************************/ *************************************************************/
@ -2071,6 +2127,7 @@ void EvalState::createBaseEnv()
addPrimOp("__fromJSON", 1, prim_fromJSON); addPrimOp("__fromJSON", 1, prim_fromJSON);
addPrimOp("__toFile", 2, prim_toFile); addPrimOp("__toFile", 2, prim_toFile);
addPrimOp("__filterSource", 2, prim_filterSource); addPrimOp("__filterSource", 2, prim_filterSource);
addPrimOp("__path", 1, prim_path);
// Sets // Sets
addPrimOp("__attrNames", 1, prim_attrNames); addPrimOp("__attrNames", 1, prim_attrNames);

View file

@ -222,11 +222,10 @@ Path Store::makeTextPath(const string & name, const Hash & hash,
} }
std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath, std::pair<Path, Hash> Store::computeStorePathForPath(const string & name,
bool recursive, HashType hashAlgo, PathFilter & filter) const const Path & srcPath, bool recursive, HashType hashAlgo, PathFilter & filter) const
{ {
Hash h = recursive ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath); Hash h = recursive ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath);
string name = baseNameOf(srcPath);
Path dstPath = makeFixedOutputPath(recursive, h, name); Path dstPath = makeFixedOutputPath(recursive, h, name);
return std::pair<Path, Hash>(dstPath, h); return std::pair<Path, Hash>(dstPath, h);
} }

View file

@ -305,9 +305,9 @@ public:
/* This is the preparatory part of addToStore(); it computes the /* This is the preparatory part of addToStore(); it computes the
store path to which srcPath is to be copied. Returns the store store path to which srcPath is to be copied. Returns the store
path and the cryptographic hash of the contents of srcPath. */ path and the cryptographic hash of the contents of srcPath. */
std::pair<Path, Hash> computeStorePathForPath(const Path & srcPath, std::pair<Path, Hash> computeStorePathForPath(const string & name,
bool recursive = true, HashType hashAlgo = htSHA256, const Path & srcPath, bool recursive = true,
PathFilter & filter = defaultPathFilter) const; HashType hashAlgo = htSHA256, PathFilter & filter = defaultPathFilter) const;
/* Preparatory part of addTextToStore(). /* Preparatory part of addTextToStore().

1
tests/lang/data Normal file
View file

@ -0,0 +1 @@
foo

View file

@ -0,0 +1 @@
"/run/user/1000/nix-test/store/wjagrv37lfvfx92g2gf3yqflwypj0q1y-output"

View file

@ -0,0 +1,7 @@
builtins.path
{ path = ./.;
filter = path: _: baseNameOf path == "data";
recursive = true;
sha256 = "1yhm3gwvg5a41yylymgblsclk95fs6jy72w0wv925mmidlhcq4sw";
name = "output";
}