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:
parent
98f3c75a0e
commit
69d82e5c58
8 changed files with 162 additions and 27 deletions
|
@ -308,7 +308,8 @@ stdenv.mkDerivation { … }
|
|||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry><term><function>builtins.filterSource</function>
|
||||
<varlistentry xml:id='builtin-filterSource'>
|
||||
<term><function>builtins.filterSource</function>
|
||||
<replaceable>e1</replaceable> <replaceable>e2</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
|
@ -768,6 +769,75 @@ Evaluates to <literal>[ "foo" ]</literal>.
|
|||
|
||||
</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>
|
||||
<replaceable>path</replaceable></term>
|
||||
|
|
|
@ -1566,7 +1566,7 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
|||
dstPath = srcToStore[path];
|
||||
else {
|
||||
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);
|
||||
srcToStore[path] = dstPath;
|
||||
printMsg(lvlChatty, format("copied source '%1%' -> '%2%'")
|
||||
|
|
|
@ -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;
|
||||
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);
|
||||
|
||||
path = state.checkSourcePath(path);
|
||||
|
||||
PathFilter filter = [&](const Path & path) {
|
||||
const auto path = settings.pureEval && expectedHash ?
|
||||
path_ :
|
||||
state.checkSourcePath(path_);
|
||||
PathFilter filter = filterFun ? ([&](const Path & path) {
|
||||
auto st = lstat(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);
|
||||
|
||||
Value fun2;
|
||||
state.callFunction(*args[0], arg1, fun2, noPos);
|
||||
state.callFunction(*filterFun, arg1, fun2, noPos);
|
||||
|
||||
Value arg2;
|
||||
mkString(arg2,
|
||||
|
@ -1044,16 +1037,79 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
|
|||
state.callFunction(fun2, arg2, res, noPos);
|
||||
|
||||
return state.forceBool(res, pos);
|
||||
};
|
||||
}) : defaultPathFilter;
|
||||
|
||||
Path dstPath = settings.readOnlyMode
|
||||
? state.store->computeStorePathForPath(path, true, htSHA256, filter).first
|
||||
: state.store->addToStore(baseNameOf(path), path, true, htSHA256, filter, state.repair);
|
||||
Path expectedStorePath;
|
||||
if (expectedHash) {
|
||||
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});
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
*************************************************************/
|
||||
|
@ -2071,6 +2127,7 @@ void EvalState::createBaseEnv()
|
|||
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);
|
||||
|
|
|
@ -222,11 +222,10 @@ Path Store::makeTextPath(const string & name, const Hash & hash,
|
|||
}
|
||||
|
||||
|
||||
std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath,
|
||||
bool recursive, HashType hashAlgo, PathFilter & filter) const
|
||||
std::pair<Path, Hash> Store::computeStorePathForPath(const string & name,
|
||||
const Path & srcPath, bool recursive, HashType hashAlgo, PathFilter & filter) const
|
||||
{
|
||||
Hash h = recursive ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath);
|
||||
string name = baseNameOf(srcPath);
|
||||
Path dstPath = makeFixedOutputPath(recursive, h, name);
|
||||
return std::pair<Path, Hash>(dstPath, h);
|
||||
}
|
||||
|
|
|
@ -305,9 +305,9 @@ public:
|
|||
/* This is the preparatory part of addToStore(); it computes the
|
||||
store path to which srcPath is to be copied. Returns the store
|
||||
path and the cryptographic hash of the contents of srcPath. */
|
||||
std::pair<Path, Hash> computeStorePathForPath(const Path & srcPath,
|
||||
bool recursive = true, HashType hashAlgo = htSHA256,
|
||||
PathFilter & filter = defaultPathFilter) const;
|
||||
std::pair<Path, Hash> computeStorePathForPath(const string & name,
|
||||
const Path & srcPath, bool recursive = true,
|
||||
HashType hashAlgo = htSHA256, PathFilter & filter = defaultPathFilter) const;
|
||||
|
||||
/* Preparatory part of addTextToStore().
|
||||
|
||||
|
|
1
tests/lang/data
Normal file
1
tests/lang/data
Normal file
|
@ -0,0 +1 @@
|
|||
foo
|
1
tests/lang/eval-okay-path.exp
Normal file
1
tests/lang/eval-okay-path.exp
Normal file
|
@ -0,0 +1 @@
|
|||
"/run/user/1000/nix-test/store/wjagrv37lfvfx92g2gf3yqflwypj0q1y-output"
|
7
tests/lang/eval-okay-path.nix
Normal file
7
tests/lang/eval-okay-path.nix
Normal file
|
@ -0,0 +1,7 @@
|
|||
builtins.path
|
||||
{ path = ./.;
|
||||
filter = path: _: baseNameOf path == "data";
|
||||
recursive = true;
|
||||
sha256 = "1yhm3gwvg5a41yylymgblsclk95fs6jy72w0wv925mmidlhcq4sw";
|
||||
name = "output";
|
||||
}
|
Loading…
Reference in a new issue