Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix IFD with CA derivations #5807

Merged
merged 2 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/libexpr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,10 @@ public:
/* Print statistics. */
void printStats();

void realiseContext(const PathSet & context);
/* Realise the given context, and return a mapping from the placeholders
* used to construct the associated value to their final store path
*/
[[nodiscard]] StringMap realiseContext(const PathSet & context);

private:

Expand Down
114 changes: 65 additions & 49 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ namespace nix {
InvalidPathError::InvalidPathError(const Path & path) :
EvalError("path '%s' is not valid", path), path(path) {}

void EvalState::realiseContext(const PathSet & context)
StringMap EvalState::realiseContext(const PathSet & context)
{
std::vector<DerivedPath::Built> drvs;
StringMap res;

for (auto & i : context) {
auto [ctxS, outputName] = decodeContext(i);
Expand All @@ -46,10 +47,12 @@ void EvalState::realiseContext(const PathSet & context)
throw InvalidPathError(store->printStorePath(ctx));
if (!outputName.empty() && ctx.isDerivation()) {
drvs.push_back({ctx, {outputName}});
} else {
res.insert_or_assign(ctxS, ctxS);
}
}

if (drvs.empty()) return;
if (drvs.empty()) return {};

if (!evalSettings.enableImportFromDerivation)
throw Error(
Expand All @@ -61,19 +64,43 @@ void EvalState::realiseContext(const PathSet & context)
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
store->buildPaths(buildReqs);

/* Get all the output paths corresponding to the placeholders we had */
for (auto & [drvPath, outputs] : drvs) {
auto outputPaths = store->queryDerivationOutputMap(drvPath);
for (auto & outputName : outputs) {
if (outputPaths.count(outputName) == 0)
throw Error("derivation '%s' does not have an output named '%s'",
store->printStorePath(drvPath), outputName);
res.insert_or_assign(
downstreamPlaceholder(*store, drvPath, outputName),
store->printStorePath(outputPaths.at(outputName))
);
}
}

/* Add the output of this derivations to the allowed
paths. */
if (allowedPaths) {
for (auto & [drvPath, outputs] : drvs) {
auto outputPaths = store->queryDerivationOutputMap(drvPath);
for (auto & outputName : outputs) {
if (outputPaths.count(outputName) == 0)
throw Error("derivation '%s' does not have an output named '%s'",
store->printStorePath(drvPath), outputName);
allowPath(outputPaths.at(outputName));
}
for (auto & [_placeholder, outputPath] : res) {
allowPath(outputPath);
}
}

return res;
}

static Path realisePath(EvalState & state, const Pos & pos, Value & v, bool requireAbsolutePath = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does requireAbsolutePath mean? I.e. when is it okay for it to be a relative path, and what is it relative to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requireAbsolutePath is here for the findFile primops (the desugaring of the angle brackets). Its first argument (the search path) can contain some strings with context, but these can be relative paths (which will then be relative to the cwd of the evaluator).

I’m not utterly happy having this flag just for that, but I’ve kept it to make sure to conserve the exact same semantics

(I had no idea that there could be strings-with-context here btw. Wonder whether that’s actually used anywhere)

{
PathSet context;

Path path = requireAbsolutePath
? state.coerceToPath(pos, v, context)
: state.coerceToString(pos, v, context, false, false);

StringMap rewrites = state.realiseContext(context);

return state.checkSourcePath(
state.toRealPath(rewriteStrings(path, rewrites), context));
}

/* Add and attribute to the given attribute map from the output name to
Expand Down Expand Up @@ -109,11 +136,9 @@ static void mkOutputString(EvalState & state, Value & v,
argument. */
static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vScope, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, vPath, context);

Path path;
try {
state.realiseContext(context);
path = realisePath(state, pos, vPath);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path),
Expand All @@ -124,8 +149,6 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
throw;
}

Path realPath = state.checkSourcePath(state.toRealPath(path, context));

// FIXME
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
if (!state.store->isStorePath(path))
Expand Down Expand Up @@ -177,7 +200,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS

else {
if (!vScope)
state.evalFile(realPath, v);
state.evalFile(path, v);
else {
state.forceAttrs(*vScope);

Expand All @@ -195,8 +218,8 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.

printTalkative("evaluating file '%1%'", realPath);
Expr * e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv);
printTalkative("evaluating file '%1%'", path);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);

e->eval(state, *env, v);
}
Expand Down Expand Up @@ -281,22 +304,19 @@ extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
/* Load a ValueInitializer from a DSO and return whatever it initializes */
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context);

