forked from lix-project/lix
primops: Move functions to primops/path.cc
Moved builtins: dirOf, filterSource, findFile, outputOf, path,
pathExists, placeholder, toPath, readDir, readFile, readFileType,
storePath, toFile
realisePath has also been moved
Change-Id: Ie30efc61ca530ececedf3d3002a9553d2e8a9c60
This commit is contained in:
parent
468d30e053
commit
4e448e78f7
|
@ -97,6 +97,7 @@ libexpr_sources = files(
|
|||
'primops/fromTOML.cc',
|
||||
'primops/import.cc',
|
||||
'primops/list.cc',
|
||||
'primops/path.cc',
|
||||
'primops/string.cc',
|
||||
'primops/types.cc',
|
||||
'value/context.cc',
|
||||
|
|
|
@ -115,25 +115,6 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
|||
return res;
|
||||
}
|
||||
|
||||
SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags)
|
||||
{
|
||||
NixStringContext context;
|
||||
|
||||
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
|
||||
|
||||
try {
|
||||
StringMap rewrites = state.realiseContext(context);
|
||||
|
||||
auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
|
||||
|
||||
return flags.checkForPureEval
|
||||
? state.checkSourcePath(realPath)
|
||||
: realPath;
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Execute a program and parse its output */
|
||||
|
@ -812,437 +793,12 @@ static RegisterPrimOp primop_derivationStrict(PrimOp {
|
|||
.fun = prim_derivationStrict,
|
||||
});
|
||||
|
||||
/* Return a placeholder string for the specified output that will be
|
||||
substituted by the corresponding output path at build time. For
|
||||
example, 'placeholder "out"' returns the string
|
||||
/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build
|
||||
time, any occurrence of this string in an derivation attribute will
|
||||
be replaced with the concrete path in the Nix store of the output
|
||||
‘out’. */
|
||||
static void prim_placeholder(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.placeholder")));
|
||||
}
|
||||
|
||||
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
|
||||
*************************************************************/
|
||||
|
||||
|
||||
/* Convert the argument to a path. !!! obsolete? */
|
||||
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
|
||||
v.mkString(path.path.abs(), 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
|
||||
generates a call to a function with an already existing store path
|
||||
as argument. You don't want to use `toPath' here because it copies
|
||||
the path to the Nix store, which yields a copy like
|
||||
/nix/store/newhash-oldhash-oldname. In the past, `toPath' had
|
||||
special case behaviour for store paths, but that created weird
|
||||
corner cases. */
|
||||
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
if (evalSettings.pureEval)
|
||||
state.error<EvalError>(
|
||||
"'%s' is not allowed in pure evaluation mode",
|
||||
"builtins.storePath"
|
||||
).atPos(pos).debugThrow();
|
||||
|
||||
NixStringContext context;
|
||||
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
|
||||
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
|
||||
directly in the store. The latter condition is necessary so
|
||||
e.g. nix-push does the right thing. */
|
||||
if (!state.store->isStorePath(path.abs()))
|
||||
path = CanonPath(canonPath(path.abs(), true));
|
||||
if (!state.store->isInStore(path.abs()))
|
||||
state.error<EvalError>("path '%1%' is not in the Nix store", path)
|
||||
.atPos(pos).debugThrow();
|
||||
auto path2 = state.store->toStorePath(path.abs()).first;
|
||||
if (!settings.readOnlyMode)
|
||||
state.store->ensurePath(path2);
|
||||
context.insert(NixStringContextElem::Opaque { .path = path2 });
|
||||
v.mkString(path.abs(), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_storePath({
|
||||
.name = "__storePath",
|
||||
.args = {"path"},
|
||||
.doc = R"(
|
||||
This function allows you to define a dependency on an already
|
||||
existing store path. For example, the derivation attribute `src
|
||||
= builtins.storePath /nix/store/f1d18v1y…-source` causes the
|
||||
derivation to depend on the specified path, which must exist or
|
||||
be substitutable. Note that this differs from a plain path
|
||||
(e.g. `src = /nix/store/f1d18v1y…-source`) in that the latter
|
||||
causes the path to be *copied* again to the Nix store, resulting
|
||||
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
|
||||
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
|
||||
)",
|
||||
.fun = prim_storePath,
|
||||
});
|
||||
|
||||
static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto & arg = *args[0];
|
||||
|
||||
/* We don’t check the path right now, because we don’t want to
|
||||
throw if the path isn’t allowed, but just return false (and we
|
||||
can’t just catch the exception here because we still want to
|
||||
throw if something in the evaluation of `arg` tries to
|
||||
access an unauthorized path). */
|
||||
auto path = realisePath(state, pos, arg, { .checkForPureEval = false });
|
||||
|
||||
/* SourcePath doesn't know about trailing slash. */
|
||||
auto mustBeDir = arg.type() == nString
|
||||
&& (arg.str().ends_with("/")
|
||||
|| arg.str().ends_with("/."));
|
||||
|
||||
try {
|
||||
auto checked = state
|
||||
.checkSourcePath(path)
|
||||
.resolveSymlinks(mustBeDir ? SymlinkResolution::Full : SymlinkResolution::Ancestors);
|
||||
|
||||
auto st = checked.maybeLstat();
|
||||
auto exists = st && (!mustBeDir || st->type == InputAccessor::tDirectory);
|
||||
v.mkBool(exists);
|
||||
} catch (SysError & e) {
|
||||
/* Don't give away info from errors while canonicalising
|
||||
‘path’ in restricted mode. */
|
||||
v.mkBool(false);
|
||||
} catch (RestrictedPathError & e) {
|
||||
v.mkBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
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 directory of the given path, i.e., everything before the
|
||||
last slash. Return either a path or a string depending on the type
|
||||
of the argument. */
|
||||
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type() == nPath) {
|
||||
auto path = args[0]->path();
|
||||
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||
} else {
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'",
|
||||
false, false);
|
||||
auto dir = dirOf(*path);
|
||||
v.mkString(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 PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
auto s = path.readFile();
|
||||
if (s.find((char) 0) != std::string::npos)
|
||||
state.error<EvalError>(
|
||||
"the contents of the file '%1%' cannot be represented as a Nix string",
|
||||
path
|
||||
).atPos(pos).debugThrow();
|
||||
StorePathSet refs;
|
||||
if (state.store->isInStore(path.path.abs())) {
|
||||
try {
|
||||
refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references;
|
||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||
}
|
||||
// Re-scan references to filter down to just the ones that actually occur in the file.
|
||||
auto refsSink = PathRefScanSink::fromPaths(refs);
|
||||
refsSink << s;
|
||||
refs = refsSink.getResultPaths();
|
||||
}
|
||||
NixStringContext context;
|
||||
for (auto && p : std::move(refs)) {
|
||||
context.insert(NixStringContextElem::Opaque {
|
||||
.path = std::move((StorePath &&)p),
|
||||
});
|
||||
}
|
||||
v.mkString(s, context);
|
||||
}
|
||||
|
||||
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"'. */
|
||||
static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.findFile");
|
||||
|
||||
SearchPath searchPath;
|
||||
|
||||
for (auto v2 : args[0]->listItems()) {
|
||||
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.findFile");
|
||||
|
||||
std::string prefix;
|
||||
Bindings::iterator i = v2->attrs->find(state.sPrefix);
|
||||
if (i != v2->attrs->end())
|
||||
prefix = state.forceStringNoCtx(*i->value, pos, "while evaluating the `prefix` attribute of an element of the list passed to builtins.findFile");
|
||||
|
||||
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
||||
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToString(pos, *i->value, context,
|
||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
|
||||
false, false).toOwned();
|
||||
|
||||
try {
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = rewriteStrings(path, rewrites);
|
||||
} catch (InvalidPathError & e) {
|
||||
state.error<EvalError>(
|
||||
"cannot find '%1%', since path '%2%' is not valid",
|
||||
path,
|
||||
e.path
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
searchPath.elements.emplace_back(SearchPath::Elem {
|
||||
.prefix = SearchPath::Prefix { .s = prefix },
|
||||
.path = SearchPath::Path { .s = path },
|
||||
});
|
||||
}
|
||||
|
||||
auto path = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
|
||||
|
||||
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_findFile(PrimOp {
|
||||
.name = "__findFile",
|
||||
.args = {"search path", "lookup path"},
|
||||
.doc = R"(
|
||||
Look up the given path with the given search path.
|
||||
|
||||
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes, `prefix`, and `path`.
|
||||
`prefix` is a relative path.
|
||||
`path` denotes a file system location; the exact syntax depends on the command line interface.
|
||||
|
||||
Examples of search path attribute sets:
|
||||
|
||||
- ```
|
||||
{
|
||||
prefix = "nixos-config";
|
||||
path = "/etc/nixos/configuration.nix";
|
||||
}
|
||||
```
|
||||
|
||||
- ```
|
||||
{
|
||||
prefix = "";
|
||||
path = "/nix/var/nix/profiles/per-user/root/channels";
|
||||
}
|
||||
```
|
||||
|
||||
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match.
|
||||
|
||||
This is the process for each entry:
|
||||
If the lookup path matches `prefix`, then the remainder of the lookup path (the "suffix") is searched for within the directory denoted by `patch`.
|
||||
Note that the `path` may need to be downloaded at this point to look inside.
|
||||
If the suffix is found inside that directory, then the entry is a match;
|
||||
the combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
|
||||
|
||||
The syntax
|
||||
|
||||
```nix
|
||||
<nixpkgs>
|
||||
```
|
||||
|
||||
is equivalent to:
|
||||
|
||||
```nix
|
||||
builtins.findFile builtins.nixPath "nixpkgs"
|
||||
```
|
||||
)",
|
||||
.fun = prim_findFile,
|
||||
});
|
||||
|
||||
static std::string_view fileTypeToString(InputAccessor::Type type)
|
||||
{
|
||||
return
|
||||
type == InputAccessor::Type::tRegular ? "regular" :
|
||||
type == InputAccessor::Type::tDirectory ? "directory" :
|
||||
type == InputAccessor::Type::tSymlink ? "symlink" :
|
||||
"unknown";
|
||||
}
|
||||
|
||||
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
/* Retrieve the directory entry type and stringize it. */
|
||||
v.mkString(fileTypeToString(path.lstat().type));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_readFileType({
|
||||
.name = "__readFileType",
|
||||
.args = {"p"},
|
||||
.doc = R"(
|
||||
Determine the directory entry type of a filesystem node, being
|
||||
one of "directory", "regular", "symlink", or "unknown".
|
||||
)",
|
||||
.fun = prim_readFileType,
|
||||
});
|
||||
|
||||
/* Read a directory (without . or ..) */
|
||||
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
|
||||
// Retrieve directory entries for all nodes in a directory.
|
||||
// This is similar to `getFileType` but is optimized to reduce system calls
|
||||
// on many systems.
|
||||
auto entries = path.readDirectory();
|
||||
auto attrs = state.buildBindings(entries.size());
|
||||
|
||||
// If we hit unknown directory entry types we may need to fallback to
|
||||
// using `getFileType` on some systems.
|
||||
// In order to reduce system calls we make each lookup lazy by using
|
||||
// `builtins.readFileType` application.
|
||||
Value * readFileType = nullptr;
|
||||
|
||||
for (auto & [name, type] : entries) {
|
||||
auto & attr = attrs.alloc(name);
|
||||
if (!type) {
|
||||
// Some filesystems or operating systems may not be able to return
|
||||
// detailed node info quickly in this case we produce a thunk to
|
||||
// query the file type lazily.
|
||||
auto epath = state.allocValue();
|
||||
epath->mkPath(path + name);
|
||||
if (!readFileType)
|
||||
readFileType = &state.getBuiltin("readFileType");
|
||||
attr.mkApp(readFileType, epath);
|
||||
} else {
|
||||
// This branch of the conditional is much more likely.
|
||||
// Here we just stringize the directory entry type.
|
||||
attr.mkString(fileTypeToString(*type));
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/* Extend single element string context with another output. */
|
||||
static void prim_outputOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
SingleDerivedPath drvPath = state.coerceToSingleDerivedPath(pos, *args[0], "while evaluating the first argument to builtins.outputOf");
|
||||
|
||||
OutputNameView outputName = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument to builtins.outputOf");
|
||||
|
||||
state.mkSingleDerivedPathString(
|
||||
SingleDerivedPath::Built {
|
||||
.drvPath = make_ref<SingleDerivedPath>(drvPath),
|
||||
.output = std::string { outputName },
|
||||
},
|
||||
v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_outputOf({
|
||||
.name = "__outputOf",
|
||||
.args = {"derivation-reference", "output-name"},
|
||||
.doc = R"(
|
||||
Return the output path of a derivation, literally or using a placeholder if needed.
|
||||
|
||||
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addresed), the output path will just be returned.
|
||||
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
|
||||
|
||||
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
|
||||
This primop can be chained arbitrarily deeply.
|
||||
For instance,
|
||||
|
||||
```nix
|
||||
builtins.outputOf
|
||||
(builtins.outputOf myDrv "out)
|
||||
"out"
|
||||
```
|
||||
|
||||
will return a placeholder for the output of the output of `myDrv`.
|
||||
|
||||
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
|
||||
)",
|
||||
.fun = prim_outputOf,
|
||||
.experimentalFeature = Xp::DynamicDerivations,
|
||||
});
|
||||
|
||||
/*************************************************************
|
||||
* Creating files
|
||||
*************************************************************/
|
||||
|
@ -1410,333 +966,7 @@ static RegisterPrimOp primop_fromJSON({
|
|||
|
||||
/* Store a string in the Nix store as a source file that can be used
|
||||
as an input by derivations. */
|
||||
static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.toFile"));
|
||||
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
|
||||
|
||||
StorePathSet refs;
|
||||
|
||||
for (auto c : context) {
|
||||
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
|
||||
refs.insert(p->path);
|
||||
else
|
||||
state.error<EvalError>(
|
||||
"files created by %1% may not reference derivations, but %2% references %3%",
|
||||
"builtins.toFile",
|
||||
name,
|
||||
c.to_string()
|
||||
).atPos(pos).debugThrow();
|
||||
}
|
||||
|
||||
auto storePath = settings.readOnlyMode
|
||||
? state.store->computeStorePathForText(name, contents, refs)
|
||||
: state.store->addTextToStore(name, contents, refs, state.repair);
|
||||
|
||||
/* Note: we don't need to add `context' to the context of the
|
||||
result, since `storePath' itself has references to the paths
|
||||
used in args[1]. */
|
||||
|
||||
/* Add the output of this to the allowed paths. */
|
||||
state.allowAndSetStorePathString(storePath, v);
|
||||
}
|
||||
|
||||
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 and its build script 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 a
|
||||
[string interpolation](@docroot@/language/values.md#type-string), 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 PosIdx pos,
|
||||
std::string_view name,
|
||||
Path path,
|
||||
Value * filterFun,
|
||||
FileIngestionMethod method,
|
||||
const std::optional<Hash> expectedHash,
|
||||
Value & v,
|
||||
const NixStringContext & context)
|
||||
{
|
||||
try {
|
||||
// FIXME: handle CA derivation outputs (where path needs to
|
||||
// be rewritten to the actual output).
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = state.toRealPath(rewriteStrings(path, rewrites), context);
|
||||
|
||||
StorePathSet refs;
|
||||
|
||||
if (state.store->isInStore(path)) {
|
||||
try {
|
||||
auto [storePath, subPath] = state.store->toStorePath(path);
|
||||
// FIXME: we should scanForReferences on the path before adding it
|
||||
refs = state.store->queryPathInfo(storePath)->references;
|
||||
path = state.store->toRealPath(storePath) + subPath;
|
||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||
}
|
||||
}
|
||||
|
||||
path = evalSettings.pureEval && expectedHash
|
||||
? path
|
||||
: state.checkSourcePath(CanonPath(path)).path.abs();
|
||||
|
||||
PathFilter filter = filterFun ? ([&](const Path & path) {
|
||||
auto st = lstat(path);
|
||||
|
||||
/* Call the filter function. The first argument is the path,
|
||||
the second is a string indicating the type of the file. */
|
||||
Value arg1;
|
||||
arg1.mkString(path);
|
||||
|
||||
Value arg2;
|
||||
arg2.mkString(
|
||||
S_ISREG(st.st_mode) ? "regular" :
|
||||
S_ISDIR(st.st_mode) ? "directory" :
|
||||
S_ISLNK(st.st_mode) ? "symlink" :
|
||||
"unknown" /* not supported, will fail! */);
|
||||
|
||||
Value * args []{&arg1, &arg2};
|
||||
Value res;
|
||||
state.callFunction(*filterFun, 2, args, res, pos);
|
||||
|
||||
return state.forceBool(res, pos, "while evaluating the return value of the path filter function");
|
||||
}) : defaultPathFilter;
|
||||
|
||||
std::optional<StorePath> expectedStorePath;
|
||||
if (expectedHash)
|
||||
expectedStorePath = state.store->makeFixedOutputPath(name, FixedOutputInfo {
|
||||
.method = method,
|
||||
.hash = *expectedHash,
|
||||
.references = {},
|
||||
});
|
||||
|
||||
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
||||
auto dstPath = fetchToStore(
|
||||
*state.store, state.rootPath(CanonPath(path)), name, method, &filter, state.repair);
|
||||
if (expectedHash && expectedStorePath != dstPath)
|
||||
state.error<EvalError>(
|
||||
"store path mismatch in (possibly filtered) path added from '%s'",
|
||||
path
|
||||
).atPos(pos).debugThrow();
|
||||
state.allowAndSetStorePathString(dstPath, v);
|
||||
} else
|
||||
state.allowAndSetStorePathString(*expectedStorePath, v);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[pos], "while adding path '%s'", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToPath(pos, *args[1], context,
|
||||
"while evaluating the second argument (the path to filter) passed to builtins.filterSource");
|
||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
|
||||
addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_filterSource({
|
||||
.name = "__filterSource",
|
||||
.args = {"e1", "e2"},
|
||||
.doc = R"(
|
||||
> **Warning**
|
||||
>
|
||||
> `filterSource` should not be used to filter store paths. Since
|
||||
> `filterSource` uses the name of the input directory while naming
|
||||
> the output directory, doing so will produce a directory name in
|
||||
> the form of `<hash2>-<hash>-<name>`, where `<hash>-<name>` is
|
||||
> the name of the input directory. Since `<hash>` depends on the
|
||||
> unfiltered directory, the name of the output directory will
|
||||
> indirectly depend on files that are filtered out by the
|
||||
> function. This will trigger a rebuild even when a filtered out
|
||||
> file is changed. Use `builtins.path` instead, which allows
|
||||
> specifying the name of the output directory.
|
||||
|
||||
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 PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
std::optional<SourcePath> path;
|
||||
std::string name;
|
||||
Value * filterFun = nullptr;
|
||||
auto method = FileIngestionMethod::Recursive;
|
||||
std::optional<Hash> expectedHash;
|
||||
NixStringContext context;
|
||||
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
|
||||
|
||||
for (auto & attr : *args[0]->attrs) {
|
||||
auto n = state.symbols[attr.name];
|
||||
if (n == "path")
|
||||
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
|
||||
else if (attr.name == state.sName)
|
||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
|
||||
else if (n == "filter")
|
||||
state.forceFunction(*(filterFun = attr.value), attr.pos, "while evaluating the `filter` parameter passed to builtins.path");
|
||||
else if (n == "recursive")
|
||||
method = FileIngestionMethod { state.forceBool(*attr.value, attr.pos, "while evaluating the `recursive` attribute passed to builtins.path") };
|
||||
else if (n == "sha256")
|
||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), htSHA256);
|
||||
else
|
||||
state.error<EvalError>(
|
||||
"unsupported argument '%1%' to 'addPath'",
|
||||
state.symbols[attr.name]
|
||||
).atPos(attr.pos).debugThrow();
|
||||
}
|
||||
if (!path)
|
||||
state.error<EvalError>(
|
||||
"missing required 'path' attribute in the first argument to builtins.path"
|
||||
).atPos(pos).debugThrow();
|
||||
if (name.empty())
|
||||
name = path->baseName();
|
||||
|
||||
addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
/*************************************************************
|
||||
|
|
932
src/libexpr/primops/path.cc
Normal file
932
src/libexpr/primops/path.cc
Normal file
|
@ -0,0 +1,932 @@
|
|||
#include "eval-settings.hh"
|
||||
#include "fetch-to-store.hh"
|
||||
#include "path-references.hh"
|
||||
#include "primops.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
// TODO: Use a const map instead ?
|
||||
static std::string_view fileTypeToString(InputAccessor::Type type)
|
||||
{
|
||||
return type == InputAccessor::Type::tRegular ? "regular"
|
||||
: type == InputAccessor::Type::tDirectory ? "directory"
|
||||
: type == InputAccessor::Type::tSymlink ? "symlink"
|
||||
: "unknown";
|
||||
}
|
||||
|
||||
static void addPath(
|
||||
EvalState & state,
|
||||
const PosIdx pos,
|
||||
std::string_view name,
|
||||
Path path,
|
||||
Value * filterFun,
|
||||
FileIngestionMethod method,
|
||||
const std::optional<Hash> expectedHash,
|
||||
Value & v,
|
||||
const NixStringContext & context
|
||||
)
|
||||
{
|
||||
try {
|
||||
// FIXME: handle CA derivation outputs (where path needs to
|
||||
// be rewritten to the actual output).
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = state.toRealPath(rewriteStrings(path, rewrites), context);
|
||||
|
||||
StorePathSet refs;
|
||||
|
||||
if (state.store->isInStore(path)) {
|
||||
try {
|
||||
auto [storePath, subPath] = state.store->toStorePath(path);
|
||||
// FIXME: we should scanForReferences on the path before adding it
|
||||
refs = state.store->queryPathInfo(storePath)->references;
|
||||
path = state.store->toRealPath(storePath) + subPath;
|
||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||
}
|
||||
}
|
||||
|
||||
path = evalSettings.pureEval && expectedHash
|
||||
? path
|
||||
: state.checkSourcePath(CanonPath(path)).path.abs();
|
||||
|
||||
PathFilter filter = filterFun ? ([&](const Path & path) {
|
||||
auto st = lstat(path);
|
||||
|
||||
/* Call the filter function. The first argument is the path,
|
||||
the second is a string indicating the type of the file. */
|
||||
Value arg1;
|
||||
arg1.mkString(path);
|
||||
|
||||
Value arg2;
|
||||
arg2.mkString(
|
||||
S_ISREG(st.st_mode) ? "regular"
|
||||
: S_ISDIR(st.st_mode) ? "directory"
|
||||
: S_ISLNK(st.st_mode) ? "symlink"
|
||||
: "unknown" /* not supported, will fail! */
|
||||
);
|
||||
|
||||
Value * args[]{&arg1, &arg2};
|
||||
Value res;
|
||||
state.callFunction(*filterFun, 2, args, res, pos);
|
||||
|
||||
return state.forceBool(
|
||||
res, pos, "while evaluating the return value of the path filter function"
|
||||
);
|
||||
})
|
||||
: defaultPathFilter;
|
||||
|
||||
std::optional<StorePath> expectedStorePath;
|
||||
if (expectedHash) {
|
||||
expectedStorePath = state.store->makeFixedOutputPath(
|
||||
name,
|
||||
FixedOutputInfo{
|
||||
.method = method,
|
||||
.hash = *expectedHash,
|
||||
.references = {},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
||||
auto dstPath = fetchToStore(
|
||||
*state.store, state.rootPath(CanonPath(path)), name, method, &filter, state.repair
|
||||
);
|
||||
if (expectedHash && expectedStorePath != dstPath) {
|
||||
state
|
||||
.error<EvalError>(
|
||||
"store path mismatch in (possibly filtered) path added from '%s'", path
|
||||
)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
state.allowAndSetStorePathString(dstPath, v);
|
||||
} else {
|
||||
state.allowAndSetStorePathString(*expectedStorePath, v);
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[pos], "while adding path '%s'", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags)
|
||||
{
|
||||
NixStringContext context;
|
||||
|
||||
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
|
||||
|
||||
try {
|
||||
StringMap rewrites = state.realiseContext(context);
|
||||
|
||||
auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
|
||||
|
||||
return flags.checkForPureEval
|
||||
? state.checkSourcePath(realPath)
|
||||
: realPath;
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* builtins.dirOf
|
||||
*/
|
||||
|
||||
static void prim_dirOf(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type() == nPath) {
|
||||
auto path = args[0]->path();
|
||||
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||
} else {
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToString(
|
||||
pos,
|
||||
*args[0],
|
||||
context,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'",
|
||||
false,
|
||||
false
|
||||
);
|
||||
auto dir = dirOf(*path);
|
||||
v.mkString(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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.filterSource
|
||||
*/
|
||||
|
||||
static void prim_filterSource(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToPath(
|
||||
pos,
|
||||
*args[1],
|
||||
context,
|
||||
"while evaluating the second argument (the path to filter) passed to builtins.filterSource"
|
||||
);
|
||||
state.forceFunction(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.filterSource"
|
||||
);
|
||||
addPath(
|
||||
state,
|
||||
pos,
|
||||
path.baseName(),
|
||||
path.path.abs(),
|
||||
args[0],
|
||||
FileIngestionMethod::Recursive,
|
||||
std::nullopt,
|
||||
v,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_filterSource({
|
||||
.name = "__filterSource",
|
||||
.args = {"e1", "e2"},
|
||||
.doc = R"(
|
||||
> **Warning**
|
||||
>
|
||||
> `filterSource` should not be used to filter store paths. Since
|
||||
> `filterSource` uses the name of the input directory while naming
|
||||
> the output directory, doing so will produce a directory name in
|
||||
> the form of `<hash2>-<hash>-<name>`, where `<hash>-<name>` is
|
||||
> the name of the input directory. Since `<hash>` depends on the
|
||||
> unfiltered directory, the name of the output directory will
|
||||
> indirectly depend on files that are filtered out by the
|
||||
> function. This will trigger a rebuild even when a filtered out
|
||||
> file is changed. Use `builtins.path` instead, which allows
|
||||
> specifying the name of the output directory.
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.findFile
|
||||
*/
|
||||
|
||||
static void prim_findFile(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
state.forceList(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.findFile"
|
||||
);
|
||||
|
||||
SearchPath searchPath;
|
||||
|
||||
for (auto v2 : args[0]->listItems()) {
|
||||
state.forceAttrs(
|
||||
*v2, pos, "while evaluating an element of the list passed to builtins.findFile"
|
||||
);
|
||||
|
||||
std::string prefix;
|
||||
Bindings::iterator i = v2->attrs->find(state.sPrefix);
|
||||
if (i != v2->attrs->end()) {
|
||||
prefix = state.forceStringNoCtx(
|
||||
*i->value,
|
||||
pos,
|
||||
"while evaluating the `prefix` attribute of an element of the list passed to "
|
||||
"builtins.findFile"
|
||||
);
|
||||
}
|
||||
|
||||
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
||||
|
||||
NixStringContext context;
|
||||
auto path = state
|
||||
.coerceToString(
|
||||
pos,
|
||||
*i->value,
|
||||
context,
|
||||
"while evaluating the `path` attribute of an element of the list "
|
||||
"passed to builtins.findFile",
|
||||
false,
|
||||
false
|
||||
)
|
||||
.toOwned();
|
||||
|
||||
try {
|
||||
auto rewrites = state.realiseContext(context);
|
||||
path = rewriteStrings(path, rewrites);
|
||||
} catch (InvalidPathError & e) {
|
||||
state.error<EvalError>("cannot find '%1%', since path '%2%' is not valid", path, e.path)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
|
||||
searchPath.elements.emplace_back(SearchPath::Elem{
|
||||
.prefix = SearchPath::Prefix{.s = prefix},
|
||||
.path = SearchPath::Path{.s = path},
|
||||
});
|
||||
}
|
||||
|
||||
auto path = state.forceStringNoCtx(
|
||||
*args[1], pos, "while evaluating the second argument passed to builtins.findFile"
|
||||
);
|
||||
|
||||
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_findFile(PrimOp{
|
||||
.name = "__findFile",
|
||||
.args = {"search path", "lookup path"},
|
||||
.doc = R"(
|
||||
Look up the given path with the given search path.
|
||||
|
||||
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes, `prefix`, and `path`.
|
||||
`prefix` is a relative path.
|
||||
`path` denotes a file system location; the exact syntax depends on the command line interface.
|
||||
|
||||
Examples of search path attribute sets:
|
||||
|
||||
- ```
|
||||
{
|
||||
prefix = "nixos-config";
|
||||
path = "/etc/nixos/configuration.nix";
|
||||
}
|
||||
```
|
||||
|
||||
- ```
|
||||
{
|
||||
prefix = "";
|
||||
path = "/nix/var/nix/profiles/per-user/root/channels";
|
||||
}
|
||||
```
|
||||
|
||||
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match.
|
||||
|
||||
This is the process for each entry:
|
||||
If the lookup path matches `prefix`, then the remainder of the lookup path (the "suffix") is searched for within the directory denoted by `patch`.
|
||||
Note that the `path` may need to be downloaded at this point to look inside.
|
||||
If the suffix is found inside that directory, then the entry is a match;
|
||||
the combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
|
||||
|
||||
The syntax
|
||||
|
||||
```nix
|
||||
<nixpkgs>
|
||||
```
|
||||
|
||||
is equivalent to:
|
||||
|
||||
```nix
|
||||
builtins.findFile builtins.nixPath "nixpkgs"
|
||||
```
|
||||
)",
|
||||
.fun = prim_findFile,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.outputOf
|
||||
*/
|
||||
|
||||
static void prim_outputOf(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
SingleDerivedPath drvPath = state.coerceToSingleDerivedPath(
|
||||
pos, *args[0], "while evaluating the first argument to builtins.outputOf"
|
||||
);
|
||||
|
||||
OutputNameView outputName = state.forceStringNoCtx(
|
||||
*args[1], pos, "while evaluating the second argument to builtins.outputOf"
|
||||
);
|
||||
|
||||
state.mkSingleDerivedPathString(
|
||||
SingleDerivedPath::Built{
|
||||
.drvPath = make_ref<SingleDerivedPath>(drvPath),
|
||||
.output = std::string{outputName},
|
||||
},
|
||||
v
|
||||
);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_outputOf({
|
||||
.name = "__outputOf",
|
||||
.args = {"derivation-reference", "output-name"},
|
||||
.doc = R"(
|
||||
Return the output path of a derivation, literally or using a placeholder if needed.
|
||||
|
||||
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addresed), the output path will just be returned.
|
||||
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
|
||||
|
||||
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
|
||||
This primop can be chained arbitrarily deeply.
|
||||
For instance,
|
||||
|
||||
```nix
|
||||
builtins.outputOf
|
||||
(builtins.outputOf myDrv "out)
|
||||
"out"
|
||||
```
|
||||
|
||||
will return a placeholder for the output of the output of `myDrv`.
|
||||
|
||||
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
|
||||
)",
|
||||
.fun = prim_outputOf,
|
||||
.experimentalFeature = Xp::DynamicDerivations,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.path
|
||||
*/
|
||||
|
||||
static void prim_path(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
std::optional<SourcePath> path;
|
||||
std::string name;
|
||||
Value * filterFun = nullptr;
|
||||
auto method = FileIngestionMethod::Recursive;
|
||||
std::optional<Hash> expectedHash;
|
||||
NixStringContext context;
|
||||
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
|
||||
|
||||
for (auto & attr : *args[0]->attrs) {
|
||||
auto n = state.symbols[attr.name];
|
||||
if (n == "path") {
|
||||
path.emplace(state.coerceToPath(
|
||||
attr.pos,
|
||||
*attr.value,
|
||||
context,
|
||||
"while evaluating the 'path' attribute passed to 'builtins.path'"
|
||||
));
|
||||
} else if (attr.name == state.sName) {
|
||||
name = state.forceStringNoCtx(
|
||||
*attr.value,
|
||||
attr.pos,
|
||||
"while evaluating the `name` attribute passed to builtins.path"
|
||||
);
|
||||
} else if (n == "filter") {
|
||||
state.forceFunction(
|
||||
*(filterFun = attr.value),
|
||||
attr.pos,
|
||||
"while evaluating the `filter` parameter passed to builtins.path"
|
||||
);
|
||||
} else if (n == "recursive") {
|
||||
method = FileIngestionMethod{state.forceBool(
|
||||
*attr.value,
|
||||
attr.pos,
|
||||
"while evaluating the `recursive` attribute passed to builtins.path"
|
||||
)};
|
||||
} else if (n == "sha256") {
|
||||
expectedHash = newHashAllowEmpty(
|
||||
state.forceStringNoCtx(
|
||||
*attr.value,
|
||||
attr.pos,
|
||||
"while evaluating the `sha256` attribute passed to builtins.path"
|
||||
),
|
||||
htSHA256
|
||||
);
|
||||
} else {
|
||||
state
|
||||
.error<EvalError>(
|
||||
"unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]
|
||||
)
|
||||
.atPos(attr.pos)
|
||||
.debugThrow();
|
||||
}
|
||||
}
|
||||
if (!path) {
|
||||
state
|
||||
.error<EvalError>(
|
||||
"missing required 'path' attribute in the first argument to builtins.path"
|
||||
)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
if (name.empty()) {
|
||||
name = path->baseName();
|
||||
}
|
||||
|
||||
addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.pathExists
|
||||
*/
|
||||
|
||||
static void prim_pathExists(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
auto & arg = *args[0];
|
||||
|
||||
/* We don’t check the path right now, because we don’t want to
|
||||
throw if the path isn’t allowed, but just return false (and we
|
||||
can’t just catch the exception here because we still want to
|
||||
throw if something in the evaluation of `arg` tries to
|
||||
access an unauthorized path). */
|
||||
auto path = realisePath(state, pos, arg, {.checkForPureEval = false});
|
||||
|
||||
/* SourcePath doesn't know about trailing slash. */
|
||||
auto mustBeDir =
|
||||
arg.type() == nString && (arg.str().ends_with("/") || arg.str().ends_with("/."));
|
||||
|
||||
try {
|
||||
auto checked = state.checkSourcePath(path).resolveSymlinks(
|
||||
mustBeDir ? SymlinkResolution::Full : SymlinkResolution::Ancestors
|
||||
);
|
||||
|
||||
auto st = checked.maybeLstat();
|
||||
auto exists = st && (!mustBeDir || st->type == InputAccessor::tDirectory);
|
||||
v.mkBool(exists);
|
||||
} catch (SysError & e) {
|
||||
/* Don't give away info from errors while canonicalising
|
||||
‘path’ in restricted mode. */
|
||||
v.mkBool(false);
|
||||
} catch (RestrictedPathError & e) {
|
||||
v.mkBool(false);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.palceholder
|
||||
*/
|
||||
|
||||
/* Return a placeholder string for the specified output that will be
|
||||
substituted by the corresponding output path at build time. For
|
||||
example, 'placeholder "out"' returns the string
|
||||
/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build
|
||||
time, any occurrence of this string in an derivation attribute will
|
||||
be replaced with the concrete path in the Nix store of the output
|
||||
‘out’. */
|
||||
static void prim_placeholder(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
v.mkString(hashPlaceholder(state.forceStringNoCtx(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.placeholder"
|
||||
)));
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.toPath
|
||||
* WARNING: deprecated
|
||||
*/
|
||||
|
||||
static void prim_toPath(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
auto path = state.coerceToPath(
|
||||
pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath"
|
||||
);
|
||||
v.mkString(path.path.abs(), 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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.readDir
|
||||
*/
|
||||
|
||||
static void prim_readDir(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
|
||||
// Retrieve directory entries for all nodes in a directory.
|
||||
// This is similar to `getFileType` but is optimized to reduce system calls
|
||||
// on many systems.
|
||||
auto entries = path.readDirectory();
|
||||
auto attrs = state.buildBindings(entries.size());
|
||||
|
||||
// If we hit unknown directory entry types we may need to fallback to
|
||||
// using `getFileType` on some systems.
|
||||
// In order to reduce system calls we make each lookup lazy by using
|
||||
// `builtins.readFileType` application.
|
||||
Value * readFileType = nullptr;
|
||||
|
||||
for (auto & [name, type] : entries) {
|
||||
auto & attr = attrs.alloc(name);
|
||||
if (!type) {
|
||||
// Some filesystems or operating systems may not be able to return
|
||||
// detailed node info quickly in this case we produce a thunk to
|
||||
// query the file type lazily.
|
||||
auto epath = state.allocValue();
|
||||
epath->mkPath(path + name);
|
||||
if (!readFileType) {
|
||||
readFileType = &state.getBuiltin("readFileType");
|
||||
}
|
||||
attr.mkApp(readFileType, epath);
|
||||
} else {
|
||||
// This branch of the conditional is much more likely.
|
||||
// Here we just stringize the directory entry type.
|
||||
attr.mkString(fileTypeToString(*type));
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.readFile
|
||||
*/
|
||||
|
||||
static void prim_readFile(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
auto s = path.readFile();
|
||||
if (s.find((char) 0) != std::string::npos) {
|
||||
state
|
||||
.error<EvalError>(
|
||||
"the contents of the file '%1%' cannot be represented as a Nix string", path
|
||||
)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
StorePathSet refs;
|
||||
if (state.store->isInStore(path.path.abs())) {
|
||||
try {
|
||||
refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)
|
||||
->references;
|
||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||
}
|
||||
// Re-scan references to filter down to just the ones that actually occur in the file.
|
||||
auto refsSink = PathRefScanSink::fromPaths(refs);
|
||||
refsSink << s;
|
||||
refs = refsSink.getResultPaths();
|
||||
}
|
||||
NixStringContext context;
|
||||
for (auto && p : std::move(refs)) {
|
||||
context.insert(NixStringContextElem::Opaque{
|
||||
.path = std::move((StorePath &&) p),
|
||||
});
|
||||
}
|
||||
v.mkString(s, context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_readFile({
|
||||
.name = "__readFile",
|
||||
.args = {"path"},
|
||||
.doc = R"(
|
||||
Return the contents of the file *path* as a string.
|
||||
)",
|
||||
.fun = prim_readFile,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.readFileType
|
||||
*/
|
||||
|
||||
static void prim_readFileType(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
/* Retrieve the directory entry type and stringize it. */
|
||||
v.mkString(fileTypeToString(path.lstat().type));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_readFileType({
|
||||
.name = "__readFileType",
|
||||
.args = {"p"},
|
||||
.doc = R"(
|
||||
Determine the directory entry type of a filesystem node, being
|
||||
one of "directory", "regular", "symlink", or "unknown".
|
||||
)",
|
||||
.fun = prim_readFileType,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.storePath
|
||||
*/
|
||||
|
||||
static void prim_storePath(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
if (evalSettings.pureEval) {
|
||||
state.error<EvalError>("'%s' is not allowed in pure evaluation mode", "builtins.storePath")
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
|
||||
NixStringContext context;
|
||||
auto path = state
|
||||
.checkSourcePath(state.coerceToPath(
|
||||
pos,
|
||||
*args[0],
|
||||
context,
|
||||
"while evaluating the first argument passed to builtins.storePath"
|
||||
))
|
||||
.path;
|
||||
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
|
||||
directly in the store. The latter condition is necessary so
|
||||
e.g. nix-push does the right thing. */
|
||||
if (!state.store->isStorePath(path.abs())) {
|
||||
path = CanonPath(canonPath(path.abs(), true));
|
||||
}
|
||||
if (!state.store->isInStore(path.abs())) {
|
||||
state.error<EvalError>("path '%1%' is not in the Nix store", path).atPos(pos).debugThrow();
|
||||
}
|
||||
auto path2 = state.store->toStorePath(path.abs()).first;
|
||||
if (!settings.readOnlyMode) {
|
||||
state.store->ensurePath(path2);
|
||||
}
|
||||
context.insert(NixStringContextElem::Opaque{.path = path2});
|
||||
v.mkString(path.abs(), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_storePath({
|
||||
.name = "__storePath",
|
||||
.args = {"path"},
|
||||
.doc = R"(
|
||||
This function allows you to define a dependency on an already
|
||||
existing store path. For example, the derivation attribute `src
|
||||
= builtins.storePath /nix/store/f1d18v1y…-source` causes the
|
||||
derivation to depend on the specified path, which must exist or
|
||||
be substitutable. Note that this differs from a plain path
|
||||
(e.g. `src = /nix/store/f1d18v1y…-source`) in that the latter
|
||||
causes the path to be *copied* again to the Nix store, resulting
|
||||
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
|
||||
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
|
||||
)",
|
||||
.fun = prim_storePath,
|
||||
});
|
||||
|
||||
/**
|
||||
* builtins.toFile
|
||||
*/
|
||||
|
||||
static void prim_toFile(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||
{
|
||||
NixStringContext context;
|
||||
std::string name(state.forceStringNoCtx(
|
||||
*args[0], pos, "while evaluating the first argument passed to builtins.toFile"
|
||||
));
|
||||
std::string contents(state.forceString(
|
||||
*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"
|
||||
));
|
||||
|
||||
StorePathSet refs;
|
||||
|
||||
for (auto c : context) {
|
||||
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw)) {
|
||||
refs.insert(p->path);
|
||||
} else {
|
||||
state
|
||||
.error<EvalError>(
|
||||
"files created by %1% may not reference derivations, but %2% references %3%",
|
||||
"builtins.toFile",
|
||||
name,
|
||||
c.to_string()
|
||||
)
|
||||
.atPos(pos)
|
||||
.debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
auto storePath = settings.readOnlyMode
|
||||
? state.store->computeStorePathForText(name, contents, refs)
|
||||
: state.store->addTextToStore(name, contents, refs, state.repair);
|
||||
|
||||
/* Note: we don't need to add `context' to the context of the
|
||||
result, since `storePath' itself has references to the paths
|
||||
used in args[1]. */
|
||||
|
||||
/* Add the output of this to the allowed paths. */
|
||||
state.allowAndSetStorePathString(storePath, v);
|
||||
}
|
||||
|
||||
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 and its build script 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 a
|
||||
[string interpolation](@docroot@/language/values.md#type-string), 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,
|
||||
});
|
||||
|
||||
}
|
Loading…
Reference in a new issue