#include "libexpr/eval-inline.hh"
#include "libexpr/primops.hh"
#include "libstore/derivations.hh"
namespace nix {
static void prim_unsafeDiscardStringContext(EvalState& state, const Pos& pos,
Value** args, Value& v) {
PathSet context;
std::string s = state.coerceToString(pos, *args[0], context);
mkString(v, s, PathSet());
}
static RegisterPrimOp r1("__unsafeDiscardStringContext", 1,
prim_unsafeDiscardStringContext);
static void prim_hasContext(EvalState& state, const Pos& pos, Value** args,
Value& v) {
PathSet context;
state.forceString(*args[0], context, pos);
mkBool(v, !context.empty());
}
static RegisterPrimOp r2("__hasContext", 1, prim_hasContext);
/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
builder without causing the derivation to be built (for instance,
in the derivation that builds NARs in nix-push, when doing
source-only deployment). This primop marks the string context so
that builtins.derivation adds the path to drv.inputSrcs rather than
drv.inputDrvs. */
static void prim_unsafeDiscardOutputDependency(EvalState& state, const Pos& pos,
Value** args, Value& v) {
PathSet context;
std::string s = state.coerceToString(pos, *args[0], context);
PathSet context2;
for (auto& p : context) {
context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
}
mkString(v, s, context2);
}
static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1,
prim_unsafeDiscardOutputDependency);
/* Extract the context of a string as a structured Nix value.
The context is represented as an attribute set whose keys are the
paths in the context set and whose values are attribute sets with
the following keys:
path: True if the relevant path is in the context as a plain store
path (i.e. the kind of context you get when interpolating
a Nix path (e.g. ./.) into a string). False if missing.
allOutputs: True if the relevant path is a derivation and it is
in the context as a drv file with all of its outputs
(i.e. the kind of context you get when referencing
.drvPath of some derivation). False if missing.
outputs: If a non-empty list, the relevant path is a derivation
and the provided outputs are referenced in the context
(i.e. the kind of context you get when referencing
.outPath of some derivation). Empty list if missing.
Note that for a given path any combination of the above attributes
may be present.
*/
static void prim_getContext(EvalState& state, const Pos& pos, Value** args,
Value& v) {
struct ContextInfo {
bool path = false;
bool allOutputs = false;
Strings outputs;
};
PathSet context;
state.forceString(*args[0], context, pos);
auto contextInfos = std::map<Path, ContextInfo>();
for (const auto& p : context) {
Path drv;
std::string output;
const Path* path = &p;
if (p.at(0) == '=') {
drv = std::string(p, 1);
path = &drv;
} else if (p.at(0) == '!') {
std::pair<std::string, std::string> ctx = decodeContext(p);
drv = ctx.first;
output = ctx.second;
path = &drv;
}
auto isPath = drv.empty();
auto isAllOutputs = (!drv.empty()) && output.empty();
auto iter = contextInfos.find(*path);
if (iter == contextInfos.end()) {
contextInfos.emplace(
*path,
ContextInfo{isPath, isAllOutputs,
output.empty() ? Strings{} : Strings{std::move(output)}});
} else {
if (isPath) {
iter->second.path = true;
} else if (isAllOutputs) {
iter->second.allOutputs = true;
} else {
iter->second.outputs.emplace_back(std::move(output));
}
}
}
state.mkAttrs(v, contextInfos.size());
auto sPath = state.symbols.Create("path");
auto sAllOutputs = state.symbols.Create("allOutputs");
for (const auto& info : contextInfos) {
auto& infoVal = *state.allocAttr(v, state.symbols.Create(info.first));
state.mkAttrs(infoVal, 3);
if (info.second.path) {
mkBool(*state.allocAttr(infoVal, sPath), true);
}
if (info.second.allOutputs) {
mkBool(*state.allocAttr(infoVal, sAllOutputs), true);
}
if (!info.second.outputs.empty()) {
auto& outputsVal = *state.allocAttr(infoVal, state.sOutputs);
state.mkList(outputsVal, info.second.outputs.size());
size_t i = 0;
for (const auto& output : info.second.outputs) {
mkString(*((*outputsVal.list)[i++] = state.allocValue()), output);
}
}
}
}
static RegisterPrimOp r4("__getContext", 1, prim_getContext);
/* Append the given context to a given string.
See the commentary above unsafeGetContext for details of the
context representation.
*/
static void prim_appendContext(EvalState& state, const Pos& pos, Value** args,
Value& v) {
PathSet context;
auto orig = state.forceString(*args[0], context, pos);
state.forceAttrs(*args[1], pos);
auto sPath = state.symbols.Create("path");
auto sAllOutputs = state.symbols.Create("allOutputs");
for (const auto& attr_iter : *args[1]->attrs) {
const Attr* i = &attr_iter.second; // TODO(tazjin): get rid of this
if (!state.store->isStorePath(i->name)) {
throw EvalError("Context key '%s' is not a store path, at %s", i->name,
i->pos);
}
if (!settings.readOnlyMode) {
state.store->ensurePath(i->name);
}
state.forceAttrs(*i->value, *i->pos);
auto iter = i->value->attrs->find(sPath);
if (iter != i->value->attrs->end()) {
if (state.forceBool(*iter->second.value, *iter->second.pos)) {
context.insert(i->name);
}
}
iter = i->value->attrs->find(sAllOutputs);
if (iter != i->value->attrs->end()) {
if (state.forceBool(*iter->second.value, *iter->second.pos)) {
if (!isDerivation(i->name)) {
throw EvalError(
"Tried to add all-outputs context of %s, which is not a "
"derivation, to a string, at %s",
i->name, i->pos);
}
context.insert("=" + std::string(i->name));
}
}
iter = i->value->attrs->find(state.sOutputs);
if (iter != i->value->attrs->end()) {
state.forceList(*iter->second.value, *iter->second.pos);
if (iter->second.value->listSize() && !isDerivation(i->name)) {
throw EvalError(
"Tried to add derivation output context of %s, which is not a "
"derivation, to a string, at %s",
i->name, i->pos);
}
for (unsigned int n = 0; n < iter->second.value->listSize(); ++n) {
auto name = state.forceStringNoCtx(*(*iter->second.value->list)[n],
*iter->second.pos);
context.insert("!" + name + "!" + std::string(i->name));
}
}
}
mkString(v, orig, context);
}
static RegisterPrimOp r5("__appendContext", 2, prim_appendContext);
} // namespace nix