Path path;
try {
state.realiseContext(context);
path = realisePath(state, pos, *args[0]);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt(
"cannot import '%1%', since path '%2%' is not valid",
path, e.path),
.msg = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
} catch (Error & e) {
e.addTrace(pos, "while importing '%s'", path);
throw;
}

path = state.checkSourcePath(path);

string sym = state.forceStringNoCtx(*args[1], pos);

void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
Expand Down Expand Up @@ -338,7 +358,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
for (unsigned int i = 1; i < args[0]->listSize(); ++i)
commandArgs.emplace_back(state.coerceToString(pos, *elems[i], context, false, false));
try {
state.realiseContext(context);
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
Expand Down Expand Up @@ -1349,10 +1369,9 @@ static RegisterPrimOp primop_storePath({

static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context);
Path path;
try {
state.realiseContext(context);
path = realisePath(state, pos, *args[0]);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt(
Expand All @@ -1363,7 +1382,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
}

try {
mkBool(v, pathExists(state.checkSourcePath(path)));
mkBool(v, pathExists(path));
} catch (SysError & e) {
/* Don't give away info from errors while canonicalising
‘path’ in restricted mode. */
Expand Down Expand Up @@ -1426,17 +1445,16 @@ static RegisterPrimOp primop_dirOf({
/* Return the contents of a file as a string. */
static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
Path path = state.coerceToPath(pos, *args[0], context);
Path path;
try {
state.realiseContext(context);
path = realisePath(state, pos, *args[0]);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}
string s = readFile(state.checkSourcePath(state.toRealPath(path, context)));
string s = readFile(path);
if (s.find((char) 0) != string::npos)
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
mkString(v, s.c_str());
Expand Down Expand Up @@ -1475,11 +1493,10 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
pos
);

PathSet context;
string path = state.coerceToString(pos, *i->value, context, false, false);
Path path;

try {
state.realiseContext(context);
path = realisePath(state, pos, *i->value, false);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
Expand Down Expand Up @@ -1512,15 +1529,14 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
.errPos = pos
});

PathSet context;
Path path = state.coerceToPath(pos, *args[1], context);
Path path;
try {
state.realiseContext(context);
path = realisePath(state, pos, *args[1]);
} catch (InvalidPathError & e) {
throw EvalError("cannot read '%s' since path '%s' is not valid, at %s", path, e.path, pos);
}

mkString(v, hashFile(*ht, state.checkSourcePath(state.toRealPath(path, context))).to_string(Base16, false));
mkString(v, hashFile(*ht, path).to_string(Base16, false));
}

static RegisterPrimOp primop_hashFile({
Expand All @@ -1537,18 +1553,17 @@ static RegisterPrimOp primop_hashFile({
/* Read a directory (without . or ..) */
static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet ctx;
Path path = state.coerceToPath(pos, *args[0], ctx);
Path path;
try {
state.realiseContext(ctx);
path = realisePath(state, pos, *args[0]);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}

DirEntries entries = readDirectory(state.checkSourcePath(path));
DirEntries entries = readDirectory(path);
state.mkAttrs(v, entries.size());

for (auto & ent : entries) {
Expand Down Expand Up @@ -1875,7 +1890,8 @@ static void addPath(
try {
// FIXME: handle CA derivation outputs (where path needs to
// be rewritten to the actual output).
state.realiseContext(context);
auto rewrites = state.realiseContext(context);
path = state.toRealPath(rewriteStrings(path, rewrites), context);

StorePathSet refs;

Expand Down
6 changes: 6 additions & 0 deletions tests/ca/import-derivation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source common.sh

export NIX_TESTS_CA_BY_DEFAULT=1

cd .. && source import-derivation.sh

2 changes: 1 addition & 1 deletion tests/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ nix_tests = \
local-store.sh remote-store.sh export.sh export-graph.sh \
db-migration.sh \
timeout.sh secure-drv-outputs.sh nix-channel.sh \
multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh \
multiple-outputs.sh import-derivation.sh ca/import-derivation.sh fetchurl.sh optimise-store.sh \
binary-cache.sh \
substitute-with-invalid-ca.sh \
binary-cache-build-remote.sh \
Expand Down