forked from lix-project/lix
Compare commits
19 commits
main
...
sb/tom-hub
Author | SHA1 | Date | |
---|---|---|---|
Tom Hubrecht | 127ee1abe7 | ||
Tom Hubrecht | 5541723b60 | ||
Tom Hubrecht | 09139554a8 | ||
Tom Hubrecht | 8035abdc37 | ||
Tom Hubrecht | c96f5bcdd2 | ||
Tom Hubrecht | a93af3f92f | ||
Tom Hubrecht | c3ffa21778 | ||
Tom Hubrecht | 2279c39bea | ||
Tom Hubrecht | 0bf532b0c1 | ||
Tom Hubrecht | 1e1cc1d741 | ||
Tom Hubrecht | 4e448e78f7 | ||
Tom Hubrecht | 468d30e053 | ||
Tom Hubrecht | d3b1d1fb37 | ||
Tom Hubrecht | 00416ee50b | ||
Tom Hubrecht | 7f1ebde7b8 | ||
Tom Hubrecht | 95968c44eb | ||
Tom Hubrecht | 7f9f2f7835 | ||
Tom Hubrecht | 84e80fa97d | ||
Tom Hubrecht | 0ba37444da |
|
@ -86,11 +86,25 @@ libexpr_sources = files(
|
||||||
'flake/flake.cc',
|
'flake/flake.cc',
|
||||||
'flake/flakeref.cc',
|
'flake/flakeref.cc',
|
||||||
'flake/lockfile.cc',
|
'flake/lockfile.cc',
|
||||||
|
'primops/arithmetic.cc',
|
||||||
|
'primops/attrset.cc',
|
||||||
'primops/context.cc',
|
'primops/context.cc',
|
||||||
|
'primops/control.cc',
|
||||||
|
'primops/debug.cc',
|
||||||
|
'primops/derivation.cc',
|
||||||
|
'primops/hash.cc',
|
||||||
'primops/fetchClosure.cc',
|
'primops/fetchClosure.cc',
|
||||||
'primops/fetchMercurial.cc',
|
'primops/fetchMercurial.cc',
|
||||||
'primops/fetchTree.cc',
|
'primops/fetchTree.cc',
|
||||||
'primops/fromTOML.cc',
|
'primops/fromTOML.cc',
|
||||||
|
'primops/import.cc',
|
||||||
|
'primops/json.cc',
|
||||||
|
'primops/list.cc',
|
||||||
|
'primops/path.cc',
|
||||||
|
'primops/string.cc',
|
||||||
|
'primops/system.cc',
|
||||||
|
'primops/toXML.cc',
|
||||||
|
'primops/types.cc',
|
||||||
'value/context.cc',
|
'value/context.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "derivations.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
||||||
#include <tuple>
|
#include <regex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -44,13 +45,150 @@ struct RegisterPrimOp
|
||||||
/**
|
/**
|
||||||
* Load a ValueInitializer from a DSO and return whatever it initializes
|
* Load a ValueInitializer from a DSO and return whatever it initializes
|
||||||
*/
|
*/
|
||||||
void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
void prim_importNative(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a program and parse its output
|
* Execute a program and parse its output
|
||||||
*/
|
*/
|
||||||
void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
void prim_exec(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
|
void prim_lessThan(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
|
void prim_importNative(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
|
void prim_trace(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
|
void prim_second(EvalState & state, const PosIdx pos, Value ** args, Value & v);
|
||||||
|
|
||||||
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column);
|
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column);
|
||||||
|
|
||||||
|
void mkOutputString(
|
||||||
|
EvalState & state,
|
||||||
|
BindingsBuilder & attrs,
|
||||||
|
const StorePath & drvPath,
|
||||||
|
const std::pair<std::string, DerivationOutput> & o
|
||||||
|
);
|
||||||
|
|
||||||
|
#if HAVE_BOEHMGC
|
||||||
|
typedef std::list<Value *, gc_allocator<Value *>> ValueList;
|
||||||
|
#else
|
||||||
|
typedef std::list<Value *> ValueList;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getAttr wrapper
|
||||||
|
*/
|
||||||
|
|
||||||
|
Bindings::iterator
|
||||||
|
getAttr(EvalState & state, Symbol attrSym, Bindings * attrSet, std::string_view errorCtx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Struct definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct RegexCache
|
||||||
|
{
|
||||||
|
// TODO use C++20 transparent comparison when available
|
||||||
|
std::unordered_map<std::string_view, std::regex> cache;
|
||||||
|
std::list<std::string> keys;
|
||||||
|
|
||||||
|
std::regex get(std::string_view re)
|
||||||
|
{
|
||||||
|
auto it = cache.find(re);
|
||||||
|
if (it != cache.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
keys.emplace_back(re);
|
||||||
|
return cache.emplace(keys.back(), std::regex(keys.back(), std::regex::extended))
|
||||||
|
.first->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompareValues
|
||||||
|
{
|
||||||
|
EvalState & state;
|
||||||
|
const PosIdx pos;
|
||||||
|
const std::string_view errorCtx;
|
||||||
|
|
||||||
|
CompareValues(EvalState & state, const PosIdx pos, const std::string_view && errorCtx)
|
||||||
|
: state(state)
|
||||||
|
, pos(pos)
|
||||||
|
, errorCtx(errorCtx){};
|
||||||
|
|
||||||
|
bool operator()(Value * v1, Value * v2) const
|
||||||
|
{
|
||||||
|
return (*this)(v1, v2, errorCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(Value * v1, Value * v2, std::string_view errorCtx) const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (v1->type() == nFloat && v2->type() == nInt) {
|
||||||
|
return v1->fpoint < v2->integer;
|
||||||
|
}
|
||||||
|
if (v1->type() == nInt && v2->type() == nFloat) {
|
||||||
|
return v1->integer < v2->fpoint;
|
||||||
|
}
|
||||||
|
if (v1->type() != v2->type()) {
|
||||||
|
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2))
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
// Allow selecting a subset of enum values
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||||
|
switch (v1->type()) {
|
||||||
|
case nInt:
|
||||||
|
return v1->integer < v2->integer;
|
||||||
|
case nFloat:
|
||||||
|
return v1->fpoint < v2->fpoint;
|
||||||
|
case nString:
|
||||||
|
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||||
|
case nPath:
|
||||||
|
return strcmp(v1->_path, v2->_path) < 0;
|
||||||
|
case nList:
|
||||||
|
// Lexicographic comparison
|
||||||
|
for (size_t i = 0;; i++) {
|
||||||
|
if (i == v2->listSize()) {
|
||||||
|
return false;
|
||||||
|
} else if (i == v1->listSize()) {
|
||||||
|
return true;
|
||||||
|
} else if (!state.eqValues(
|
||||||
|
*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return (*this)(
|
||||||
|
v1->listElems()[i],
|
||||||
|
v2->listElems()[i],
|
||||||
|
"while comparing two list elements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"cannot compare %s with %s; values of that type are incomparable",
|
||||||
|
showType(*v1),
|
||||||
|
showType(*v2)
|
||||||
|
)
|
||||||
|
.debugThrow();
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
if (!errorCtx.empty()) {
|
||||||
|
e.addTrace(nullptr, errorCtx);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RealisePathFlags
|
||||||
|
{
|
||||||
|
// Whether to check that the path is allowed in pure eval mode
|
||||||
|
bool checkForPureEval = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
SourcePath
|
||||||
|
realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
312
src/libexpr/primops/arithmetic.cc
Normal file
312
src/libexpr/primops/arithmetic.cc
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
#include <math.h> // for ceil, floor
|
||||||
|
#include <limits> // for numeric_limits
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "primops.hh" // for CompareValues, prim_lessThan
|
||||||
|
#include "value.hh" // for Value, nFloat, NixInt, NixFloat
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.add
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_add(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||||
|
v.mkFloat(
|
||||||
|
state.forceFloat(*args[0], pos, "while evaluating the first argument of the addition")
|
||||||
|
+ state.forceFloat(
|
||||||
|
*args[1], pos, "while evaluating the second argument of the addition"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(*args[0], pos, "while evaluating the first argument of the addition")
|
||||||
|
+ state.forceInt(*args[1], pos, "while evaluating the second argument of the addition")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_add({
|
||||||
|
.name = "__add",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the sum of the numbers *e1* and *e2*.
|
||||||
|
Return a float if either *e1* or *e2* is a float, otherwise
|
||||||
|
return an integer.
|
||||||
|
)",
|
||||||
|
.fun = prim_add,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.bitAnd
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_bitAnd(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd"
|
||||||
|
)
|
||||||
|
& state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_bitAnd({
|
||||||
|
.name = "__bitAnd",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the bitwise AND of the integers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_bitAnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.bitOr
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_bitOr(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.bitOr"
|
||||||
|
)
|
||||||
|
| state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.bitOr"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_bitOr({
|
||||||
|
.name = "__bitOr",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the bitwise OR of the integers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_bitOr,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.bitXor
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_bitXor(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.bitXor"
|
||||||
|
)
|
||||||
|
^ state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.bitXor"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_bitXor({
|
||||||
|
.name = "__bitXor",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the bitwise XOR of the integers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_bitXor,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.ceil
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_ceil(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto value = state.forceFloat(
|
||||||
|
*args[0],
|
||||||
|
args[0]->determinePos(pos),
|
||||||
|
"while evaluating the first argument passed to builtins.ceil"
|
||||||
|
);
|
||||||
|
v.mkInt(ceil(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_ceil({
|
||||||
|
.name = "__ceil",
|
||||||
|
.args = {"double"},
|
||||||
|
.doc = R"(
|
||||||
|
Converts an IEEE-754 double-precision floating-point number (*double*) to
|
||||||
|
the next higher integer.
|
||||||
|
|
||||||
|
If the datatype is neither an integer nor a "float", an evaluation error will be
|
||||||
|
thrown.
|
||||||
|
)",
|
||||||
|
.fun = prim_ceil,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.div
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_div(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
|
||||||
|
NixFloat f2 =
|
||||||
|
state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
|
||||||
|
if (f2 == 0) {
|
||||||
|
state.error<EvalError>("division by zero").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||||
|
v.mkFloat(
|
||||||
|
state.forceFloat(*args[0], pos, "while evaluating the first operand of the division")
|
||||||
|
/ f2
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
NixInt i1 =
|
||||||
|
state.forceInt(*args[0], pos, "while evaluating the first operand of the division");
|
||||||
|
NixInt i2 =
|
||||||
|
state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
|
||||||
|
/* Avoid division overflow as it might raise SIGFPE. */
|
||||||
|
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) {
|
||||||
|
state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkInt(i1 / i2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_div({
|
||||||
|
.name = "__div",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the quotient of the numbers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_div,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.floor
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_floor(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto value = state.forceFloat(
|
||||||
|
*args[0],
|
||||||
|
args[0]->determinePos(pos),
|
||||||
|
"while evaluating the first argument passed to builtins.floor"
|
||||||
|
);
|
||||||
|
v.mkInt(floor(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_floor({
|
||||||
|
.name = "__floor",
|
||||||
|
.args = {"double"},
|
||||||
|
.doc = R"(
|
||||||
|
Converts an IEEE-754 double-precision floating-point number (*double*) to
|
||||||
|
the next lower integer.
|
||||||
|
|
||||||
|
If the datatype is neither an integer nor a "float", an evaluation error will be
|
||||||
|
thrown.
|
||||||
|
)",
|
||||||
|
.fun = prim_floor,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.lessThan
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_lessThan(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
// pos is exact here, no need for a message.
|
||||||
|
CompareValues comp(state, noPos, "");
|
||||||
|
v.mkBool(comp(args[0], args[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_lessThan({
|
||||||
|
.name = "__lessThan",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if the number *e1* is less than the number *e2*, and
|
||||||
|
`false` otherwise. Evaluation aborts if either *e1* or *e2* does not
|
||||||
|
evaluate to a number.
|
||||||
|
)",
|
||||||
|
.fun = prim_lessThan,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.mul
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_mul(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||||
|
v.mkFloat(
|
||||||
|
state.forceFloat(*args[0], pos, "while evaluating the first of the multiplication")
|
||||||
|
* state.forceFloat(
|
||||||
|
*args[1], pos, "while evaluating the second argument of the multiplication"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(
|
||||||
|
*args[0], pos, "while evaluating the first argument of the multiplication"
|
||||||
|
)
|
||||||
|
* state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument of the multiplication"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_mul({
|
||||||
|
.name = "__mul",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the product of the numbers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_mul,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.sub
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_sub(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||||
|
v.mkFloat(
|
||||||
|
state.forceFloat(
|
||||||
|
*args[0], pos, "while evaluating the first argument of the subtraction"
|
||||||
|
)
|
||||||
|
- state.forceFloat(
|
||||||
|
*args[1], pos, "while evaluating the second argument of the subtraction"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
v.mkInt(
|
||||||
|
state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction")
|
||||||
|
- state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument of the subtraction"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_sub({
|
||||||
|
.name = "__sub",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the difference between the numbers *e1* and *e2*.
|
||||||
|
)",
|
||||||
|
.fun = prim_sub,
|
||||||
|
});
|
||||||
|
}
|
698
src/libexpr/primops/attrset.cc
Normal file
698
src/libexpr/primops/attrset.cc
Normal file
|
@ -0,0 +1,698 @@
|
||||||
|
#include <string.h> // for size_t, memcpy, strcmp
|
||||||
|
#include <algorithm> // for sort, min, set_difference
|
||||||
|
#include <boost/container/small_vector.hpp> // for small_vector
|
||||||
|
#include <boost/container/vector.hpp> // for operator+, operator-
|
||||||
|
#include <iterator> // for back_insert_iterator
|
||||||
|
#include <map> // for map, _Rb_tree_iterator
|
||||||
|
#include <set> // for set
|
||||||
|
#include <string_view> // for string_view, operator<=>
|
||||||
|
#include <utility> // for pair
|
||||||
|
#include <vector> // for vector
|
||||||
|
#include "attr-set.hh" // for Attr, Bindings, Bindings...
|
||||||
|
#include "eval-error.hh" // for TypeError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp, prim_...
|
||||||
|
#include "gc-small-vector.hh" // for SmallValueVector
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "primops.hh" // for CompareValues, ValueList
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolTable, Sym...
|
||||||
|
#include "value.hh" // for Value, ValueVector, Valu...
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
Bindings::iterator
|
||||||
|
getAttr(EvalState & state, Symbol attrSym, Bindings * attrSet, std::string_view errorCtx)
|
||||||
|
{
|
||||||
|
Bindings::iterator value = attrSet->find(attrSym);
|
||||||
|
if (value == attrSet->end()) {
|
||||||
|
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym])
|
||||||
|
.withTrace(noPos, errorCtx)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.attrNames
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_attrNames(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames");
|
||||||
|
|
||||||
|
state.mkList(v, args[0]->attrs->size());
|
||||||
|
|
||||||
|
size_t n = 0;
|
||||||
|
for (auto & i : *args[0]->attrs) {
|
||||||
|
(v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(v.listElems(), v.listElems() + n, [](Value * v1, Value * v2) {
|
||||||
|
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_attrNames({
|
||||||
|
.name = "__attrNames",
|
||||||
|
.args = {"set"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the names of the attributes in the set *set* in an
|
||||||
|
alphabetically sorted list. For instance, `builtins.attrNames { y
|
||||||
|
= 1; x = "foo"; }` evaluates to `[ "x" "y" ]`.
|
||||||
|
)",
|
||||||
|
.fun = prim_attrNames,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.attrNames
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_attrValues(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues");
|
||||||
|
|
||||||
|
state.mkList(v, args[0]->attrs->size());
|
||||||
|
|
||||||
|
unsigned int n = 0;
|
||||||
|
for (auto & i : *args[0]->attrs) {
|
||||||
|
v.listElems()[n++] = (Value *) &i;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(v.listElems(), v.listElems() + n, [&](Value * v1, Value * v2) {
|
||||||
|
std::string_view s1 = state.symbols[((Attr *) v1)->name],
|
||||||
|
s2 = state.symbols[((Attr *) v2)->name];
|
||||||
|
return s1 < s2;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < n; ++i) {
|
||||||
|
v.listElems()[i] = ((Attr *) v.listElems()[i])->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_attrValues({
|
||||||
|
.name = "__attrValues",
|
||||||
|
.args = {"set"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the values of the attributes in the set *set* in the order
|
||||||
|
corresponding to the sorted attribute names.
|
||||||
|
)",
|
||||||
|
.fun = prim_attrValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.catAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_catAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto attrName = state.symbols.create(state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.catAttrs"
|
||||||
|
));
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.catAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
SmallValueVector<nonRecursiveStackReservation> res(args[1]->listSize());
|
||||||
|
size_t found = 0;
|
||||||
|
|
||||||
|
for (auto v2 : args[1]->listItems()) {
|
||||||
|
state.forceAttrs(
|
||||||
|
*v2,
|
||||||
|
pos,
|
||||||
|
"while evaluating an element in the list passed as second argument to builtins.catAttrs"
|
||||||
|
);
|
||||||
|
Bindings::iterator i = v2->attrs->find(attrName);
|
||||||
|
if (i != v2->attrs->end()) {
|
||||||
|
res[found++] = i->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.mkList(v, found);
|
||||||
|
for (unsigned int n = 0; n < found; ++n) {
|
||||||
|
v.listElems()[n] = res[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_catAttrs({
|
||||||
|
.name = "__catAttrs",
|
||||||
|
.args = {"attr", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Collect each attribute named *attr* from a list of attribute
|
||||||
|
sets. Attrsets that don't contain the named attribute are
|
||||||
|
ignored. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `[1 2]`.
|
||||||
|
)",
|
||||||
|
.fun = prim_catAttrs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.genericClosure
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_genericClosure(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Get the start set. */
|
||||||
|
Bindings::iterator startSet = getAttr(
|
||||||
|
state,
|
||||||
|
state.sStartSet,
|
||||||
|
args[0]->attrs,
|
||||||
|
"in the attrset passed as argument to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.forceList(
|
||||||
|
*startSet->value,
|
||||||
|
noPos,
|
||||||
|
"while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
ValueList workSet;
|
||||||
|
for (auto elem : startSet->value->listItems()) {
|
||||||
|
workSet.push_back(elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startSet->value->listSize() == 0) {
|
||||||
|
v = *startSet->value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the operator. */
|
||||||
|
Bindings::iterator op = getAttr(
|
||||||
|
state,
|
||||||
|
state.sOperator,
|
||||||
|
args[0]->attrs,
|
||||||
|
"in the attrset passed as argument to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
state.forceFunction(
|
||||||
|
*op->value,
|
||||||
|
noPos,
|
||||||
|
"while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Construct the closure by applying the operator to elements of
|
||||||
|
`workSet', adding the result to `workSet', continuing until
|
||||||
|
no new elements are found. */
|
||||||
|
ValueList res;
|
||||||
|
// `doneKeys' doesn't need to be a GC root, because its values are
|
||||||
|
// reachable from res.
|
||||||
|
auto cmp = CompareValues(
|
||||||
|
state, noPos, "while comparing the `key` attributes of two genericClosure elements"
|
||||||
|
);
|
||||||
|
std::set<Value *, decltype(cmp)> doneKeys(cmp);
|
||||||
|
while (!workSet.empty()) {
|
||||||
|
Value * e = *(workSet.begin());
|
||||||
|
workSet.pop_front();
|
||||||
|
|
||||||
|
state.forceAttrs(
|
||||||
|
*e,
|
||||||
|
noPos,
|
||||||
|
"while evaluating one of the elements generated by (or initially passed to) "
|
||||||
|
"builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
Bindings::iterator key = getAttr(
|
||||||
|
state,
|
||||||
|
state.sKey,
|
||||||
|
e->attrs,
|
||||||
|
"in one of the attrsets generated by (or initially passed to) builtins.genericClosure"
|
||||||
|
);
|
||||||
|
state.forceValue(*key->value, noPos);
|
||||||
|
|
||||||
|
if (!doneKeys.insert(key->value).second) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res.push_back(e);
|
||||||
|
|
||||||
|
/* Call the `operator' function with `e' as argument. */
|
||||||
|
Value newElements;
|
||||||
|
state.callFunction(*op->value, 1, &e, newElements, noPos);
|
||||||
|
state.forceList(
|
||||||
|
newElements,
|
||||||
|
noPos,
|
||||||
|
"while evaluating the return value of the `operator` passed to builtins.genericClosure"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Add the values returned by the operator to the work set. */
|
||||||
|
for (auto elem : newElements.listItems()) {
|
||||||
|
state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by
|
||||||
|
// the `operator` passed to builtins.genericClosure");
|
||||||
|
workSet.push_back(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the result list. */
|
||||||
|
state.mkList(v, res.size());
|
||||||
|
unsigned int n = 0;
|
||||||
|
for (auto & i : res) {
|
||||||
|
v.listElems()[n++] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_genericClosure(PrimOp{
|
||||||
|
.name = "__genericClosure",
|
||||||
|
.args = {"attrset"},
|
||||||
|
.arity = 1,
|
||||||
|
.doc = R"(
|
||||||
|
Take an *attrset* with values named `startSet` and `operator` in order to
|
||||||
|
return a *list of attrsets* by starting with the `startSet` and recursively
|
||||||
|
applying the `operator` function to each `item`. The *attrsets* in the
|
||||||
|
`startSet` and the *attrsets* produced by `operator` must contain a value
|
||||||
|
named `key` which is comparable. The result is produced by calling `operator`
|
||||||
|
for each `item` with a value for `key` that has not been called yet including
|
||||||
|
newly produced `item`s. The function terminates when no new `item`s are
|
||||||
|
produced. The resulting *list of attrsets* contains only *attrsets* with a
|
||||||
|
unique key. For example,
|
||||||
|
|
||||||
|
```
|
||||||
|
builtins.genericClosure {
|
||||||
|
startSet = [ {key = 5;} ];
|
||||||
|
operator = item: [{
|
||||||
|
key = if (item.key / 2 ) * 2 == item.key
|
||||||
|
then item.key / 2
|
||||||
|
else 3 * item.key + 1;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
evaluates to
|
||||||
|
```
|
||||||
|
[ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ]
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_genericClosure,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.getAttr
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_getAttr(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto attr = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.getAttr"
|
||||||
|
);
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.getAttr"
|
||||||
|
);
|
||||||
|
Bindings::iterator i = getAttr(
|
||||||
|
state,
|
||||||
|
state.symbols.create(attr),
|
||||||
|
args[1]->attrs,
|
||||||
|
"in the attribute set under consideration"
|
||||||
|
);
|
||||||
|
// !!! add to stack trace?
|
||||||
|
if (state.countCalls && i->pos) {
|
||||||
|
state.attrSelects[i->pos]++;
|
||||||
|
}
|
||||||
|
state.forceValue(*i->value, pos);
|
||||||
|
v = *i->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_getAttr({
|
||||||
|
.name = "__getAttr",
|
||||||
|
.args = {"s", "set"},
|
||||||
|
.doc = R"(
|
||||||
|
`getAttr` returns the attribute named *s* from *set*. Evaluation
|
||||||
|
aborts if the attribute doesn’t exist. This is a dynamic version of
|
||||||
|
the `.` operator, since *s* is an expression rather than an
|
||||||
|
identifier.
|
||||||
|
)",
|
||||||
|
.fun = prim_getAttr,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.groupBy
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_groupBy(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.groupBy"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.groupBy"
|
||||||
|
);
|
||||||
|
|
||||||
|
ValueVectorMap attrs;
|
||||||
|
|
||||||
|
for (auto vElem : args[1]->listItems()) {
|
||||||
|
Value res;
|
||||||
|
state.callFunction(*args[0], *vElem, res, pos);
|
||||||
|
auto name = state.forceStringNoCtx(
|
||||||
|
res,
|
||||||
|
pos,
|
||||||
|
"while evaluating the return value of the grouping function passed to builtins.groupBy"
|
||||||
|
);
|
||||||
|
auto sym = state.symbols.create(name);
|
||||||
|
auto vector = attrs.try_emplace(sym, ValueVector()).first;
|
||||||
|
vector->second.push_back(vElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attrs2 = state.buildBindings(attrs.size());
|
||||||
|
|
||||||
|
for (auto & i : attrs) {
|
||||||
|
auto & list = attrs2.alloc(i.first);
|
||||||
|
auto size = i.second.size();
|
||||||
|
state.mkList(list, size);
|
||||||
|
memcpy(list.listElems(), i.second.data(), sizeof(Value *) * size);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs2.alreadySorted());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_groupBy({
|
||||||
|
.name = "__groupBy",
|
||||||
|
.args = {"f", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Groups elements of *list* together by the string returned from the
|
||||||
|
function *f* called on each element. It returns an attribute set
|
||||||
|
where each attribute value contains the elements of *list* that are
|
||||||
|
mapped to the same corresponding attribute name returned by *f*.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.groupBy (builtins.substring 0 1) ["foo" "bar" "baz"]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ b = [ "bar" "baz" ]; f = [ "foo" ]; }
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_groupBy,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.hasAttr
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_hasAttr(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto attr = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.hasAttr"
|
||||||
|
);
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.hasAttr"
|
||||||
|
);
|
||||||
|
v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_hasAttr({
|
||||||
|
.name = "__hasAttr",
|
||||||
|
.args = {"s", "set"},
|
||||||
|
.doc = R"(
|
||||||
|
`hasAttr` returns `true` if *set* has an attribute named *s*, and
|
||||||
|
`false` otherwise. This is a dynamic version of the `?` operator,
|
||||||
|
since *s* is an expression rather than an identifier.
|
||||||
|
)",
|
||||||
|
.fun = prim_hasAttr,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.intersectAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.intersectAttrs"
|
||||||
|
);
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
Bindings & left = *args[0]->attrs;
|
||||||
|
Bindings & right = *args[1]->attrs;
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(std::min(left.size(), right.size()));
|
||||||
|
|
||||||
|
// The current implementation has good asymptotic complexity and is reasonably
|
||||||
|
// simple. Further optimization may be possible, but does not seem productive,
|
||||||
|
// considering the state of eval performance in 2022.
|
||||||
|
//
|
||||||
|
// I have looked for reusable and/or standard solutions and these are my
|
||||||
|
// findings:
|
||||||
|
//
|
||||||
|
// STL
|
||||||
|
// ===
|
||||||
|
// std::set_intersection is not suitable, as it only performs a simultaneous
|
||||||
|
// linear scan; not taking advantage of random access. This is O(n + m), so
|
||||||
|
// linear in the largest set, which is not acceptable for callPackage in Nixpkgs.
|
||||||
|
//
|
||||||
|
// Simultaneous scan, with alternating simple binary search
|
||||||
|
// ===
|
||||||
|
// One alternative algorithm scans the attrsets simultaneously, jumping
|
||||||
|
// forward using `lower_bound` in case of inequality. This should perform
|
||||||
|
// well on very similar sets, having a local and predictable access pattern.
|
||||||
|
// On dissimilar sets, it seems to need more comparisons than the current
|
||||||
|
// algorithm, as few consecutive attrs match. `lower_bound` could take
|
||||||
|
// advantage of the decreasing remaining search space, but this causes
|
||||||
|
// the medians to move, which can mean that they don't stay in the cache
|
||||||
|
// like they would with the current naive `find`.
|
||||||
|
//
|
||||||
|
// Double binary search
|
||||||
|
// ===
|
||||||
|
// The optimal algorithm may be "Double binary search", which doesn't
|
||||||
|
// scan at all, but rather divides both sets simultaneously.
|
||||||
|
// See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al.
|
||||||
|
// https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf
|
||||||
|
// The only downsides I can think of are not having a linear access pattern
|
||||||
|
// for similar sets, and having to maintain a more intricate algorithm.
|
||||||
|
//
|
||||||
|
// Adaptive
|
||||||
|
// ===
|
||||||
|
// Finally one could run try a simultaneous scan, count misses and fall back
|
||||||
|
// to double binary search when the counter hit some threshold and/or ratio.
|
||||||
|
|
||||||
|
if (left.size() < right.size()) {
|
||||||
|
for (auto & l : left) {
|
||||||
|
Bindings::iterator r = right.find(l.name);
|
||||||
|
if (r != right.end()) {
|
||||||
|
attrs.insert(*r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto & r : right) {
|
||||||
|
Bindings::iterator l = left.find(r.name);
|
||||||
|
if (l != left.end()) {
|
||||||
|
attrs.insert(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_intersectAttrs({
|
||||||
|
.name = "__intersectAttrs",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a set consisting of the attributes in the set *e2* which have the
|
||||||
|
same name as some attribute in *e1*.
|
||||||
|
|
||||||
|
Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size.
|
||||||
|
)",
|
||||||
|
.fun = prim_intersectAttrs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.mapAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.mapAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(args[1]->attrs->size());
|
||||||
|
|
||||||
|
for (auto & i : *args[1]->attrs) {
|
||||||
|
Value * vName = state.allocValue();
|
||||||
|
Value * vFun2 = state.allocValue();
|
||||||
|
vName->mkString(state.symbols[i.name]);
|
||||||
|
vFun2->mkApp(args[0], vName);
|
||||||
|
attrs.alloc(i.name).mkApp(vFun2, i.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_mapAttrs({
|
||||||
|
.name = "__mapAttrs",
|
||||||
|
.args = {"f", "attrset"},
|
||||||
|
.doc = R"(
|
||||||
|
Apply function *f* to every element of *attrset*. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `{ a = 10; b = 20; }`.
|
||||||
|
)",
|
||||||
|
.fun = prim_mapAttrs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.mapAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.removeAttrs"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.removeAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Get the attribute names to be removed.
|
||||||
|
We keep them as Attrs instead of Symbols so std::set_difference
|
||||||
|
can be used to remove them from attrs[0]. */
|
||||||
|
// 64: large enough to fit the attributes of a derivation
|
||||||
|
boost::container::small_vector<Attr, 64> names;
|
||||||
|
names.reserve(args[1]->listSize());
|
||||||
|
for (auto elem : args[1]->listItems()) {
|
||||||
|
state.forceStringNoCtx(
|
||||||
|
*elem,
|
||||||
|
pos,
|
||||||
|
"while evaluating the values of the second argument passed to builtins.removeAttrs"
|
||||||
|
);
|
||||||
|
names.emplace_back(state.symbols.create(elem->string.s), nullptr);
|
||||||
|
}
|
||||||
|
std::sort(names.begin(), names.end());
|
||||||
|
|
||||||
|
/* Copy all attributes not in that set. Note that we don't need
|
||||||
|
to sort v.attrs because it's a subset of an already sorted
|
||||||
|
vector. */
|
||||||
|
auto attrs = state.buildBindings(args[0]->attrs->size());
|
||||||
|
std::set_difference(
|
||||||
|
args[0]->attrs->begin(),
|
||||||
|
args[0]->attrs->end(),
|
||||||
|
names.begin(),
|
||||||
|
names.end(),
|
||||||
|
std::back_inserter(attrs)
|
||||||
|
);
|
||||||
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_removeAttrs({
|
||||||
|
.name = "removeAttrs",
|
||||||
|
.args = {"set", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Remove the attributes listed in *list* from *set*. The attributes
|
||||||
|
don’t have to exist in *set*. For instance,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
removeAttrs { x = 1; y = 2; z = 3; } [ "a" "x" "z" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `{ y = 2; }`.
|
||||||
|
)",
|
||||||
|
.fun = prim_removeAttrs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.zipAttrsWith
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
// we will first count how many values are present for each given key.
|
||||||
|
// we then allocate a single attrset and pre-populate it with lists of
|
||||||
|
// appropriate sizes, stash the pointers to the list elements of each,
|
||||||
|
// and populate the lists. after that we replace the list in the every
|
||||||
|
// attribute with the merge function application. this way we need not
|
||||||
|
// use (slightly slower) temporary storage the GC does not know about.
|
||||||
|
|
||||||
|
std::map<Symbol, std::pair<size_t, Value **>> attrsSeen;
|
||||||
|
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith"
|
||||||
|
);
|
||||||
|
const auto listSize = args[1]->listSize();
|
||||||
|
const auto listElems = args[1]->listElems();
|
||||||
|
|
||||||
|
for (unsigned int n = 0; n < listSize; ++n) {
|
||||||
|
Value * vElem = listElems[n];
|
||||||
|
state.forceAttrs(
|
||||||
|
*vElem,
|
||||||
|
noPos,
|
||||||
|
"while evaluating a value of the list passed as second argument to "
|
||||||
|
"builtins.zipAttrsWith"
|
||||||
|
);
|
||||||
|
for (auto & attr : *vElem->attrs) {
|
||||||
|
attrsSeen[attr.name].first++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(attrsSeen.size());
|
||||||
|
for (auto & [sym, elem] : attrsSeen) {
|
||||||
|
auto & list = attrs.alloc(sym);
|
||||||
|
state.mkList(list, elem.first);
|
||||||
|
elem.second = list.listElems();
|
||||||
|
}
|
||||||
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
|
||||||
|
for (unsigned int n = 0; n < listSize; ++n) {
|
||||||
|
Value * vElem = listElems[n];
|
||||||
|
for (auto & attr : *vElem->attrs) {
|
||||||
|
*attrsSeen[attr.name].second++ = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & attr : *v.attrs) {
|
||||||
|
auto name = state.allocValue();
|
||||||
|
name->mkString(state.symbols[attr.name]);
|
||||||
|
auto call1 = state.allocValue();
|
||||||
|
call1->mkApp(args[0], name);
|
||||||
|
auto call2 = state.allocValue();
|
||||||
|
call2->mkApp(call1, attr.value);
|
||||||
|
attr.value = call2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_zipAttrsWith({
|
||||||
|
.name = "__zipAttrsWith",
|
||||||
|
.args = {"f", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Transpose a list of attribute sets into an attribute set of lists,
|
||||||
|
then apply `mapAttrs`.
|
||||||
|
|
||||||
|
`f` receives two arguments: the attribute name and a non-empty
|
||||||
|
list of all values encountered for that attribute name.
|
||||||
|
|
||||||
|
The result is an attribute set where the attribute names are the
|
||||||
|
union of the attribute names in each element of `list`. The attribute
|
||||||
|
values are the return values of `f`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.zipAttrsWith
|
||||||
|
(name: values: { inherit name values; })
|
||||||
|
[ { a = "x"; } { a = "y"; b = "z"; } ]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
a = { name = "a"; values = [ "x" "y" ]; };
|
||||||
|
b = { name = "b"; values = [ "z" ]; };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_zipAttrsWith,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -1,142 +1,191 @@
|
||||||
#include "primops.hh"
|
#include <map> // for map, _Rb_tree_const_iterator
|
||||||
#include "eval-inline.hh"
|
#include <memory> // for shared_ptr
|
||||||
#include "derivations.hh"
|
#include <string> // for basic_string, operator<=>, char...
|
||||||
#include "store-api.hh"
|
#include <utility> // for move, pair
|
||||||
|
#include <variant> // for visit, get_if
|
||||||
|
#include <vector> // for vector
|
||||||
|
#include "attr-set.hh" // for Attr, Bindings, BindingsBuilder
|
||||||
|
#include "config.hh" // for Setting, ExperimentalFeatureSet...
|
||||||
|
#include "derivations.hh" // for isDerivation
|
||||||
|
#include "derived-path.hh" // for DerivedPath, makeConstantStoreP...
|
||||||
|
#include "downstream-placeholder.hh" // for DownstreamPlaceholder
|
||||||
|
#include "error.hh" // for Error
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, evalSettings
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature, Xp
|
||||||
|
#include "fmt.hh" // for HintFmt
|
||||||
|
#include "globals.hh" // for Settings, settings
|
||||||
|
#include "outputs-spec.hh" // for OutputsSpec
|
||||||
|
#include "path.hh" // for StorePath, StorePathSet
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "store-api.hh" // for Store, resolveDerivedPath, copy...
|
||||||
|
#include "symbol-table.hh" // for SymbolStr, Symbol, SymbolTable
|
||||||
|
#include "types.hh" // for StringMap, BackedStringView
|
||||||
|
#include "util.hh" // for overloaded, enumerate
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value/context.hh" // for NixStringContextElem, NixString...
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
std::vector<DerivedPath::Built> drvs;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
StringMap res;
|
||||||
v.mkString(*s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static RegisterPrimOp primop_unsafeDiscardStringContext({
|
for (auto & c : context) {
|
||||||
.name = "__unsafeDiscardStringContext",
|
auto ensureValid = [&](const StorePath & p) {
|
||||||
.arity = 1,
|
if (!store->isValidPath(p)) {
|
||||||
.fun = prim_unsafeDiscardStringContext
|
error<InvalidPathError>(store->printStorePath(p)).debugThrow();
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const NixStringContextElem::Built & b) {
|
||||||
|
drvs.push_back(DerivedPath::Built{
|
||||||
|
.drvPath = b.drvPath,
|
||||||
|
.outputs = OutputsSpec::Names{b.output},
|
||||||
|
});
|
||||||
|
ensureValid(b.drvPath->getBaseStorePath());
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::Opaque & o) {
|
||||||
|
auto ctxS = store->printStorePath(o.path);
|
||||||
|
res.insert_or_assign(ctxS, ctxS);
|
||||||
|
ensureValid(o.path);
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::DrvDeep & d) {
|
||||||
|
/* Treat same as Opaque */
|
||||||
|
auto ctxS = store->printStorePath(d.drvPath);
|
||||||
|
res.insert_or_assign(ctxS, ctxS);
|
||||||
|
ensureValid(d.drvPath);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
c.raw
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drvs.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
if (!evalSettings.enableImportFromDerivation) {
|
||||||
{
|
error<EvalError>(
|
||||||
NixStringContext context;
|
"cannot build '%1%' during evaluation because the option "
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
"'allow-import-from-derivation' is disabled",
|
||||||
v.mkBool(!context.empty());
|
drvs.begin()->to_string(*store)
|
||||||
}
|
)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_hasContext({
|
/* Build/substitute the context. */
|
||||||
.name = "__hasContext",
|
std::vector<DerivedPath> buildReqs;
|
||||||
.args = {"s"},
|
for (auto & d : drvs) {
|
||||||
.doc = R"(
|
buildReqs.emplace_back(DerivedPath{d});
|
||||||
Return `true` if string *s* has a non-empty context.
|
}
|
||||||
The context can be obtained with
|
buildStore->buildPaths(buildReqs, bmNormal, store);
|
||||||
[`getContext`](#builtins-getContext).
|
|
||||||
|
|
||||||
> **Example**
|
StorePathSet outputsToCopyAndAllow;
|
||||||
>
|
|
||||||
> Many operations require a string context to be empty because they are intended only to work with "regular" strings, and also to help users avoid unintentionally losing track of string context elements.
|
|
||||||
> `builtins.hasContext` can help create better domain-specific errors in those case.
|
|
||||||
>
|
|
||||||
> ```nix
|
|
||||||
> name: meta:
|
|
||||||
>
|
|
||||||
> if builtins.hasContext name
|
|
||||||
> then throw "package name cannot contain string context"
|
|
||||||
> else { ${name} = meta; }
|
|
||||||
> ```
|
|
||||||
)",
|
|
||||||
.fun = prim_hasContext
|
|
||||||
});
|
|
||||||
|
|
||||||
|
for (auto & drv : drvs) {
|
||||||
|
auto outputs = resolveDerivedPath(*buildStore, drv, &*store);
|
||||||
|
for (auto & [outputName, outputPath] : outputs) {
|
||||||
|
outputsToCopyAndAllow.insert(outputPath);
|
||||||
|
|
||||||
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
/* Get all the output paths corresponding to the placeholders we had */
|
||||||
{
|
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||||
NixStringContext context;
|
res.insert_or_assign(
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
DownstreamPlaceholder::fromSingleDerivedPathBuilt(SingleDerivedPath::Built{
|
||||||
|
.drvPath = drv.drvPath,
|
||||||
NixStringContext context2;
|
.output = outputName,
|
||||||
for (auto && c : context) {
|
})
|
||||||
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) {
|
.render(),
|
||||||
context2.emplace(NixStringContextElem::Opaque {
|
buildStore->printStorePath(outputPath)
|
||||||
.path = ptr->drvPath
|
);
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
/* Can reuse original item */
|
|
||||||
context2.emplace(std::move(c).raw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v.mkString(*s, context2);
|
if (store != buildStore) {
|
||||||
|
copyClosure(*buildStore, *store, outputsToCopyAndAllow);
|
||||||
|
}
|
||||||
|
if (allowedPaths) {
|
||||||
|
for (auto & outputPath : outputsToCopyAndAllow) {
|
||||||
|
/* Add the output of this derivations to the allowed
|
||||||
|
paths. */
|
||||||
|
allowPath(outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_unsafeDiscardOutputDependency({
|
/**
|
||||||
.name = "__unsafeDiscardOutputDependency",
|
* builtins.addDrvOutputDependencies
|
||||||
.args = {"s"},
|
*/
|
||||||
.doc = R"(
|
|
||||||
Create a copy of the given string where every "derivation deep" string context element is turned into a constant string context element.
|
|
||||||
|
|
||||||
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
|
static void
|
||||||
|
prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context,
|
|
||||||
whereas Nix normally tracks all dependencies consistently.
|
|
||||||
Safe operations "grow" but never "shrink" string contexts.
|
|
||||||
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).
|
|
||||||
Replacing a constant string context element with a "derivation deep" element is a safe operation that just enlargens the string context without forgetting anything.
|
|
||||||
|
|
||||||
[`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies
|
|
||||||
)",
|
|
||||||
.fun = prim_unsafeDiscardOutputDependency
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
|
auto s = state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the argument passed to builtins.addDrvOutputDependencies"
|
||||||
|
);
|
||||||
|
|
||||||
auto contextSize = context.size();
|
auto contextSize = context.size();
|
||||||
if (contextSize != 1) {
|
if (contextSize != 1) {
|
||||||
state.error<EvalError>(
|
state
|
||||||
"context of string '%s' must have exactly one element, but has %d",
|
.error<EvalError>(
|
||||||
*s,
|
"context of string '%s' must have exactly one element, but has %d", *s, contextSize
|
||||||
contextSize
|
)
|
||||||
).atPos(pos).debugThrow();
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
}
|
}
|
||||||
NixStringContext context2 {
|
NixStringContext context2{
|
||||||
(NixStringContextElem { std::visit(overloaded {
|
(NixStringContextElem{std::visit(
|
||||||
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
|
overloaded{
|
||||||
if (!c.path.isDerivation()) {
|
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
|
||||||
state.error<EvalError>(
|
if (!c.path.isDerivation()) {
|
||||||
"path '%s' is not a derivation",
|
state
|
||||||
state.store->printStorePath(c.path)
|
.error<EvalError>(
|
||||||
).atPos(pos).debugThrow();
|
"path '%s' is not a derivation", state.store->printStorePath(c.path)
|
||||||
}
|
)
|
||||||
return NixStringContextElem::DrvDeep {
|
.atPos(pos)
|
||||||
.drvPath = c.path,
|
.debugThrow();
|
||||||
};
|
}
|
||||||
|
return NixStringContextElem::DrvDeep{
|
||||||
|
.drvPath = c.path,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"`addDrvOutputDependencies` can only act on derivations, not on a "
|
||||||
|
"derivation output such as '%1%'",
|
||||||
|
c.output
|
||||||
|
)
|
||||||
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
|
||||||
|
/* Reuse original item because we want this to be idempotent. */
|
||||||
|
return std::move(c);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
|
context.begin()->raw
|
||||||
state.error<EvalError>(
|
)}),
|
||||||
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
|
|
||||||
c.output
|
|
||||||
).atPos(pos).debugThrow();
|
|
||||||
},
|
|
||||||
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
|
|
||||||
/* Reuse original item because we want this to be idempotent. */
|
|
||||||
return std::move(c);
|
|
||||||
},
|
|
||||||
}, context.begin()->raw) }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
v.mkString(*s, context2);
|
v.mkString(*s, context2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_addDrvOutputDependencies({
|
PrimOp primop_addDrvOutputDependencies(
|
||||||
.name = "__addDrvOutputDependencies",
|
{.name = "__addDrvOutputDependencies",
|
||||||
.args = {"s"},
|
.args = {"s"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
Create a copy of the given string where a single constant string context element is turned into a "derivation deep" string context element.
|
Create a copy of the given string where a single constant string context element is turned into a "derivation deep" string context element.
|
||||||
|
|
||||||
The store path that is the constant string context element should point to a valid derivation, and end in `.drv`.
|
The store path that is the constant string context element should point to a valid derivation, and end in `.drv`.
|
||||||
|
@ -146,9 +195,153 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
|
||||||
|
|
||||||
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
|
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
|
||||||
)",
|
)",
|
||||||
.fun = prim_addDrvOutputDependencies
|
.fun = prim_addDrvOutputDependencies}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.addErrorContext
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
} catch (Error & e) {
|
||||||
|
NixStringContext context;
|
||||||
|
auto message =
|
||||||
|
state
|
||||||
|
.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the error message passed to builtins.addErrorContext",
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.toOwned();
|
||||||
|
e.addTrace(nullptr, HintFmt(message));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_addErrorContext(PrimOp{
|
||||||
|
.name = "__addErrorContext",
|
||||||
|
.arity = 2,
|
||||||
|
.fun = prim_addErrorContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.appendContext
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_appendContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto orig = state.forceString(
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
noPos,
|
||||||
|
"while evaluating the first argument passed to builtins.appendContext"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.appendContext"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto sPath = state.symbols.create("path");
|
||||||
|
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||||
|
for (auto & i : *args[1]->attrs) {
|
||||||
|
const auto & name = state.symbols[i.name];
|
||||||
|
if (!state.store->isStorePath(name)) {
|
||||||
|
state.error<EvalError>("context key '%s' is not a store path", name)
|
||||||
|
.atPos(i.pos)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
auto namePath = state.store->parseStorePath(name);
|
||||||
|
if (!settings.readOnlyMode) {
|
||||||
|
state.store->ensurePath(namePath);
|
||||||
|
}
|
||||||
|
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
||||||
|
auto iter = i.value->attrs->find(sPath);
|
||||||
|
if (iter != i.value->attrs->end()) {
|
||||||
|
if (state.forceBool(
|
||||||
|
*iter->value,
|
||||||
|
iter->pos,
|
||||||
|
"while evaluating the `path` attribute of a string context"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
context.emplace(NixStringContextElem::Opaque{
|
||||||
|
.path = namePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iter = i.value->attrs->find(sAllOutputs);
|
||||||
|
if (iter != i.value->attrs->end()) {
|
||||||
|
if (state.forceBool(
|
||||||
|
*iter->value,
|
||||||
|
iter->pos,
|
||||||
|
"while evaluating the `allOutputs` attribute of a string context"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (!isDerivation(name)) {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"tried to add all-outputs context of %s, which is not a derivation, to "
|
||||||
|
"a string",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.atPos(i.pos)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
context.emplace(NixStringContextElem::DrvDeep{
|
||||||
|
.drvPath = namePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iter = i.value->attrs->find(state.sOutputs);
|
||||||
|
if (iter != i.value->attrs->end()) {
|
||||||
|
state.forceList(
|
||||||
|
*iter->value,
|
||||||
|
iter->pos,
|
||||||
|
"while evaluating the `outputs` attribute of a string context"
|
||||||
|
);
|
||||||
|
if (iter->value->listSize() && !isDerivation(name)) {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"tried to add derivation output context of %s, which is not a derivation, "
|
||||||
|
"to a string",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.atPos(i.pos)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
for (auto elem : iter->value->listItems()) {
|
||||||
|
auto outputName = state.forceStringNoCtx(
|
||||||
|
*elem, iter->pos, "while evaluating an output name within a string context"
|
||||||
|
);
|
||||||
|
context.emplace(NixStringContextElem::Built{
|
||||||
|
.drvPath = makeConstantStorePathRef(namePath),
|
||||||
|
.output = std::string{outputName},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkString(orig, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_appendContext({
|
||||||
|
.name = "__appendContext",
|
||||||
|
.arity = 2,
|
||||||
|
.fun = prim_appendContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.getContext
|
||||||
|
*/
|
||||||
|
|
||||||
/* Extract the context of a string as a structured Nix value.
|
/* Extract the context of a string as a structured Nix value.
|
||||||
|
|
||||||
|
@ -169,31 +362,37 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
|
||||||
Note that for a given path any combination of the above attributes
|
Note that for a given path any combination of the above attributes
|
||||||
may be present.
|
may be present.
|
||||||
*/
|
*/
|
||||||
static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
{
|
{
|
||||||
struct ContextInfo {
|
struct ContextInfo
|
||||||
|
{
|
||||||
bool path = false;
|
bool path = false;
|
||||||
bool allOutputs = false;
|
bool allOutputs = false;
|
||||||
Strings outputs;
|
Strings outputs;
|
||||||
};
|
};
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
state.forceString(
|
||||||
|
*args[0], context, pos, "while evaluating the argument passed to builtins.getContext"
|
||||||
|
);
|
||||||
auto contextInfos = std::map<StorePath, ContextInfo>();
|
auto contextInfos = std::map<StorePath, ContextInfo>();
|
||||||
for (auto && i : context) {
|
for (auto && i : context) {
|
||||||
std::visit(overloaded {
|
std::visit(
|
||||||
[&](NixStringContextElem::DrvDeep && d) {
|
overloaded{
|
||||||
contextInfos[std::move(d.drvPath)].allOutputs = true;
|
[&](NixStringContextElem::DrvDeep && d) {
|
||||||
|
contextInfos[std::move(d.drvPath)].allOutputs = true;
|
||||||
|
},
|
||||||
|
[&](NixStringContextElem::Built && b) {
|
||||||
|
// FIXME should eventually show string context as is, no
|
||||||
|
// resolving here.
|
||||||
|
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
|
||||||
|
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
|
||||||
|
},
|
||||||
|
[&](NixStringContextElem::Opaque && o) {
|
||||||
|
contextInfos[std::move(o.path)].path = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[&](NixStringContextElem::Built && b) {
|
((NixStringContextElem &&) i).raw
|
||||||
// FIXME should eventually show string context as is, no
|
);
|
||||||
// resolving here.
|
|
||||||
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
|
|
||||||
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
|
|
||||||
},
|
|
||||||
[&](NixStringContextElem::Opaque && o) {
|
|
||||||
contextInfos[std::move(o.path)].path = true;
|
|
||||||
},
|
|
||||||
}, ((NixStringContextElem &&) i).raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attrs = state.buildBindings(contextInfos.size());
|
auto attrs = state.buildBindings(contextInfos.size());
|
||||||
|
@ -202,15 +401,18 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||||
for (const auto & info : contextInfos) {
|
for (const auto & info : contextInfos) {
|
||||||
auto infoAttrs = state.buildBindings(3);
|
auto infoAttrs = state.buildBindings(3);
|
||||||
if (info.second.path)
|
if (info.second.path) {
|
||||||
infoAttrs.alloc(sPath).mkBool(true);
|
infoAttrs.alloc(sPath).mkBool(true);
|
||||||
if (info.second.allOutputs)
|
}
|
||||||
|
if (info.second.allOutputs) {
|
||||||
infoAttrs.alloc(sAllOutputs).mkBool(true);
|
infoAttrs.alloc(sAllOutputs).mkBool(true);
|
||||||
|
}
|
||||||
if (!info.second.outputs.empty()) {
|
if (!info.second.outputs.empty()) {
|
||||||
auto & outputsVal = infoAttrs.alloc(state.sOutputs);
|
auto & outputsVal = infoAttrs.alloc(state.sOutputs);
|
||||||
state.mkList(outputsVal, info.second.outputs.size());
|
state.mkList(outputsVal, info.second.outputs.size());
|
||||||
for (const auto & [i, output] : enumerate(info.second.outputs))
|
for (const auto & [i, output] : enumerate(info.second.outputs)) {
|
||||||
(outputsVal.listElems()[i] = state.allocValue())->mkString(output);
|
(outputsVal.listElems()[i] = state.allocValue())->mkString(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
|
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
|
||||||
}
|
}
|
||||||
|
@ -218,7 +420,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
v.mkAttrs(attrs);
|
v.mkAttrs(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_getContext({
|
PrimOp primop_getContext({
|
||||||
.name = "__getContext",
|
.name = "__getContext",
|
||||||
.args = {"s"},
|
.args = {"s"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
@ -240,84 +442,114 @@ static RegisterPrimOp primop_getContext({
|
||||||
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
|
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
|
||||||
```
|
```
|
||||||
)",
|
)",
|
||||||
.fun = prim_getContext
|
.fun = prim_getContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.hasContext
|
||||||
|
*/
|
||||||
|
|
||||||
/* Append the given context to a given string.
|
static void prim_hasContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
|
||||||
See the commentary above unsafeGetContext for details of the
|
|
||||||
context representation.
|
|
||||||
*/
|
|
||||||
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
state.forceString(
|
||||||
|
*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext"
|
||||||
|
);
|
||||||
|
v.mkBool(!context.empty());
|
||||||
|
}
|
||||||
|
|
||||||
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
|
PrimOp primop_hasContext(
|
||||||
|
{.name = "__hasContext",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if string *s* has a non-empty context.
|
||||||
|
The context can be obtained with
|
||||||
|
[`getContext`](#builtins-getContext).
|
||||||
|
|
||||||
auto sPath = state.symbols.create("path");
|
> **Example**
|
||||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
>
|
||||||
for (auto & i : *args[1]->attrs) {
|
> Many operations require a string context to be empty because they are intended only to work with "regular" strings, and also to help users avoid unintentionally losing track of string context elements.
|
||||||
const auto & name = state.symbols[i.name];
|
> `builtins.hasContext` can help create better domain-specific errors in those case.
|
||||||
if (!state.store->isStorePath(name))
|
>
|
||||||
state.error<EvalError>(
|
> ```nix
|
||||||
"context key '%s' is not a store path",
|
> name: meta:
|
||||||
name
|
>
|
||||||
).atPos(i.pos).debugThrow();
|
> if builtins.hasContext name
|
||||||
auto namePath = state.store->parseStorePath(name);
|
> then throw "package name cannot contain string context"
|
||||||
if (!settings.readOnlyMode)
|
> else { ${name} = meta; }
|
||||||
state.store->ensurePath(namePath);
|
> ```
|
||||||
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
)",
|
||||||
auto iter = i.value->attrs->find(sPath);
|
.fun = prim_hasContext}
|
||||||
if (iter != i.value->attrs->end()) {
|
);
|
||||||
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
|
|
||||||
context.emplace(NixStringContextElem::Opaque {
|
|
||||||
.path = namePath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
iter = i.value->attrs->find(sAllOutputs);
|
/**
|
||||||
if (iter != i.value->attrs->end()) {
|
* builtins.unsafeDiscardOutputDependency
|
||||||
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
|
*/
|
||||||
if (!isDerivation(name)) {
|
|
||||||
state.error<EvalError>(
|
|
||||||
"tried to add all-outputs context of %s, which is not a derivation, to a string",
|
|
||||||
name
|
|
||||||
).atPos(i.pos).debugThrow();
|
|
||||||
}
|
|
||||||
context.emplace(NixStringContextElem::DrvDeep {
|
|
||||||
.drvPath = namePath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iter = i.value->attrs->find(state.sOutputs);
|
static void
|
||||||
if (iter != i.value->attrs->end()) {
|
prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
|
{
|
||||||
if (iter->value->listSize() && !isDerivation(name)) {
|
NixStringContext context;
|
||||||
state.error<EvalError>(
|
auto s = state.coerceToString(
|
||||||
"tried to add derivation output context of %s, which is not a derivation, to a string",
|
pos,
|
||||||
name
|
*args[0],
|
||||||
).atPos(i.pos).debugThrow();
|
context,
|
||||||
}
|
"while evaluating the argument passed to builtins.unsafeDiscardOutputDependency"
|
||||||
for (auto elem : iter->value->listItems()) {
|
);
|
||||||
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
|
||||||
context.emplace(NixStringContextElem::Built {
|
NixStringContext context2;
|
||||||
.drvPath = makeConstantStorePathRef(namePath),
|
for (auto && c : context) {
|
||||||
.output = std::string { outputName },
|
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) {
|
||||||
});
|
context2.emplace(NixStringContextElem::Opaque{.path = ptr->drvPath});
|
||||||
}
|
} else {
|
||||||
|
/* Can reuse original item */
|
||||||
|
context2.emplace(std::move(c).raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v.mkString(orig, context);
|
v.mkString(*s, context2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_appendContext({
|
PrimOp primop_unsafeDiscardOutputDependency(
|
||||||
.name = "__appendContext",
|
{.name = "__unsafeDiscardOutputDependency",
|
||||||
.arity = 2,
|
.args = {"s"},
|
||||||
.fun = prim_appendContext
|
.doc = R"(
|
||||||
|
Create a copy of the given string where every "derivation deep" string context element is turned into a constant string context element.
|
||||||
|
|
||||||
|
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
|
||||||
|
|
||||||
|
This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context,
|
||||||
|
whereas Nix normally tracks all dependencies consistently.
|
||||||
|
Safe operations "grow" but never "shrink" string contexts.
|
||||||
|
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).
|
||||||
|
Replacing a constant string context element with a "derivation deep" element is a safe operation that just enlargens the string context without forgetting anything.
|
||||||
|
|
||||||
|
[`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies
|
||||||
|
)",
|
||||||
|
.fun = prim_unsafeDiscardOutputDependency}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins. unsafeDiscardStringContext
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the argument passed to builtins.unsafeDiscardStringContext"
|
||||||
|
);
|
||||||
|
v.mkString(*s);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_unsafeDiscardStringContext({
|
||||||
|
.name = "__unsafeDiscardStringContext",
|
||||||
|
.arity = 1,
|
||||||
|
.fun = prim_unsafeDiscardStringContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
165
src/libexpr/primops/control.cc
Normal file
165
src/libexpr/primops/control.cc
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#include <list> // for list
|
||||||
|
#include <string> // for basic_string
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder
|
||||||
|
#include "error.hh" // for Trace, Error, lvlInfo, ErrorInfo
|
||||||
|
#include "eval-error.hh" // for Abort, ThrownError, EvalErrorBuilder
|
||||||
|
#include "eval-settings.hh"
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp, DebugTrace, ValMap
|
||||||
|
#include "fmt.hh" // for HintFmt
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "symbol-table.hh" // for Symbol
|
||||||
|
#include "types.hh" // for BackedStringView
|
||||||
|
#include "util.hh" // for MaintainCount
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
enum class ReplExitStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.abort
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_abort(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state
|
||||||
|
.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the error message passed to builtins.abort"
|
||||||
|
)
|
||||||
|
.toOwned();
|
||||||
|
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_abort({
|
||||||
|
.name = "abort",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Abort Nix expression evaluation and print the error message *s*.
|
||||||
|
)",
|
||||||
|
.fun = prim_abort,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.break
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_break(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
if (state.debugRepl && !state.debugTraces.empty()) {
|
||||||
|
auto error = Error(ErrorInfo{
|
||||||
|
.level = lvlInfo,
|
||||||
|
.msg = HintFmt("breakpoint reached"),
|
||||||
|
.pos = state.positions[pos],
|
||||||
|
});
|
||||||
|
|
||||||
|
auto & dt = state.debugTraces.front();
|
||||||
|
state.runDebugRepl(&error, dt.env, dt.expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the value we were passed.
|
||||||
|
v = *args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_break({
|
||||||
|
.name = "break",
|
||||||
|
.args = {"v"},
|
||||||
|
.doc = R"(
|
||||||
|
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
|
||||||
|
Otherwise, return the argument `v`.
|
||||||
|
)",
|
||||||
|
.fun = prim_break,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.throw
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_throw(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto s =
|
||||||
|
state
|
||||||
|
.coerceToString(
|
||||||
|
pos, *args[0], context, "while evaluating the error message passed to builtin.throw"
|
||||||
|
)
|
||||||
|
.toOwned();
|
||||||
|
state.error<ThrownError>(s).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_throw({
|
||||||
|
.name = "throw",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Throw an error message *s*. This usually aborts Nix expression
|
||||||
|
evaluation, but in `nix-env -qa` and other commands that try to
|
||||||
|
evaluate a set of derivations to get information about those
|
||||||
|
derivations, a derivation that throws an error is silently skipped
|
||||||
|
(which is not the case for `abort`).
|
||||||
|
)",
|
||||||
|
.fun = prim_throw,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.tryEval
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_tryEval(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto attrs = state.buildBindings(2);
|
||||||
|
|
||||||
|
/* increment state.trylevel, and decrement it when this function returns. */
|
||||||
|
MaintainCount trylevel(state.trylevel);
|
||||||
|
|
||||||
|
ReplExitStatus (*savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
|
||||||
|
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry) {
|
||||||
|
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
|
||||||
|
savedDebugRepl = state.debugRepl;
|
||||||
|
state.debugRepl = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
attrs.insert(state.sValue, args[0]);
|
||||||
|
attrs.alloc("success").mkBool(true);
|
||||||
|
} catch (AssertionError & e) {
|
||||||
|
attrs.alloc(state.sValue).mkBool(false);
|
||||||
|
attrs.alloc("success").mkBool(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore the debugRepl pointer if we saved it earlier.
|
||||||
|
if (savedDebugRepl) {
|
||||||
|
state.debugRepl = savedDebugRepl;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_tryEval({
|
||||||
|
.name = "__tryEval",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Try to shallowly evaluate *e*. Return a set containing the
|
||||||
|
attributes `success` (`true` if *e* evaluated successfully,
|
||||||
|
`false` if an error was thrown) and `value`, equalling *e* if
|
||||||
|
successful and `false` otherwise. `tryEval` will only prevent
|
||||||
|
errors created by `throw` or `assert` from being thrown.
|
||||||
|
Errors `tryEval` will not catch are for example those created
|
||||||
|
by `abort` and type errors generated by builtins. Also note that
|
||||||
|
this doesn't evaluate *e* deeply, so `let e = { x = throw ""; };
|
||||||
|
in (builtins.tryEval e).success` will be `true`. Using
|
||||||
|
`builtins.deepSeq` one can get the expected result:
|
||||||
|
`let e = { x = throw ""; }; in
|
||||||
|
(builtins.tryEval (builtins.deepSeq e e)).success` will be
|
||||||
|
`false`.
|
||||||
|
)",
|
||||||
|
.fun = prim_tryEval,
|
||||||
|
});
|
||||||
|
}
|
181
src/libexpr/primops/debug.cc
Normal file
181
src/libexpr/primops/debug.cc
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#include <list> // for list
|
||||||
|
#include <string> // for basic_string
|
||||||
|
#include "attr-set.hh" // for Bindings
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, evalSettings
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp, DebugTrace
|
||||||
|
#include "logging.hh" // for Logger, printError
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "position.hh" // for Pos
|
||||||
|
#include "primops.hh" // for makePositionThunks, prim_second, prim_trace
|
||||||
|
#include "print.hh" // for ValuePrinter
|
||||||
|
#include "symbol-table.hh" // for SymbolTable
|
||||||
|
#include "value.hh" // for Value, nString
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
// access to exact position information (ie, line and colum numbers) is deferred
|
||||||
|
// due to the cost associated with calculating that information and how rarely
|
||||||
|
// it is used in practice. this is achieved by creating thunks to otherwise
|
||||||
|
// inaccessible primops that are not exposed as __op or under builtins to turn
|
||||||
|
// the internal PosIdx back into a line and column number, respectively. exposing
|
||||||
|
// these primops in any way would at best be not useful and at worst create wildly
|
||||||
|
// indeterministic eval results depending on parse order of files.
|
||||||
|
//
|
||||||
|
// in a simpler world this would instead be implemented as another kind of thunk,
|
||||||
|
// but each type of thunk has an associated runtime cost in the current evaluator.
|
||||||
|
// as with black holes this cost is too high to justify another thunk type to check
|
||||||
|
// for in the very hot path that is forceValue.
|
||||||
|
static struct LazyPosAcessors
|
||||||
|
{
|
||||||
|
PrimOp primop_lineOfPos{
|
||||||
|
.arity = 1,
|
||||||
|
.fun = [](EvalState & state, PosIdx pos, Value ** args, Value & v
|
||||||
|
) { v.mkInt(state.positions[PosIdx(args[0]->integer)].line); }
|
||||||
|
};
|
||||||
|
PrimOp primop_columnOfPos{
|
||||||
|
.arity = 1,
|
||||||
|
.fun = [](EvalState & state, PosIdx pos, Value ** args, Value & v
|
||||||
|
) { v.mkInt(state.positions[PosIdx(args[0]->integer)].column); }
|
||||||
|
};
|
||||||
|
|
||||||
|
Value lineOfPos, columnOfPos;
|
||||||
|
|
||||||
|
LazyPosAcessors()
|
||||||
|
{
|
||||||
|
lineOfPos.mkPrimOp(&primop_lineOfPos);
|
||||||
|
columnOfPos.mkPrimOp(&primop_columnOfPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(EvalState & state, const PosIdx pos, Value & line, Value & column)
|
||||||
|
{
|
||||||
|
Value * posV = state.allocValue();
|
||||||
|
posV->mkInt(pos.id);
|
||||||
|
line.mkApp(&lineOfPos, posV);
|
||||||
|
column.mkApp(&columnOfPos, posV);
|
||||||
|
}
|
||||||
|
} makeLazyPosAccessors;
|
||||||
|
|
||||||
|
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column)
|
||||||
|
{
|
||||||
|
makeLazyPosAccessors(state, pos, line, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes two arguments and evaluates to the second one. Used as the
|
||||||
|
* builtins.traceVerbose implementation when --trace-verbose is not enabled
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_second(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.deepSeq
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_deepSeq(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValueDeep(*args[0]);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_deepSeq({
|
||||||
|
.name = "__deepSeq",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
This is like `seq e1 e2`, except that *e1* is evaluated *deeply*:
|
||||||
|
if it’s a list or set, its elements or attributes are also
|
||||||
|
evaluated recursively.
|
||||||
|
)",
|
||||||
|
.fun = prim_deepSeq,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.seq
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_seq(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_seq({
|
||||||
|
.name = "__seq",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Evaluate *e1*, then evaluate and return *e2*. This ensures that a
|
||||||
|
computation is strict in the value of *e1*.
|
||||||
|
)",
|
||||||
|
.fun = prim_seq,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.trace
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_trace(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
if (args[0]->type() == nString) {
|
||||||
|
printError("trace: %1%", args[0]->string.s);
|
||||||
|
} else {
|
||||||
|
printError("trace: %1%", ValuePrinter(state, *args[0]));
|
||||||
|
}
|
||||||
|
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
|
||||||
|
const DebugTrace & last = state.debugTraces.front();
|
||||||
|
state.runDebugRepl(nullptr, last.env, last.expr);
|
||||||
|
}
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_trace({
|
||||||
|
.name = "__trace",
|
||||||
|
.args = {"e1", "e2"},
|
||||||
|
.doc = R"(
|
||||||
|
Evaluate *e1* and print its abstract syntax representation on
|
||||||
|
standard error. Then return *e2*. This function is useful for
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
If the
|
||||||
|
[`debugger-on-trace`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace)
|
||||||
|
option is set to `true` and the `--debugger` flag is given, the
|
||||||
|
interactive debugger will be started when `trace` is called (like
|
||||||
|
[`break`](@docroot@/language/builtins.md#builtins-break)).
|
||||||
|
)",
|
||||||
|
.fun = prim_trace,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.unsafeGetAttrPos
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto attr = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.unsafeGetAttrPos"
|
||||||
|
);
|
||||||
|
state.forceAttrs(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.unsafeGetAttrPos"
|
||||||
|
);
|
||||||
|
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
|
||||||
|
if (i == args[1]->attrs->end()) {
|
||||||
|
v.mkNull();
|
||||||
|
} else {
|
||||||
|
state.mkPos(v, i->pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_unsafeGetAttrPos(PrimOp{
|
||||||
|
.name = "__unsafeGetAttrPos",
|
||||||
|
.arity = 2,
|
||||||
|
.fun = prim_unsafeGetAttrPos,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
429
src/libexpr/primops/derivation.cc
Normal file
429
src/libexpr/primops/derivation.cc
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
#include <map> // for _Rb_tree_const_ite...
|
||||||
|
#include <memory> // for allocator, shared_ptr
|
||||||
|
#include <nlohmann/json.hpp> // for basic_json
|
||||||
|
#include <nlohmann/json_fwd.hpp> // for json
|
||||||
|
#include <optional> // for optional, operator==
|
||||||
|
#include <set> // for set
|
||||||
|
#include <string> // for char_traits, basic...
|
||||||
|
#include <string_view> // for operator==, basic_...
|
||||||
|
#include <utility> // for move, pair
|
||||||
|
#include <variant> // for visit
|
||||||
|
#include "attr-set.hh" // for Attr, Bindings
|
||||||
|
#include "config.hh" // for ExperimentalFeatur...
|
||||||
|
#include "content-address.hh" // for ContentAddressMethod
|
||||||
|
#include "derivations.hh" // for Derivation, Deriva...
|
||||||
|
#include "derived-path-map.hh" // for DerivedPathMap
|
||||||
|
#include "error.hh" // for Error, lvlChatty
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErr...
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature
|
||||||
|
#include "fmt.hh" // for HintFmt
|
||||||
|
#include "hash.hh" // for parseHashTypeOpt
|
||||||
|
#include "logging.hh" // for Logger, printMsg
|
||||||
|
#include "path.hh" // for StorePath, StorePa...
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "position.hh" // for Pos, operator<<
|
||||||
|
#include "primops.hh" // for getAttr, mkOutputS...
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "store-api.hh" // for Store
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolStr
|
||||||
|
#include "sync.hh" // for Sync
|
||||||
|
#include "types.hh" // for Strings, BackedStr...
|
||||||
|
#include "util.hh" // for get, tokenizeString
|
||||||
|
#include "value-to-json.hh" // for printValueAsJSON
|
||||||
|
#include "value.hh" // for Value, nNull
|
||||||
|
#include "value/context.hh" // for NixStringContextElem
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static void derivationStrictInternal(EvalState & state, const std::string &
|
||||||
|
drvName, Bindings * attrs, Value & v)
|
||||||
|
{
|
||||||
|
/* Check whether attributes should be passed as a JSON file. */
|
||||||
|
using nlohmann::json;
|
||||||
|
std::optional<json> jsonObject;
|
||||||
|
auto pos = v.determinePos(noPos);
|
||||||
|
auto attr = attrs->find(state.sStructuredAttrs);
|
||||||
|
if (attr != attrs->end() &&
|
||||||
|
state.forceBool(*attr->value, pos,
|
||||||
|
"while evaluating the `__structuredAttrs` "
|
||||||
|
"attribute passed to builtins.derivationStrict"))
|
||||||
|
jsonObject = json::object();
|
||||||
|
|
||||||
|
/* Check whether null attributes should be ignored. */
|
||||||
|
bool ignoreNulls = false;
|
||||||
|
attr = attrs->find(state.sIgnoreNulls);
|
||||||
|
if (attr != attrs->end())
|
||||||
|
ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
|
||||||
|
|
||||||
|
/* Build the derivation expression by processing the attributes. */
|
||||||
|
Derivation drv;
|
||||||
|
drv.name = drvName;
|
||||||
|
|
||||||
|
NixStringContext context;
|
||||||
|
|
||||||
|
bool contentAddressed = false;
|
||||||
|
bool isImpure = false;
|
||||||
|
std::optional<std::string> outputHash;
|
||||||
|
std::string outputHashAlgo;
|
||||||
|
std::optional<ContentAddressMethod> ingestionMethod;
|
||||||
|
|
||||||
|
StringSet outputs;
|
||||||
|
outputs.insert("out");
|
||||||
|
|
||||||
|
for (auto & i : attrs->lexicographicOrder(state.symbols)) {
|
||||||
|
if (i->name == state.sIgnoreNulls) continue;
|
||||||
|
const std::string & key = state.symbols[i->name];
|
||||||
|
vomit("processing attribute '%1%'", key);
|
||||||
|
|
||||||
|
auto handleHashMode = [&](const std::string_view s) {
|
||||||
|
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
|
||||||
|
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
|
||||||
|
else if (s == "text") {
|
||||||
|
experimentalFeatureSettings.require(Xp::DynamicDerivations);
|
||||||
|
ingestionMethod = TextIngestionMethod {};
|
||||||
|
} else
|
||||||
|
state.error<EvalError>(
|
||||||
|
"invalid value '%s' for 'outputHashMode' attribute", s
|
||||||
|
).atPos(v).debugThrow();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto handleOutputs = [&](const Strings & ss) {
|
||||||
|
outputs.clear();
|
||||||
|
for (auto & j : ss) {
|
||||||
|
if (outputs.find(j) != outputs.end())
|
||||||
|
state.error<EvalError>("duplicate derivation output '%1%'", j)
|
||||||
|
.atPos(v)
|
||||||
|
.debugThrow();
|
||||||
|
/* !!! Check whether j is a valid attribute
|
||||||
|
name. */
|
||||||
|
/* Derivations cannot be named ‘drv’, because
|
||||||
|
then we'd have an attribute ‘drvPath’ in
|
||||||
|
the resulting set. */
|
||||||
|
if (j == "drv")
|
||||||
|
state.error<EvalError>("invalid derivation output name 'drv'")
|
||||||
|
.atPos(v)
|
||||||
|
.debugThrow();
|
||||||
|
outputs.insert(j);
|
||||||
|
}
|
||||||
|
if (outputs.empty())
|
||||||
|
state.error<EvalError>("derivation cannot have an empty set of outputs")
|
||||||
|
.atPos(v)
|
||||||
|
.debugThrow();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This try-catch block adds context for most errors.
|
||||||
|
// Use this empty error context to signify that we defer to it.
|
||||||
|
const std::string_view context_below("");
|
||||||
|
|
||||||
|
if (ignoreNulls) {
|
||||||
|
state.forceValue(*i->value, pos);
|
||||||
|
if (i->value->type() == nNull) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i->name == state.sContentAddressed && state.forceBool(*i->value, pos, context_below)) {
|
||||||
|
contentAddressed = true;
|
||||||
|
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (i->name == state.sImpure && state.forceBool(*i->value, pos, context_below)) {
|
||||||
|
isImpure = true;
|
||||||
|
experimentalFeatureSettings.require(Xp::ImpureDerivations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The `args' attribute is special: it supplies the
|
||||||
|
command-line arguments to the builder. */
|
||||||
|
else if (i->name == state.sArgs) {
|
||||||
|
state.forceList(*i->value, pos, context_below);
|
||||||
|
for (auto elem : i->value->listItems()) {
|
||||||
|
auto s = state.coerceToString(pos, *elem, context,
|
||||||
|
"while evaluating an element of the argument list",
|
||||||
|
true).toOwned();
|
||||||
|
drv.args.push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All other attributes are passed to the builder through
|
||||||
|
the environment. */
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (jsonObject) {
|
||||||
|
|
||||||
|
if (i->name == state.sStructuredAttrs) continue;
|
||||||
|
|
||||||
|
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
|
||||||
|
|
||||||
|
if (i->name == state.sBuilder)
|
||||||
|
drv.builder = state.forceString(*i->value, context, pos, context_below);
|
||||||
|
else if (i->name == state.sSystem)
|
||||||
|
drv.platform = state.forceStringNoCtx(*i->value, pos, context_below);
|
||||||
|
else if (i->name == state.sOutputHash)
|
||||||
|
outputHash = state.forceStringNoCtx(*i->value, pos, context_below);
|
||||||
|
else if (i->name == state.sOutputHashAlgo)
|
||||||
|
outputHashAlgo = state.forceStringNoCtx(*i->value, pos, context_below);
|
||||||
|
else if (i->name == state.sOutputHashMode)
|
||||||
|
handleHashMode(state.forceStringNoCtx(*i->value, pos, context_below));
|
||||||
|
else if (i->name == state.sOutputs) {
|
||||||
|
/* Require ‘outputs’ to be a list of strings. */
|
||||||
|
state.forceList(*i->value, pos, context_below);
|
||||||
|
Strings ss;
|
||||||
|
for (auto elem : i->value->listItems())
|
||||||
|
ss.emplace_back(state.forceStringNoCtx(*elem, pos, context_below));
|
||||||
|
handleOutputs(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
auto s = state.coerceToString(pos, *i->value, context, context_below, true).toOwned();
|
||||||
|
drv.env.emplace(key, s);
|
||||||
|
if (i->name == state.sBuilder) drv.builder = std::move(s);
|
||||||
|
else if (i->name == state.sSystem) drv.platform = std::move(s);
|
||||||
|
else if (i->name == state.sOutputHash) outputHash = std::move(s);
|
||||||
|
else if (i->name == state.sOutputHashAlgo) outputHashAlgo = std::move(s);
|
||||||
|
else if (i->name == state.sOutputHashMode) handleHashMode(s);
|
||||||
|
else if (i->name == state.sOutputs)
|
||||||
|
handleOutputs(tokenizeString<Strings>(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(state.positions[i->pos],
|
||||||
|
HintFmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObject) {
|
||||||
|
drv.env.emplace("__json", jsonObject->dump());
|
||||||
|
jsonObject.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Everything in the context of the strings in the derivation
|
||||||
|
attributes should be added as dependencies of the resulting
|
||||||
|
derivation. */
|
||||||
|
for (auto & c : context) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
/* Since this allows the builder to gain access to every
|
||||||
|
path in the dependency graph of the derivation (including
|
||||||
|
all outputs), all paths in the graph must be added to
|
||||||
|
this derivation's list of inputs to ensure that they are
|
||||||
|
available when the builder runs. */
|
||||||
|
[&](const NixStringContextElem::DrvDeep & d) {
|
||||||
|
/* !!! This doesn't work if readOnlyMode is set. */
|
||||||
|
StorePathSet refs;
|
||||||
|
state.store->computeFSClosure(d.drvPath, refs);
|
||||||
|
for (auto & j : refs) {
|
||||||
|
drv.inputSrcs.insert(j);
|
||||||
|
if (j.isDerivation()) {
|
||||||
|
drv.inputDrvs.map[j].value = state.store->readDerivation(j).outputNames();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::Built & b) {
|
||||||
|
drv.inputDrvs.ensureSlot(*b.drvPath).value.insert(b.output);
|
||||||
|
},
|
||||||
|
[&](const NixStringContextElem::Opaque & o) {
|
||||||
|
drv.inputSrcs.insert(o.path);
|
||||||
|
},
|
||||||
|
}, c.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do we have all required attributes? */
|
||||||
|
if (drv.builder == "")
|
||||||
|
state.error<EvalError>("required attribute 'builder' missing")
|
||||||
|
.atPos(v)
|
||||||
|
.debugThrow();
|
||||||
|
|
||||||
|
if (drv.platform == "")
|
||||||
|
state.error<EvalError>("required attribute 'system' missing")
|
||||||
|
.atPos(v)
|
||||||
|
.debugThrow();
|
||||||
|
|
||||||
|
/* Check whether the derivation name is valid. */
|
||||||
|
if (isDerivation(drvName) &&
|
||||||
|
!(ingestionMethod == ContentAddressMethod { TextIngestionMethod { } } &&
|
||||||
|
outputs.size() == 1 &&
|
||||||
|
*(outputs.begin()) == "out"))
|
||||||
|
{
|
||||||
|
state.error<EvalError>(
|
||||||
|
"derivation names are allowed to end in '%s' only if they produce a single derivation file",
|
||||||
|
drvExtension
|
||||||
|
).atPos(v).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputHash) {
|
||||||
|
/* Handle fixed-output derivations.
|
||||||
|
|
||||||
|
Ignore `__contentAddressed` because fixed output derivations are
|
||||||
|
already content addressed. */
|
||||||
|
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||||
|
state.error<EvalError>(
|
||||||
|
"multiple outputs are not supported in fixed-output derivations"
|
||||||
|
).atPos(v).debugThrow();
|
||||||
|
|
||||||
|
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||||
|
|
||||||
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
|
||||||
|
|
||||||
|
DerivationOutput::CAFixed dof {
|
||||||
|
.ca = ContentAddress {
|
||||||
|
.method = std::move(method),
|
||||||
|
.hash = std::move(h),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
drv.env["out"] = state.store->printStorePath(dof.path(*state.store, drvName, "out"));
|
||||||
|
drv.outputs.insert_or_assign("out", std::move(dof));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (contentAddressed || isImpure) {
|
||||||
|
if (contentAddressed && isImpure)
|
||||||
|
state.error<EvalError>("derivation cannot be both content-addressed and impure")
|
||||||
|
.atPos(v).debugThrow();
|
||||||
|
|
||||||
|
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
||||||
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
|
||||||
|
|
||||||
|
for (auto & i : outputs) {
|
||||||
|
drv.env[i] = hashPlaceholder(i);
|
||||||
|
if (isImpure)
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::Impure {
|
||||||
|
.method = method,
|
||||||
|
.hashType = ht,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::CAFloating {
|
||||||
|
.method = method,
|
||||||
|
.hashType = ht,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
/* Compute a hash over the "masked" store derivation, which is
|
||||||
|
the final one except that in the list of outputs, the
|
||||||
|
output paths are empty strings, and the corresponding
|
||||||
|
environment variables have an empty value. This ensures
|
||||||
|
that changes in the set of output names do get reflected in
|
||||||
|
the hash. */
|
||||||
|
for (auto & i : outputs) {
|
||||||
|
drv.env[i] = "";
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::Deferred { });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
|
||||||
|
switch (hashModulo.kind) {
|
||||||
|
case DrvHash::Kind::Regular:
|
||||||
|
for (auto & i : outputs) {
|
||||||
|
auto h = get(hashModulo.hashes, i);
|
||||||
|
if (!h)
|
||||||
|
state.error<AssertionError>(
|
||||||
|
"derivation produced no hash for output '%s'",
|
||||||
|
i
|
||||||
|
).atPos(v).debugThrow();
|
||||||
|
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
||||||
|
drv.env[i] = state.store->printStorePath(outPath);
|
||||||
|
drv.outputs.insert_or_assign(
|
||||||
|
i,
|
||||||
|
DerivationOutput::InputAddressed {
|
||||||
|
.path = std::move(outPath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
;
|
||||||
|
case DrvHash::Kind::Deferred:
|
||||||
|
for (auto & i : outputs) {
|
||||||
|
drv.outputs.insert_or_assign(i, DerivationOutput::Deferred {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the resulting term into the Nix store directory. */
|
||||||
|
auto drvPath = writeDerivation(*state.store, drv, state.repair);
|
||||||
|
auto drvPathS = state.store->printStorePath(drvPath);
|
||||||
|
|
||||||
|
printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
|
||||||
|
|
||||||
|
/* Optimisation, but required in read-only mode! because in that
|
||||||
|
case we don't actually write store derivations, so we can't
|
||||||
|
read them later. */
|
||||||
|
{
|
||||||
|
auto h = hashDerivationModulo(*state.store, drv, false);
|
||||||
|
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = state.buildBindings(1 + drv.outputs.size());
|
||||||
|
result.alloc(state.sDrvPath).mkString(drvPathS, {
|
||||||
|
NixStringContextElem::DrvDeep { .drvPath = drvPath },
|
||||||
|
});
|
||||||
|
for (auto & i : drv.outputs)
|
||||||
|
mkOutputString(state, result, drvPath, i);
|
||||||
|
|
||||||
|
v.mkAttrs(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Construct (as a unobservable side effect) a Nix derivation
|
||||||
|
expression that performs the derivation described by the argument
|
||||||
|
set. Returns the original set extended with the following
|
||||||
|
attributes: `outPath' containing the primary output path of the
|
||||||
|
derivation; `drvPath' containing the path of the Nix expression;
|
||||||
|
and `type' set to `derivation' to indicate that this is a
|
||||||
|
derivation. */
|
||||||
|
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
|
||||||
|
|
||||||
|
Bindings * attrs = args[0]->attrs;
|
||||||
|
|
||||||
|
/* Figure out the name first (for stack backtraces). */
|
||||||
|
Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
|
||||||
|
|
||||||
|
std::string drvName;
|
||||||
|
try {
|
||||||
|
drvName = state.forceStringNoCtx(*nameAttr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(state.positions[nameAttr->pos], "while evaluating the derivation attribute 'name'");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
derivationStrictInternal(state, drvName, attrs, v);
|
||||||
|
} catch (Error & e) {
|
||||||
|
Pos pos = state.positions[nameAttr->pos];
|
||||||
|
/*
|
||||||
|
* Here we make two abuses of the error system
|
||||||
|
*
|
||||||
|
* 1. We print the location as a string to avoid a code snippet being
|
||||||
|
* printed. While the location of the name attribute is a good hint, the
|
||||||
|
* exact code there is irrelevant.
|
||||||
|
*
|
||||||
|
* 2. We mark this trace as a frame trace, meaning that we stop printing
|
||||||
|
* less important traces from now on. In particular, this prevents the
|
||||||
|
* display of the automatic "while calling builtins.derivationStrict"
|
||||||
|
* trace, which is of little use for the public we target here.
|
||||||
|
*
|
||||||
|
* Please keep in mind that error reporting is done on a best-effort
|
||||||
|
* basis in nix. There is no accurate location for a derivation, as it
|
||||||
|
* often results from the composition of several functions
|
||||||
|
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
|
||||||
|
*/
|
||||||
|
e.addTrace(nullptr, HintFmt(
|
||||||
|
"while evaluating derivation '%s'\n"
|
||||||
|
" whose name attribute is located at %s",
|
||||||
|
drvName, pos));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_derivationStrict(PrimOp {
|
||||||
|
.name = "derivationStrict",
|
||||||
|
.arity = 1,
|
||||||
|
.fun = prim_derivationStrict,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,20 @@
|
||||||
#include "primops.hh"
|
#include <optional> // for optional
|
||||||
#include "store-api.hh"
|
#include <string> // for allocator, char_tr...
|
||||||
#include "make-content-addressed.hh"
|
#include "attr-set.hh" // for Attr, Bindings
|
||||||
#include "url.hh"
|
#include "error.hh" // for Error, Trace
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature
|
||||||
|
#include "fmt.hh" // for HintFmt
|
||||||
|
#include "make-content-addressed.hh" // for makeContentAddressed
|
||||||
|
#include "path-info.hh" // for ValidPathInfo
|
||||||
|
#include "path.hh" // for StorePath
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "realisation.hh" // for RealisedPath
|
||||||
|
#include "store-api.hh" // for Store, copyClosure
|
||||||
|
#include "url.hh" // for ParsedURL, parseURL
|
||||||
|
#include "util.hh" // for getEnv, operator+
|
||||||
|
#include "value.hh" // for Value, nString
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -207,7 +220,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
runFetchClosureWithContentAddressedPath(state, pos, *fromStore, *fromPath, v);
|
runFetchClosureWithContentAddressedPath(state, pos, *fromStore, *fromPath, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchClosure({
|
PrimOp primop_fetchClosure({
|
||||||
.name = "__fetchClosure",
|
.name = "__fetchClosure",
|
||||||
.args = {"args"},
|
.args = {"args"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
#include "primops.hh"
|
#include <optional> // for optional
|
||||||
#include "eval-inline.hh"
|
#include <regex> // for regex_match
|
||||||
#include "eval-settings.hh"
|
#include <string> // for allocator, char_traits, basic_string
|
||||||
#include "store-api.hh"
|
#include <string_view> // for operator==, basic_string_view, string_view
|
||||||
#include "fetchers.hh"
|
#include <utility> // for move
|
||||||
#include "url.hh"
|
#include "attr-set.hh" // for Attr, BindingsBuilder, Bindings
|
||||||
#include "url-parts.hh"
|
#include "attrs.hh" // for Attrs
|
||||||
|
#include "error.hh" // for Error
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, evalSettings
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "fetchers.hh" // for Input, Tree
|
||||||
|
#include "hash.hh" // for Hash, HashType
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolStr, SymbolTable
|
||||||
|
#include "types.hh" // for BackedStringView
|
||||||
|
#include "url-parts.hh" // for revRegex
|
||||||
|
#include "value.hh" // for Value, nAttrs
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -83,7 +96,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
state.allowPath(tree.storePath);
|
state.allowPath(tree.storePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r_fetchMercurial({
|
PrimOp primop_fetchMercurial({
|
||||||
.name = "fetchMercurial",
|
.name = "fetchMercurial",
|
||||||
.arity = 1,
|
.arity = 1,
|
||||||
.fun = prim_fetchMercurial
|
.fun = prim_fetchMercurial
|
||||||
|
|
|
@ -1,15 +1,36 @@
|
||||||
#include "primops.hh"
|
#include <assert.h> // for assert
|
||||||
#include "eval-inline.hh"
|
#include <stdint.h> // for uint64_t
|
||||||
#include "eval-settings.hh"
|
#include <ctime> // for gmtime
|
||||||
#include "store-api.hh"
|
#include <iomanip> // for put_time, operator<<
|
||||||
#include "fetchers.hh"
|
#include <map> // for operator==, map
|
||||||
#include "filetransfer.hh"
|
#include <optional> // for optional, operator==
|
||||||
#include "registry.hh"
|
#include <regex> // for regex_match, regex...
|
||||||
#include "url.hh"
|
#include <string> // for char_traits, basic...
|
||||||
|
#include <string_view> // for operator==, basic_...
|
||||||
#include <ctime>
|
#include <utility> // for move, pair
|
||||||
#include <iomanip>
|
#include "attr-set.hh" // for Attr, BindingsBuilder
|
||||||
#include <regex>
|
#include "attrs.hh" // for maybeGetStrAttr
|
||||||
|
#include "config.hh" // for Setting, Experimen...
|
||||||
|
#include "content-address.hh" // for FileIngestionMethod
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErr...
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, eval...
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature
|
||||||
|
#include "fetchers.hh" // for Input, Tree, downl...
|
||||||
|
#include "fmt.hh" // for fmt
|
||||||
|
#include "hash.hh" // for Hash, HashType, Base
|
||||||
|
#include "path-info.hh" // for ValidPathInfo
|
||||||
|
#include "path.hh" // for StorePath
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "registry.hh" // for lookupInRegistries
|
||||||
|
#include "store-api.hh" // for Store
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolTable
|
||||||
|
#include "types.hh" // for BackedStringView
|
||||||
|
#include "url.hh" // for ParsedURL
|
||||||
|
#include "util.hh" // for baseNameOf
|
||||||
|
#include "value.hh" // for Value, nAttrs, nBool
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -198,7 +219,7 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: document
|
// FIXME: document
|
||||||
static RegisterPrimOp primop_fetchTree({
|
PrimOp primop_fetchTree({
|
||||||
.name = "fetchTree",
|
.name = "fetchTree",
|
||||||
.arity = 1,
|
.arity = 1,
|
||||||
.fun = prim_fetchTree
|
.fun = prim_fetchTree
|
||||||
|
@ -290,7 +311,7 @@ static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
fetch(state, pos, args, v, "fetchurl", false, "");
|
fetch(state, pos, args, v, "fetchurl", false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchurl({
|
PrimOp primop_fetchurl({
|
||||||
.name = "__fetchurl",
|
.name = "__fetchurl",
|
||||||
.args = {"url"},
|
.args = {"url"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
@ -306,7 +327,7 @@ static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchTarball({
|
PrimOp primop_fetchTarball({
|
||||||
.name = "fetchTarball",
|
.name = "fetchTarball",
|
||||||
.args = {"args"},
|
.args = {"args"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
@ -356,7 +377,7 @@ static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchGit({
|
PrimOp primop_fetchGit({
|
||||||
.name = "fetchGit",
|
.name = "fetchGit",
|
||||||
.args = {"args"},
|
.args = {"args"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
#include "primops.hh"
|
#include <stddef.h> // for size_t
|
||||||
#include "eval-inline.hh"
|
#include <stdint.h> // for int64_t
|
||||||
|
#include <exception> // for exception
|
||||||
#include <sstream>
|
#include <functional> // for function
|
||||||
#include <toml.hpp>
|
#include <sstream> // for char_traits, basic_ostringstream
|
||||||
|
#include <stdexcept> // for runtime_error
|
||||||
|
#include <string> // for basic_string, operator<<, operat...
|
||||||
|
#include <toml/comments.hpp> // for operator<<
|
||||||
|
#include <toml/datetime.hpp> // for operator<<
|
||||||
|
#include <toml/get.hpp> // for get
|
||||||
|
#include <toml/parser.hpp> // for parse
|
||||||
|
#include <toml/serializer.hpp> // for operator<<
|
||||||
|
#include <toml/types.hpp> // for value_t
|
||||||
|
#include <toml/value.hpp> // for value, table
|
||||||
|
#include <unordered_map> // for unordered_map, _Node_iterator
|
||||||
|
#include <utility> // for pair
|
||||||
|
#include <vector> // for vector
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder
|
||||||
|
#include "config.hh" // for ExperimentalFeatureSettings, exp...
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature, Xp
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "value.hh" // for Value, NixFloat
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -86,7 +105,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fromTOML({
|
PrimOp primop_fromTOML({
|
||||||
.name = "fromTOML",
|
.name = "fromTOML",
|
||||||
.args = {"e"},
|
.args = {"e"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
|
|
75
src/libexpr/primops/hash.cc
Normal file
75
src/libexpr/primops/hash.cc
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#include "hash.hh"
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "primops.hh" // for realisePath
|
||||||
|
#include "source-path.hh" // for SourcePath
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.hashFile
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_hashFile(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto type = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.hashFile"
|
||||||
|
);
|
||||||
|
std::optional<HashType> ht = parseHashType(type);
|
||||||
|
if (!ht) {
|
||||||
|
state.error<EvalError>("unknown hash type '%1%'", type).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = realisePath(state, pos, *args[1]);
|
||||||
|
|
||||||
|
v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_hashFile({
|
||||||
|
.name = "__hashFile",
|
||||||
|
.args = {"type", "p"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a base-16 representation of the cryptographic hash of the
|
||||||
|
file at path *p*. The hash algorithm specified by *type* must be one
|
||||||
|
of `"md5"`, `"sha1"`, `"sha256"` or `"sha512"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_hashFile,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.hashString
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_hashString(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto type = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.hashString"
|
||||||
|
);
|
||||||
|
std::optional<HashType> ht = parseHashType(type);
|
||||||
|
if (!ht) {
|
||||||
|
state.error<EvalError>("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
NixStringContext context; // discarded
|
||||||
|
auto s = state.forceString(
|
||||||
|
*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString"
|
||||||
|
);
|
||||||
|
|
||||||
|
v.mkString(hashString(*ht, s).to_string(Base16, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_hashString({
|
||||||
|
.name = "__hashString",
|
||||||
|
.args = {"type", "s"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a base-16 representation of the cryptographic hash of string
|
||||||
|
*s*. The hash algorithm specified by *type* must be one of `"md5"`,
|
||||||
|
`"sha1"`, `"sha256"` or `"sha512"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_hashString,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
290
src/libexpr/primops/import.cc
Normal file
290
src/libexpr/primops/import.cc
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
#include <dlfcn.h> // for dlerror, dlopen, dlsym, RTLD_LAZY, RTLD_...
|
||||||
|
#include <memory> // for allocator, __shared_ptr_access, shared_ptr
|
||||||
|
#include <optional> // for optional, nullopt, nullopt_t
|
||||||
|
#include <string> // for basic_string, char_traits, string, opera...
|
||||||
|
#include <utility> // for pair
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder, Bindings, Attr
|
||||||
|
#include "canon-path.hh" // for CanonPath
|
||||||
|
#include "derivations.hh" // for Derivation, DerivationOutput, isDerivation
|
||||||
|
#include "derived-path.hh" // for makeConstantStorePathRef, SingleDerivedPath
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp, Env, resolveExprPath
|
||||||
|
#include "logging.hh" // for Logger, debug
|
||||||
|
#include "nixexpr.hh" // for StaticEnv, Expr
|
||||||
|
#include "path.hh" // for StorePath
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "primops.hh" // for realisePath, prim_importNative, mkOutput...
|
||||||
|
#include "ref.hh" // for ref
|
||||||
|
#include "source-path.hh" // for SourcePath
|
||||||
|
#include "store-api.hh" // for Store
|
||||||
|
#include "symbol-table.hh" // for Symbol
|
||||||
|
#include "util.hh" // for enumerate
|
||||||
|
#include "value.hh" // for Value, allocRootValue
|
||||||
|
#include "value/context.hh" // for NixStringContextElem
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/* Want reasonable symbol names, so extern C */
|
||||||
|
/* !!! Should we pass the Pos or the file name too? */
|
||||||
|
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add and attribute to the given attribute map from the output name to
|
||||||
|
* the output path, or a placeholder.
|
||||||
|
*
|
||||||
|
* Where possible the path is used, but for floating CA derivations we
|
||||||
|
* may not know it. For sake of determinism we always assume we don't
|
||||||
|
* and instead put in a place holder. In either case, however, the
|
||||||
|
* string context will contain the drv path and output name, so
|
||||||
|
* downstream derivations will have the proper dependency, and in
|
||||||
|
* addition, before building, the placeholder will be rewritten to be
|
||||||
|
* the actual path.
|
||||||
|
*
|
||||||
|
* The 'drv' and 'drvPath' outputs must correspond.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void mkOutputString(
|
||||||
|
EvalState & state,
|
||||||
|
BindingsBuilder & attrs,
|
||||||
|
const StorePath & drvPath,
|
||||||
|
const std::pair<std::string, DerivationOutput> & o
|
||||||
|
)
|
||||||
|
{
|
||||||
|
state.mkOutputString(
|
||||||
|
attrs.alloc(o.first),
|
||||||
|
SingleDerivedPath::Built{
|
||||||
|
.drvPath = makeConstantStorePathRef(drvPath),
|
||||||
|
.output = o.first,
|
||||||
|
},
|
||||||
|
o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load and evaluate an expression from path specified by the
|
||||||
|
argument. */
|
||||||
|
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
|
||||||
|
{
|
||||||
|
auto path = realisePath(state, pos, vPath);
|
||||||
|
auto path2 = path.path.abs();
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
|
||||||
|
if (!state.store->isStorePath(path2)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto storePath = state.store->parseStorePath(path2);
|
||||||
|
if (!(state.store->isValidPath(storePath) && isDerivation(path2))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return storePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (auto storePath = isValidDerivationInStore()) {
|
||||||
|
Derivation drv = state.store->readDerivation(*storePath);
|
||||||
|
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||||
|
attrs.alloc(state.sDrvPath)
|
||||||
|
.mkString(
|
||||||
|
path2,
|
||||||
|
{
|
||||||
|
NixStringContextElem::DrvDeep{.drvPath = *storePath},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||||
|
auto & outputsVal = attrs.alloc(state.sOutputs);
|
||||||
|
state.mkList(outputsVal, drv.outputs.size());
|
||||||
|
|
||||||
|
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||||
|
mkOutputString(state, attrs, *storePath, o);
|
||||||
|
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto w = state.allocValue();
|
||||||
|
w->mkAttrs(attrs);
|
||||||
|
|
||||||
|
if (!state.vImportedDrvToDerivation) {
|
||||||
|
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||||
|
state.eval(
|
||||||
|
state.parseExprFromString(
|
||||||
|
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||||
|
, CanonPath::root
|
||||||
|
),
|
||||||
|
**state.vImportedDrvToDerivation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.forceFunction(
|
||||||
|
**state.vImportedDrvToDerivation,
|
||||||
|
pos,
|
||||||
|
"while evaluating imported-drv-to-derivation.nix.gen.hh"
|
||||||
|
);
|
||||||
|
v.mkApp(*state.vImportedDrvToDerivation, w);
|
||||||
|
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (path2 == corepkgsPrefix + "fetchurl.nix")
|
||||||
|
{
|
||||||
|
state.eval(
|
||||||
|
state.parseExprFromString(
|
||||||
|
#include "fetchurl.nix.gen.hh"
|
||||||
|
, CanonPath::root
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!vScope) {
|
||||||
|
state.evalFile(path, v);
|
||||||
|
} else {
|
||||||
|
state.forceAttrs(
|
||||||
|
*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport"
|
||||||
|
);
|
||||||
|
|
||||||
|
Env * env = &state.allocEnv(vScope->attrs->size());
|
||||||
|
env->up = &state.baseEnv;
|
||||||
|
|
||||||
|
auto staticEnv = std::make_shared<StaticEnv>(
|
||||||
|
nullptr, state.staticBaseEnv.get(), vScope->attrs->size()
|
||||||
|
);
|
||||||
|
|
||||||
|
unsigned int displ = 0;
|
||||||
|
for (auto & attr : *vScope->attrs) {
|
||||||
|
staticEnv->vars.emplace_back(attr.name, displ);
|
||||||
|
env->values[displ++] = attr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to call staticEnv.sort(), because
|
||||||
|
// args[0]->attrs is already sorted.
|
||||||
|
|
||||||
|
debug("evaluating file '%1%'", path);
|
||||||
|
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
|
||||||
|
|
||||||
|
e->eval(state, *env, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prim_import(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
import(state, pos, *args[0], nullptr, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_import({
|
||||||
|
.name = "import",
|
||||||
|
.args = {"path"},
|
||||||
|
// TODO turn "normal path values" into link below
|
||||||
|
.doc = R"(
|
||||||
|
Load, parse and return the Nix expression in the file *path*.
|
||||||
|
|
||||||
|
The value *path* can be a path, a string, or an attribute set with an
|
||||||
|
`__toString` attribute or a `outPath` attribute (as derivations or flake
|
||||||
|
inputs typically have).
|
||||||
|
|
||||||
|
If *path* is a directory, the file `default.nix` in that directory
|
||||||
|
is loaded.
|
||||||
|
|
||||||
|
Evaluation aborts if the file doesn’t exist or contains
|
||||||
|
an incorrect Nix expression. `import` implements Nix’s module
|
||||||
|
system: you can put any Nix expression (such as a set or a
|
||||||
|
function) in a separate file, and use it from Nix expressions in
|
||||||
|
other files.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Unlike some languages, `import` is a regular function in Nix.
|
||||||
|
> Paths using the angle bracket syntax (e.g., `import` *\<foo\>*)
|
||||||
|
> are normal [path values](@docroot@/language/values.md#type-path).
|
||||||
|
|
||||||
|
A Nix expression loaded by `import` must not contain any *free
|
||||||
|
variables* (identifiers that are not defined in the Nix expression
|
||||||
|
itself and are not built-in). Therefore, it cannot refer to
|
||||||
|
variables that are in scope at the call site. For instance, if you
|
||||||
|
have a calling expression
|
||||||
|
|
||||||
|
```nix
|
||||||
|
rec {
|
||||||
|
x = 123;
|
||||||
|
y = import ./foo.nix;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
then the following `foo.nix` will give an error:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
x + 456
|
||||||
|
```
|
||||||
|
|
||||||
|
since `x` is not in scope in `foo.nix`. If you want `x` to be
|
||||||
|
available in `foo.nix`, you should pass it as a function argument:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
rec {
|
||||||
|
x = 123;
|
||||||
|
y = import ./foo.nix x;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
```nix
|
||||||
|
x: x + 456
|
||||||
|
```
|
||||||
|
|
||||||
|
(The function argument doesn’t have to be called `x` in `foo.nix`;
|
||||||
|
any name would work.)
|
||||||
|
)",
|
||||||
|
.fun = prim_import,
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Load a ValueInitializer from a DSO and return whatever it initializes */
|
||||||
|
void prim_importNative(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
|
|
||||||
|
std::string sym(state.forceStringNoCtx(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.importNative"
|
||||||
|
));
|
||||||
|
|
||||||
|
void * handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
|
if (!handle) {
|
||||||
|
state.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
dlerror();
|
||||||
|
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
||||||
|
if (!func) {
|
||||||
|
char * message = dlerror();
|
||||||
|
if (message) {
|
||||||
|
state
|
||||||
|
.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message)
|
||||||
|
.debugThrow();
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected",
|
||||||
|
sym,
|
||||||
|
path
|
||||||
|
)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(func)(state, v);
|
||||||
|
|
||||||
|
/* We don't dlclose because v may be a primop referencing a function in the shared object file
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prim_scopedImport(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
import(state, pos, *args[1], args[0], v);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_scopedImport(PrimOp{
|
||||||
|
.name = "scopedImport",
|
||||||
|
.arity = 2,
|
||||||
|
.fun = prim_scopedImport,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
70
src/libexpr/primops/json.cc
Normal file
70
src/libexpr/primops/json.cc
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#include <sstream> // for basic_ostringstream, basic_ios, basic_os...
|
||||||
|
#include "eval.hh" // for PrimOp, EvalState
|
||||||
|
#include "json-to-value.hh" // for JSONParseError, parseJSON
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "value-to-json.hh" // for printValueAsJSON
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.fromJSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_fromJSON(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto s = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.fromJSON"
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
parseJSON(state, s, v);
|
||||||
|
} catch (JSONParseError & e) {
|
||||||
|
e.addTrace(state.positions[pos], "while decoding a JSON string");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_fromJSON({
|
||||||
|
.name = "__fromJSON",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Convert a JSON string to a Nix value. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.fromJSON ''{"x": [1, 2, 3], "y": null}''
|
||||||
|
```
|
||||||
|
|
||||||
|
returns the value `{ x = [ 1 2 3 ]; y = null; }`.
|
||||||
|
)",
|
||||||
|
.fun = prim_fromJSON,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.toJSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_toJSON(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
NixStringContext context;
|
||||||
|
printValueAsJSON(state, true, *args[0], pos, out, context);
|
||||||
|
v.mkString(out.str(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_toJSON({
|
||||||
|
.name = "__toJSON",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a string containing a JSON representation of *e*. Strings,
|
||||||
|
integers, floats, booleans, nulls and lists are mapped to their JSON
|
||||||
|
equivalents. Sets (except derivations) are represented as objects.
|
||||||
|
Derivations are translated to a JSON string containing the
|
||||||
|
derivation’s output path. Paths are copied to the store and
|
||||||
|
represented as a JSON string of the resulting store path.
|
||||||
|
)",
|
||||||
|
.fun = prim_toJSON,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
694
src/libexpr/primops/list.cc
Normal file
694
src/libexpr/primops/list.cc
Normal file
|
@ -0,0 +1,694 @@
|
||||||
|
#include <string.h> // for memcpy, size_t
|
||||||
|
#include <algorithm> // for stable_sort
|
||||||
|
#include <boost/container/vector.hpp> // for vector
|
||||||
|
#include <functional> // for function
|
||||||
|
#include <set> // for set
|
||||||
|
#include <string> // for basic_string, allocator, opera...
|
||||||
|
#include <utility> // for pair
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder, Bindings
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "gc-small-vector.hh" // for SmallTemporaryValueVector, Sma...
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "primops.hh" // for getAttr, CompareValues, prim_l...
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolTable
|
||||||
|
#include "util.hh" // for enumerate
|
||||||
|
#include "value.hh" // for Value, ValueVector
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
const std::string fun = any ? "builtins.any" : "builtins.all";
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, std::string("while evaluating the first argument passed to " + fun)
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, std::string("while evaluating the second argument passed to " + fun)
|
||||||
|
);
|
||||||
|
Value vTmp;
|
||||||
|
for (auto elem : args[1]->listItems()) {
|
||||||
|
state.callFunction(*args[0], *elem, vTmp, pos);
|
||||||
|
bool res = state.forceBool(
|
||||||
|
vTmp, pos, "while evaluating the return value of the function passed to " + fun
|
||||||
|
);
|
||||||
|
if (res == any) {
|
||||||
|
v.mkBool(any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.mkBool(!any);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
||||||
|
if (n < 0 || (unsigned int) n >= list.listSize()) {
|
||||||
|
state.error<EvalError>("list index %1% is out of bounds", n).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
state.forceValue(*list.listElems()[n], pos);
|
||||||
|
v = *list.listElems()[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.all
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_all(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
anyOrAll(false, state, pos, args, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_all({
|
||||||
|
.name = "__all",
|
||||||
|
.args = {"pred", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if the function *pred* returns `true` for all elements
|
||||||
|
of *list*, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_all,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.any
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_any(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
anyOrAll(true, state, pos, args, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_any({
|
||||||
|
.name = "__any",
|
||||||
|
.args = {"pred", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if the function *pred* returns `true` for at least one
|
||||||
|
element of *list*, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_any,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.concatLists
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_concatLists(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.concatLists"
|
||||||
|
);
|
||||||
|
state.concatLists(
|
||||||
|
v,
|
||||||
|
args[0]->listSize(),
|
||||||
|
args[0]->listElems(),
|
||||||
|
pos,
|
||||||
|
"while evaluating a value of the list passed to builtins.concatLists"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_concatLists({
|
||||||
|
.name = "__concatLists",
|
||||||
|
.args = {"lists"},
|
||||||
|
.doc = R"(
|
||||||
|
Concatenate a list of lists into a single list.
|
||||||
|
)",
|
||||||
|
.fun = prim_concatLists,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.concatMap
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_concatMap(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.concatMap"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.concatMap"
|
||||||
|
);
|
||||||
|
auto nrLists = args[1]->listSize();
|
||||||
|
|
||||||
|
// List of returned lists before concatenation. References to these Values must NOT be
|
||||||
|
// persisted.
|
||||||
|
SmallTemporaryValueVector<conservativeStackReservation> lists(nrLists);
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||||
|
Value * vElem = args[1]->listElems()[n];
|
||||||
|
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||||
|
state.forceList(
|
||||||
|
lists[n],
|
||||||
|
lists[n].determinePos(args[0]->determinePos(pos)),
|
||||||
|
"while evaluating the return value of the function passed to builtins.concatMap"
|
||||||
|
);
|
||||||
|
len += lists[n].listSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.mkList(v, len);
|
||||||
|
auto out = v.listElems();
|
||||||
|
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
|
||||||
|
auto l = lists[n].listSize();
|
||||||
|
if (l) {
|
||||||
|
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
|
||||||
|
}
|
||||||
|
pos += l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_concatMap({
|
||||||
|
.name = "__concatMap",
|
||||||
|
.args = {"f", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
This function is equivalent to `builtins.concatLists (map f list)`
|
||||||
|
but is more efficient.
|
||||||
|
)",
|
||||||
|
.fun = prim_concatMap,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.elem
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_elem(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
bool res = false;
|
||||||
|
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.elem");
|
||||||
|
for (auto elem : args[1]->listItems()) {
|
||||||
|
if (state.eqValues(
|
||||||
|
*args[0],
|
||||||
|
*elem,
|
||||||
|
pos,
|
||||||
|
"while searching for the presence of the given element in the list"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.mkBool(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_elem({
|
||||||
|
.name = "__elem",
|
||||||
|
.args = {"x", "xs"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if a value equal to *x* occurs in the list *xs*, and
|
||||||
|
`false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_elem,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.elemAt
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_elemAt(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
elemAt(
|
||||||
|
state,
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_elemAt({
|
||||||
|
.name = "__elemAt",
|
||||||
|
.args = {"xs", "n"},
|
||||||
|
.doc = R"(
|
||||||
|
Return element *n* from the list *xs*. Elements are counted starting
|
||||||
|
from 0. A fatal error occurs if the index is out of bounds.
|
||||||
|
)",
|
||||||
|
.fun = prim_elemAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.filter
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_filter(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.filter"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (args[1]->listSize() == 0) {
|
||||||
|
v = *args[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.filter"
|
||||||
|
);
|
||||||
|
|
||||||
|
SmallValueVector<nonRecursiveStackReservation> vs(args[1]->listSize());
|
||||||
|
size_t k = 0;
|
||||||
|
|
||||||
|
bool same = true;
|
||||||
|
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
|
||||||
|
Value res;
|
||||||
|
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
|
||||||
|
if (state.forceBool(
|
||||||
|
res,
|
||||||
|
pos,
|
||||||
|
"while evaluating the return value of the filtering function passed to "
|
||||||
|
"builtins.filter"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
vs[k++] = args[1]->listElems()[n];
|
||||||
|
} else {
|
||||||
|
same = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (same) {
|
||||||
|
v = *args[1];
|
||||||
|
} else {
|
||||||
|
state.mkList(v, k);
|
||||||
|
for (unsigned int n = 0; n < k; ++n) {
|
||||||
|
v.listElems()[n] = vs[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.foldl'
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.foldlStrict"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[2], pos, "while evaluating the third argument passed to builtins.foldlStrict"
|
||||||
|
);
|
||||||
|
if (args[2]->listSize()) {
|
||||||
|
Value * vCur = args[1];
|
||||||
|
for (auto [n, elem] : enumerate(args[2]->listItems())) {
|
||||||
|
Value * vs[]{vCur, elem};
|
||||||
|
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||||
|
state.callFunction(*args[0], 2, vs, *vCur, pos);
|
||||||
|
}
|
||||||
|
state.forceValue(v, pos);
|
||||||
|
} else {
|
||||||
|
state.forceValue(*args[1], pos);
|
||||||
|
v = *args[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_foldlStrict({
|
||||||
|
.name = "__foldl'",
|
||||||
|
.args = {"op", "nul", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Reduce a list by applying a binary operator, from left to right,
|
||||||
|
e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2)
|
||||||
|
...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.
|
||||||
|
The return value of each application of `op` is evaluated immediately,
|
||||||
|
even for intermediate values.
|
||||||
|
)",
|
||||||
|
.fun = prim_foldlStrict,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.genList
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_genList(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto len = state.forceInt(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.genList"
|
||||||
|
);
|
||||||
|
if (len < 0) {
|
||||||
|
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
// More strict than striclty (!) necessary, but acceptable
|
||||||
|
// as evaluating map without accessing any values makes little sense.
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], noPos, "while evaluating the first argument passed to builtins.genList"
|
||||||
|
);
|
||||||
|
state.mkList(v, len);
|
||||||
|
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||||
|
auto arg = state.allocValue();
|
||||||
|
arg->mkInt(n);
|
||||||
|
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_genList({
|
||||||
|
.name = "__genList",
|
||||||
|
.args = {"generator", "length"},
|
||||||
|
.doc = R"(
|
||||||
|
Generate list of size *length*, with each element *i* equal to the
|
||||||
|
value returned by *generator* `i`. For example,
|
||||||
|
```nix
|
||||||
|
builtins.genList (x: x * x) 5
|
||||||
|
```
|
||||||
|
returns the list `[ 0 1 4 9 16 ]`.
|
||||||
|
)",
|
||||||
|
.fun = prim_genList,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.head
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_head(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
elemAt(state, pos, *args[0], 0, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_head({
|
||||||
|
.name = "__head",
|
||||||
|
.args = {"list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the first element of a list; abort evaluation if the argument
|
||||||
|
isn’t a list or is an empty list. You can test whether a list is
|
||||||
|
empty by comparing it with `[]`.
|
||||||
|
)",
|
||||||
|
.fun = prim_head,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.length
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_length(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.length");
|
||||||
|
v.mkInt(args[0]->listSize());
|
||||||
|
}
|
||||||
|
PrimOp primop_length({
|
||||||
|
.name = "__length",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the length of the list *e*.
|
||||||
|
)",
|
||||||
|
.fun = prim_length,
|
||||||
|
});
|
||||||
|
|
||||||
|
PrimOp primop_filter({
|
||||||
|
.name = "__filter",
|
||||||
|
.args = {"f", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a list consisting of the elements of *list* for which the
|
||||||
|
function *f* returns `true`.
|
||||||
|
)",
|
||||||
|
.fun = prim_filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.listToAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(args[0]->listSize());
|
||||||
|
|
||||||
|
std::set<Symbol> seen;
|
||||||
|
|
||||||
|
for (auto v2 : args[0]->listItems()) {
|
||||||
|
state.forceAttrs(
|
||||||
|
*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
Bindings::iterator j =
|
||||||
|
getAttr(state, state.sName, v2->attrs, "in a {name=...; value=...;} pair");
|
||||||
|
|
||||||
|
auto name = state.forceStringNoCtx(
|
||||||
|
*j->value,
|
||||||
|
j->pos,
|
||||||
|
"while evaluating the `name` attribute of an element of the list passed to "
|
||||||
|
"builtins.listToAttrs"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto sym = state.symbols.create(name);
|
||||||
|
if (seen.insert(sym).second) {
|
||||||
|
Bindings::iterator j2 =
|
||||||
|
getAttr(state, state.sValue, v2->attrs, "in a {name=...; value=...;} pair");
|
||||||
|
attrs.insert(sym, j2->value, j2->pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_listToAttrs({
|
||||||
|
.name = "__listToAttrs",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Construct a set from a list specifying the names and values of each
|
||||||
|
attribute. Each element of the list should be a set consisting of a
|
||||||
|
string-valued attribute `name` specifying the name of the attribute,
|
||||||
|
and an attribute `value` specifying its value.
|
||||||
|
|
||||||
|
In case of duplicate occurrences of the same name, the first
|
||||||
|
takes precedence.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.listToAttrs
|
||||||
|
[ { name = "foo"; value = 123; }
|
||||||
|
{ name = "bar"; value = 456; }
|
||||||
|
{ name = "bar"; value = 420; }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ foo = 123; bar = 456; }
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_listToAttrs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.map
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_map(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.map");
|
||||||
|
|
||||||
|
if (args[1]->listSize() == 0) {
|
||||||
|
v = *args[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.map"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.mkList(v, args[1]->listSize());
|
||||||
|
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||||
|
(v.listElems()[n] = state.allocValue())->mkApp(args[0], args[1]->listElems()[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_map({
|
||||||
|
.name = "map",
|
||||||
|
.args = {"f", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Apply the function *f* to each element in the list *list*. For
|
||||||
|
example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
map (x: "foo" + x) [ "bar" "bla" "abc" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
|
||||||
|
)",
|
||||||
|
.fun = prim_map,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.partition
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_partition(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.partition"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.partition"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto len = args[1]->listSize();
|
||||||
|
|
||||||
|
ValueVector right, wrong;
|
||||||
|
|
||||||
|
for (unsigned int n = 0; n < len; ++n) {
|
||||||
|
auto vElem = args[1]->listElems()[n];
|
||||||
|
state.forceValue(*vElem, pos);
|
||||||
|
Value res;
|
||||||
|
state.callFunction(*args[0], *vElem, res, pos);
|
||||||
|
if (state.forceBool(
|
||||||
|
res,
|
||||||
|
pos,
|
||||||
|
"while evaluating the return value of the partition function passed to "
|
||||||
|
"builtins.partition"
|
||||||
|
))
|
||||||
|
{
|
||||||
|
right.push_back(vElem);
|
||||||
|
} else {
|
||||||
|
wrong.push_back(vElem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(2);
|
||||||
|
|
||||||
|
auto & vRight = attrs.alloc(state.sRight);
|
||||||
|
auto rsize = right.size();
|
||||||
|
state.mkList(vRight, rsize);
|
||||||
|
if (rsize) {
|
||||||
|
memcpy(vRight.listElems(), right.data(), sizeof(Value *) * rsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto & vWrong = attrs.alloc(state.sWrong);
|
||||||
|
auto wsize = wrong.size();
|
||||||
|
state.mkList(vWrong, wsize);
|
||||||
|
if (wsize) {
|
||||||
|
memcpy(vWrong.listElems(), wrong.data(), sizeof(Value *) * wsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_partition({
|
||||||
|
.name = "__partition",
|
||||||
|
.args = {"pred", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Given a predicate function *pred*, this function returns an
|
||||||
|
attrset containing a list named `right`, containing the elements
|
||||||
|
in *list* for which *pred* returned `true`, and a list named
|
||||||
|
`wrong`, containing the elements for which it returned
|
||||||
|
`false`. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.partition (x: x > 10) [1 23 9 3 42]
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ right = [ 23 42 ]; wrong = [ 1 9 3 ]; }
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_partition,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.sort
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_sort(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.sort");
|
||||||
|
|
||||||
|
auto len = args[1]->listSize();
|
||||||
|
if (len == 0) {
|
||||||
|
v = *args[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.forceFunction(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.sort"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.mkList(v, len);
|
||||||
|
for (unsigned int n = 0; n < len; ++n) {
|
||||||
|
state.forceValue(*args[1]->listElems()[n], pos);
|
||||||
|
v.listElems()[n] = args[1]->listElems()[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto comparator = [&](Value * a, Value * b) {
|
||||||
|
/* Optimization: if the comparator is lessThan, bypass
|
||||||
|
callFunction. */
|
||||||
|
/* TODO: (layus) this is absurd. An optimisation like this
|
||||||
|
should be outside the lambda creation */
|
||||||
|
if (args[0]->isPrimOp()) {
|
||||||
|
auto ptr = args[0]->primOp->fun.target<decltype(&prim_lessThan)>();
|
||||||
|
if (ptr && *ptr == prim_lessThan) {
|
||||||
|
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value * vs[] = {a, b};
|
||||||
|
Value vBool;
|
||||||
|
state.callFunction(*args[0], 2, vs, vBool, noPos);
|
||||||
|
return state.forceBool(
|
||||||
|
vBool,
|
||||||
|
pos,
|
||||||
|
"while evaluating the return value of the sorting function passed to builtins.sort"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* FIXME: std::sort can segfault if the comparator is not a strict
|
||||||
|
weak ordering. What to do? std::stable_sort() seems more
|
||||||
|
resilient, but no guarantees... */
|
||||||
|
std::stable_sort(v.listElems(), v.listElems() + len, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_sort({
|
||||||
|
.name = "__sort",
|
||||||
|
.args = {"comparator", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return *list* in sorted order. It repeatedly calls the function
|
||||||
|
*comparator* with two elements. The comparator should return `true`
|
||||||
|
if the first element is less than the second, and `false` otherwise.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
produces the list `[ 42 77 147 249 483 526 ]`.
|
||||||
|
|
||||||
|
This is a stable sort: it preserves the relative order of elements
|
||||||
|
deemed equal by the comparator.
|
||||||
|
)",
|
||||||
|
.fun = prim_sort,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.tail
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_tail(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
||||||
|
if (args[0]->listSize() == 0) {
|
||||||
|
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.mkList(v, args[0]->listSize() - 1);
|
||||||
|
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||||
|
v.listElems()[n] = args[0]->listElems()[n + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_tail({
|
||||||
|
.name = "__tail",
|
||||||
|
.args = {"list"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the second to last elements of a list; abort evaluation if
|
||||||
|
the argument isn’t a list or is an empty list.
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> This function should generally be avoided since it's inefficient:
|
||||||
|
> unlike Haskell's `tail`, it takes O(n) time, so recursing over a
|
||||||
|
> list by repeatedly calling `tail` takes O(n^2) time.
|
||||||
|
)",
|
||||||
|
.fun = prim_tail,
|
||||||
|
});
|
||||||
|
}
|
966
src/libexpr/primops/path.cc
Normal file
966
src/libexpr/primops/path.cc
Normal file
|
@ -0,0 +1,966 @@
|
||||||
|
#include <sys/stat.h> // for stat, S_ISDIR, S_I...
|
||||||
|
#include <list> // for list
|
||||||
|
#include <optional> // for optional, operator!=
|
||||||
|
#include <set> // for _Rb_tree_const_ite...
|
||||||
|
#include <string> // for basic_string, char...
|
||||||
|
#include <string_view> // for string_view
|
||||||
|
#include <utility> // for move, pair
|
||||||
|
#include <variant> // for get_if
|
||||||
|
#include "attr-set.hh" // for Attr, Bindings
|
||||||
|
#include "canon-path.hh" // for CanonPath
|
||||||
|
#include "content-address.hh" // for FileIngestionMethod
|
||||||
|
#include "derivations.hh" // for hashPlaceholder
|
||||||
|
#include "derived-path.hh" // for SingleDerivedPath
|
||||||
|
#include "error.hh" // for Error, SysError
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErr...
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, eval...
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "experimental-features.hh" // for ExperimentalFeature
|
||||||
|
#include "fetch-to-store.hh" // for fetchToStore
|
||||||
|
#include "globals.hh" // for Settings, settings
|
||||||
|
#include "hash.hh" // for Hash, newHashAllow...
|
||||||
|
#include "input-accessor.hh" // for InputAccessor
|
||||||
|
#include "outputs-spec.hh" // for OutputNameView
|
||||||
|
#include "path-info.hh" // for ValidPathInfo
|
||||||
|
#include "path-references.hh" // for PathRefScanSink
|
||||||
|
#include "path.hh" // for StorePath, StorePa...
|
||||||
|
#include "pos-idx.hh" // for PosIdx, noPos
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "primops.hh" // for RealisePathFlags
|
||||||
|
#include "ref.hh" // for ref, make_ref
|
||||||
|
#include "search-path.hh" // for SearchPath
|
||||||
|
#include "serialise.hh" // for operator<<, Sink
|
||||||
|
#include "source-path.hh" // for SourcePath, Symlin...
|
||||||
|
#include "store-api.hh" // for Store
|
||||||
|
#include "symbol-table.hh" // for Symbol, SymbolStr
|
||||||
|
#include "types.hh" // for BackedStringView
|
||||||
|
#include "util.hh" // for rewriteStrings
|
||||||
|
#include "value.hh" // for Value, nPath, nString
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
641
src/libexpr/primops/string.cc
Normal file
641
src/libexpr/primops/string.cc
Normal file
|
@ -0,0 +1,641 @@
|
||||||
|
#include <assert.h> // for assert
|
||||||
|
#include <stddef.h> // for size_t
|
||||||
|
#include <iterator> // for distance
|
||||||
|
#include <memory> // for shared_ptr, __shared_ptr_access, make_sh...
|
||||||
|
#include <regex> // for regex_error, match_results, cregex_iterator
|
||||||
|
#include <string> // for basic_string, char_traits, string
|
||||||
|
#include <string_view> // for basic_string_view
|
||||||
|
#include <unordered_map> // for unordered_map, operator==, _Node_iterator
|
||||||
|
#include <utility> // for move, pair
|
||||||
|
#include <vector> // for vector
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp, makeRegexCache, prim_...
|
||||||
|
#include "names.hh" // for DrvName, compareVersions, nextComponent
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "primops.hh" // for RegexCache
|
||||||
|
#include "symbol-table.hh" // for Symbol
|
||||||
|
#include "types.hh" // for BackedStringView, Strings
|
||||||
|
#include "util.hh" // for baseNameOf, enumerate
|
||||||
|
#include "value.hh" // for Value, nString
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::shared_ptr<RegexCache> makeRegexCache()
|
||||||
|
{
|
||||||
|
return std::make_shared<RegexCache>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.baseNameOf
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
v.mkString(
|
||||||
|
baseNameOf(*state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the first argument passed to builtins.baseNameOf",
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)),
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_baseNameOf({
|
||||||
|
.name = "baseNameOf",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the *base name* of the string *s*, that is, everything
|
||||||
|
following the final slash in the string. This is similar to the GNU
|
||||||
|
`basename` command.
|
||||||
|
)",
|
||||||
|
.fun = prim_baseNameOf,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.compareVersions
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_compareVersions(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto version1 = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.compareVersions"
|
||||||
|
);
|
||||||
|
auto version2 = state.forceStringNoCtx(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.compareVersions"
|
||||||
|
);
|
||||||
|
v.mkInt(compareVersions(version1, version2));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_compareVersions({
|
||||||
|
.name = "__compareVersions",
|
||||||
|
.args = {"s1", "s2"},
|
||||||
|
.doc = R"(
|
||||||
|
Compare two strings representing versions and return `-1` if
|
||||||
|
version *s1* is older than version *s2*, `0` if they are the same,
|
||||||
|
and `1` if *s1* is newer than *s2*. The version comparison
|
||||||
|
algorithm is the same as the one used by [`nix-env
|
||||||
|
-u`](../command-ref/nix-env.md#operation---upgrade).
|
||||||
|
)",
|
||||||
|
.fun = prim_compareVersions,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.concatStringsSep
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
|
||||||
|
auto sep = state.forceString(
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
pos,
|
||||||
|
"while evaluating the first argument (the separator string) passed to "
|
||||||
|
"builtins.concatStringsSep"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1],
|
||||||
|
pos,
|
||||||
|
"while evaluating the second argument (the list of strings to concat) passed to "
|
||||||
|
"builtins.concatStringsSep"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string res;
|
||||||
|
res.reserve((args[1]->listSize() + 32) * sep.size());
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
for (auto elem : args[1]->listItems()) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
res += sep;
|
||||||
|
}
|
||||||
|
res += *state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*elem,
|
||||||
|
context,
|
||||||
|
"while evaluating one element of the list of strings to concat passed to "
|
||||||
|
"builtins.concatStringsSep"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkString(res, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_concatStringsSep(
|
||||||
|
{.name = "__concatStringsSep",
|
||||||
|
.args = {"separator", "list"},
|
||||||
|
.doc = R"(
|
||||||
|
Concatenate a list of strings with a separator between each
|
||||||
|
element, e.g. `concatStringsSep "/" ["usr" "local" "bin"] ==
|
||||||
|
"usr/local/bin"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_concatStringsSep}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.match
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_match(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto re = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.match"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
auto regex = state.regexCache->get(re);
|
||||||
|
|
||||||
|
NixStringContext context;
|
||||||
|
const auto str = state.forceString(
|
||||||
|
*args[1], context, pos, "while evaluating the second argument passed to builtins.match"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::cmatch match;
|
||||||
|
if (!std::regex_match(str.begin(), str.end(), match, regex)) {
|
||||||
|
v.mkNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the first match is the whole string
|
||||||
|
const size_t len = match.size() - 1;
|
||||||
|
state.mkList(v, len);
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
if (!match[i + 1].matched) {
|
||||||
|
(v.listElems()[i] = state.allocValue())->mkNull();
|
||||||
|
} else {
|
||||||
|
(v.listElems()[i] = state.allocValue())->mkString(match[i + 1].str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (std::regex_error & e) {
|
||||||
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
|
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
|
||||||
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
|
} else {
|
||||||
|
state.error<EvalError>("invalid regular expression '%s'", re).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_match({
|
||||||
|
.name = "__match",
|
||||||
|
.args = {"regex", "str"},
|
||||||
|
.doc = R"s(
|
||||||
|
Returns a list if the [extended POSIX regular
|
||||||
|
expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04)
|
||||||
|
*regex* matches *str* precisely, otherwise returns `null`. Each item
|
||||||
|
in the list is a regex group.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.match "ab" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `null`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.match "abc" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ ]`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.match "a(b)(c)" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ "b" "c" ]`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " FOO "
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ "FOO" ]`.
|
||||||
|
)s",
|
||||||
|
.fun = prim_match,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.parseDrvName
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_parseDrvName(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto name = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.parseDrvName"
|
||||||
|
);
|
||||||
|
DrvName parsed(name);
|
||||||
|
auto attrs = state.buildBindings(2);
|
||||||
|
attrs.alloc(state.sName).mkString(parsed.name);
|
||||||
|
attrs.alloc("version").mkString(parsed.version);
|
||||||
|
v.mkAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_parseDrvName({
|
||||||
|
.name = "__parseDrvName",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Split the string *s* into a package name and version. The package
|
||||||
|
name is everything up to but not including the first dash not followed
|
||||||
|
by a letter, and the version is everything following that dash. The
|
||||||
|
result is returned in a set `{ name, version }`. Thus,
|
||||||
|
`builtins.parseDrvName "nix-0.12pre12876"` returns `{ name =
|
||||||
|
"nix"; version = "0.12pre12876"; }`.
|
||||||
|
)",
|
||||||
|
.fun = prim_parseDrvName,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.replaceStrings
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings"
|
||||||
|
);
|
||||||
|
state.forceList(
|
||||||
|
*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings"
|
||||||
|
);
|
||||||
|
if (args[0]->listSize() != args[1]->listSize()) {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
|
||||||
|
)
|
||||||
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> from;
|
||||||
|
from.reserve(args[0]->listSize());
|
||||||
|
for (auto elem : args[0]->listItems()) {
|
||||||
|
from.emplace_back(state.forceString(
|
||||||
|
*elem,
|
||||||
|
pos,
|
||||||
|
"while evaluating one of the strings to replace passed to builtins.replaceStrings"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<size_t, std::string> cache;
|
||||||
|
auto to = args[1]->listItems();
|
||||||
|
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state.forceString(
|
||||||
|
*args[2],
|
||||||
|
context,
|
||||||
|
pos,
|
||||||
|
"while evaluating the third argument passed to builtins.replaceStrings"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string res;
|
||||||
|
// Loops one past last character to handle the case where 'from' contains an empty string.
|
||||||
|
for (size_t p = 0; p <= s.size();) {
|
||||||
|
bool found = false;
|
||||||
|
auto i = from.begin();
|
||||||
|
auto j = to.begin();
|
||||||
|
size_t j_index = 0;
|
||||||
|
for (; i != from.end(); ++i, ++j, ++j_index) {
|
||||||
|
if (s.compare(p, i->size(), *i) == 0) {
|
||||||
|
found = true;
|
||||||
|
auto v = cache.find(j_index);
|
||||||
|
if (v == cache.end()) {
|
||||||
|
NixStringContext ctx;
|
||||||
|
auto ts = state.forceString(
|
||||||
|
**j,
|
||||||
|
ctx,
|
||||||
|
pos,
|
||||||
|
"while evaluating one of the replacement strings passed to "
|
||||||
|
"builtins.replaceStrings"
|
||||||
|
);
|
||||||
|
v = (cache.emplace(j_index, ts)).first;
|
||||||
|
for (auto & path : ctx) {
|
||||||
|
context.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += v->second;
|
||||||
|
if (i->empty()) {
|
||||||
|
if (p < s.size()) {
|
||||||
|
res += s[p];
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
} else {
|
||||||
|
p += i->size();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
if (p < s.size()) {
|
||||||
|
res += s[p];
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.mkString(res, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_replaceStrings({
|
||||||
|
.name = "__replaceStrings",
|
||||||
|
.args = {"from", "to", "s"},
|
||||||
|
.doc = R"(
|
||||||
|
Given string *s*, replace every occurrence of the strings in *from*
|
||||||
|
with the corresponding string in *to*.
|
||||||
|
|
||||||
|
The argument *to* is lazy, that is, it is only evaluated when its corresponding pattern in *from* is matched in the string *s*
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.replaceStrings ["oo" "a"] ["a" "i"] "foobar"
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `"fabir"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_replaceStrings,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.split
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_split(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto re = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.split"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
auto regex = state.regexCache->get(re);
|
||||||
|
|
||||||
|
NixStringContext context;
|
||||||
|
const auto str = state.forceString(
|
||||||
|
*args[1], context, pos, "while evaluating the second argument passed to builtins.split"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
|
||||||
|
auto end = std::cregex_iterator();
|
||||||
|
|
||||||
|
// Any matches results are surrounded by non-matching results.
|
||||||
|
const size_t len = std::distance(begin, end);
|
||||||
|
state.mkList(v, 2 * len + 1);
|
||||||
|
size_t idx = 0;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
v.listElems()[idx++] = args[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = begin; i != end; ++i) {
|
||||||
|
assert(idx <= 2 * len + 1 - 3);
|
||||||
|
auto match = *i;
|
||||||
|
|
||||||
|
// Add a string for non-matched characters.
|
||||||
|
(v.listElems()[idx++] = state.allocValue())->mkString(match.prefix().str());
|
||||||
|
|
||||||
|
// Add a list for matched substrings.
|
||||||
|
const size_t slen = match.size() - 1;
|
||||||
|
auto elem = v.listElems()[idx++] = state.allocValue();
|
||||||
|
|
||||||
|
// Start at 1, beacause the first match is the whole string.
|
||||||
|
state.mkList(*elem, slen);
|
||||||
|
for (size_t si = 0; si < slen; ++si) {
|
||||||
|
if (!match[si + 1].matched) {
|
||||||
|
(elem->listElems()[si] = state.allocValue())->mkNull();
|
||||||
|
} else {
|
||||||
|
(elem->listElems()[si] = state.allocValue())->mkString(match[si + 1].str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a string for non-matched suffix characters.
|
||||||
|
if (idx == 2 * len) {
|
||||||
|
(v.listElems()[idx++] = state.allocValue())->mkString(match.suffix().str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(idx == 2 * len + 1);
|
||||||
|
|
||||||
|
} catch (std::regex_error & e) {
|
||||||
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
|
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
|
||||||
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
|
} else {
|
||||||
|
state.error<EvalError>("invalid regular expression '%s'", re).atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_split({
|
||||||
|
.name = "__split",
|
||||||
|
.args = {"regex", "str"},
|
||||||
|
.doc = R"s(
|
||||||
|
Returns a list composed of non matched strings interleaved with the
|
||||||
|
lists of the [extended POSIX regular
|
||||||
|
expression](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04)
|
||||||
|
*regex* matches of *str*. Each item in the lists of matched
|
||||||
|
sequences is a regex group.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.split "(a)b" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ "" [ "a" ] "c" ]`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.split "([ac])" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ "" [ "a" ] "b" [ "c" ] "" ]`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.split "(a)|(c)" "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ "" [ "a" null ] "b" [ null "c" ] "" ]`.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.split "([[:upper:]]+)" " FOO "
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluates to `[ " " [ "FOO" ] " " ]`.
|
||||||
|
)s",
|
||||||
|
.fun = prim_split,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.splitVersion
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_splitVersion(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
auto version = state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.splitVersion"
|
||||||
|
);
|
||||||
|
auto iter = version.cbegin();
|
||||||
|
Strings components;
|
||||||
|
while (iter != version.cend()) {
|
||||||
|
auto component = nextComponent(iter, version.cend());
|
||||||
|
if (component.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
components.emplace_back(component);
|
||||||
|
}
|
||||||
|
state.mkList(v, components.size());
|
||||||
|
for (const auto & [n, component] : enumerate(components)) {
|
||||||
|
(v.listElems()[n] = state.allocValue())->mkString(std::move(component));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_splitVersion({
|
||||||
|
.name = "__splitVersion",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
Split a string representing a version into its components, by the
|
||||||
|
same version splitting logic underlying the version comparison in
|
||||||
|
[`nix-env -u`](../command-ref/nix-env.md#operation---upgrade).
|
||||||
|
)",
|
||||||
|
.fun = prim_splitVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.stringLength
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_stringLength(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state.coerceToString(
|
||||||
|
pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength"
|
||||||
|
);
|
||||||
|
v.mkInt(s->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_stringLength({
|
||||||
|
.name = "__stringLength",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the length of the string *e*. If *e* is not a string,
|
||||||
|
evaluation is aborted.
|
||||||
|
)",
|
||||||
|
.fun = prim_stringLength,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.substring
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_substring(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
int start = state.forceInt(
|
||||||
|
*args[0],
|
||||||
|
pos,
|
||||||
|
"while evaluating the first argument (the start offset) passed to builtins.substring"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = state.forceInt(
|
||||||
|
*args[1],
|
||||||
|
pos,
|
||||||
|
"while evaluating the second argument (the substring length) passed to builtins.substring"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Special-case on empty substring to avoid O(n) strlen
|
||||||
|
// This allows for the use of empty substrings to efficently capture string context
|
||||||
|
if (len == 0) {
|
||||||
|
state.forceValue(*args[2], pos);
|
||||||
|
if (args[2]->type() == nString) {
|
||||||
|
v.mkString("", args[2]->string.context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[2],
|
||||||
|
context,
|
||||||
|
"while evaluating the third argument (the string) passed to builtins.substring"
|
||||||
|
);
|
||||||
|
|
||||||
|
v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_substring({
|
||||||
|
.name = "__substring",
|
||||||
|
.args = {"start", "len", "s"},
|
||||||
|
.doc = R"(
|
||||||
|
Return the substring of *s* from character position *start*
|
||||||
|
(zero-based) up to but not including *start + len*. If *start* is
|
||||||
|
greater than the length of the string, an empty string is returned,
|
||||||
|
and if *start + len* lies beyond the end of the string, only the
|
||||||
|
substring up to the end of the string is returned. *start* must be
|
||||||
|
non-negative. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.substring 0 3 "nixos"
|
||||||
|
```
|
||||||
|
|
||||||
|
evaluates to `"nix"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_substring,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.toString
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_toString(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
NixStringContext context;
|
||||||
|
auto s = state.coerceToString(
|
||||||
|
pos,
|
||||||
|
*args[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the first argument passed to builtins.toString",
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
v.mkString(*s, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_toString({
|
||||||
|
.name = "toString",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Convert the expression *e* to a string. *e* can be:
|
||||||
|
|
||||||
|
- A string (in which case the string is returned unmodified).
|
||||||
|
|
||||||
|
- A path (e.g., `toString /foo/bar` yields `"/foo/bar"`.
|
||||||
|
|
||||||
|
- A set containing `{ __toString = self: ...; }` or `{ outPath = ...; }`.
|
||||||
|
|
||||||
|
- An integer.
|
||||||
|
|
||||||
|
- A list, in which case the string representations of its elements
|
||||||
|
are joined with spaces.
|
||||||
|
|
||||||
|
- A Boolean (`false` yields `""`, `true` yields `"1"`).
|
||||||
|
|
||||||
|
- `null`, which yields the empty string.
|
||||||
|
)",
|
||||||
|
.fun = prim_toString,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
116
src/libexpr/primops/system.cc
Normal file
116
src/libexpr/primops/system.cc
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
#include <optional> // for optional
|
||||||
|
#include <string> // for basic_string, char...
|
||||||
|
#include <utility> // for move
|
||||||
|
#include "canon-path.hh" // for CanonPath
|
||||||
|
#include "error.hh" // for Error
|
||||||
|
#include "eval-error.hh" // for EvalError, EvalErr...
|
||||||
|
#include "eval-settings.hh" // for EvalSettings, eval...
|
||||||
|
#include "eval.hh" // for EvalState, PrimOp
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "pos-table.hh" // for PosTable
|
||||||
|
#include "primops.hh" // for prim_exec
|
||||||
|
#include "types.hh" // for BackedStringView
|
||||||
|
#include "util.hh" // for getEnv, runProgram
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct Expr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.exec
|
||||||
|
*/
|
||||||
|
|
||||||
|
void prim_exec(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.exec");
|
||||||
|
auto elems = args[0]->listElems();
|
||||||
|
auto count = args[0]->listSize();
|
||||||
|
if (count == 0) {
|
||||||
|
state.error<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
NixStringContext context;
|
||||||
|
auto program =
|
||||||
|
state
|
||||||
|
.coerceToString(
|
||||||
|
pos,
|
||||||
|
*elems[0],
|
||||||
|
context,
|
||||||
|
"while evaluating the first element of the argument passed to builtins.exec",
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.toOwned();
|
||||||
|
Strings commandArgs;
|
||||||
|
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
|
||||||
|
commandArgs.push_back(
|
||||||
|
state
|
||||||
|
.coerceToString(
|
||||||
|
pos,
|
||||||
|
*elems[i],
|
||||||
|
context,
|
||||||
|
"while evaluating an element of the argument passed to builtins.exec",
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.toOwned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||||
|
} catch (InvalidPathError & e) {
|
||||||
|
state
|
||||||
|
.error<EvalError>(
|
||||||
|
"cannot execute '%1%', since path '%2%' is not valid", program, e.path
|
||||||
|
)
|
||||||
|
.atPos(pos)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto output = runProgram(program, true, commandArgs);
|
||||||
|
Expr * parsed;
|
||||||
|
try {
|
||||||
|
parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
state.eval(parsed, v);
|
||||||
|
} catch (Error & e) {
|
||||||
|
e.addTrace(state.positions[pos], "while evaluating the output from '%1%'", program);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.getEnv
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_getEnv(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
std::string name(state.forceStringNoCtx(
|
||||||
|
*args[0], pos, "while evaluating the first argument passed to builtins.getEnv"
|
||||||
|
));
|
||||||
|
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_getEnv({
|
||||||
|
.name = "__getEnv",
|
||||||
|
.args = {"s"},
|
||||||
|
.doc = R"(
|
||||||
|
`getEnv` returns the value of the environment variable *s*, or an
|
||||||
|
empty string if the variable doesn’t exist. This function should be
|
||||||
|
used with care, as it can introduce all sorts of nasty environment
|
||||||
|
dependencies in your Nix expression.
|
||||||
|
|
||||||
|
`getEnv` is used in Nix Packages to locate the file
|
||||||
|
`~/.nixpkgs/config.nix`, which contains user-local settings for Nix
|
||||||
|
Packages. (That is, it does a `getEnv "HOME"` to locate the user’s
|
||||||
|
home directory.)
|
||||||
|
)",
|
||||||
|
.fun = prim_getEnv,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
119
src/libexpr/primops/toXML.cc
Normal file
119
src/libexpr/primops/toXML.cc
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#include <sstream> // for basic_ostringstream, basic_ios, basic_os...
|
||||||
|
#include "eval.hh" // for PrimOp, EvalState (ptr only)
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "value.hh" // for Value
|
||||||
|
#include "value-to-xml.hh" // for printValueAsXML
|
||||||
|
#include "value/context.hh" // for NixStringContext
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.toXML
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_toXML(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
NixStringContext context;
|
||||||
|
printValueAsXML(state, true, false, *args[0], out, context, pos);
|
||||||
|
v.mkString(out.str(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_toXML({
|
||||||
|
.name = "__toXML",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a string containing an XML representation of *e*. The main
|
||||||
|
application for `toXML` is to communicate information with the
|
||||||
|
builder in a more structured format than plain environment
|
||||||
|
variables.
|
||||||
|
|
||||||
|
Here is an example where this is the case:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ stdenv, fetchurl, libxslt, jira, uberwiki }:
|
||||||
|
|
||||||
|
stdenv.mkDerivation (rec {
|
||||||
|
name = "web-server";
|
||||||
|
|
||||||
|
buildInputs = [ libxslt ];
|
||||||
|
|
||||||
|
builder = builtins.toFile "builder.sh" "
|
||||||
|
source $stdenv/setup
|
||||||
|
mkdir $out
|
||||||
|
echo "$servlets" | xsltproc ${stylesheet} - > $out/server-conf.xml ①
|
||||||
|
";
|
||||||
|
|
||||||
|
stylesheet = builtins.toFile "stylesheet.xsl" ②
|
||||||
|
"<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
|
||||||
|
<xsl:template match='/'>
|
||||||
|
<Configure>
|
||||||
|
<xsl:for-each select='/expr/list/attrs'>
|
||||||
|
<Call name='addWebApplication'>
|
||||||
|
<Arg><xsl:value-of select=\"attr[@name = 'path']/string/@value\" /></Arg>
|
||||||
|
<Arg><xsl:value-of select=\"attr[@name = 'war']/path/@value\" /></Arg>
|
||||||
|
</Call>
|
||||||
|
</xsl:for-each>
|
||||||
|
</Configure>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
|
";
|
||||||
|
|
||||||
|
servlets = builtins.toXML [ ③
|
||||||
|
{ path = "/bugtracker"; war = jira + "/lib/atlassian-jira.war"; }
|
||||||
|
{ path = "/wiki"; war = uberwiki + "/uberwiki.war"; }
|
||||||
|
];
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The builder is supposed to generate the configuration file for a
|
||||||
|
[Jetty servlet container](http://jetty.mortbay.org/). A servlet
|
||||||
|
container contains a number of servlets (`*.war` files) each
|
||||||
|
exported under a specific URI prefix. So the servlet configuration
|
||||||
|
is a list of sets containing the `path` and `war` of the servlet
|
||||||
|
(①). This kind of information is difficult to communicate with the
|
||||||
|
normal method of passing information through an environment
|
||||||
|
variable, which just concatenates everything together into a
|
||||||
|
string (which might just work in this case, but wouldn’t work if
|
||||||
|
fields are optional or contain lists themselves). Instead the Nix
|
||||||
|
expression is converted to an XML representation with `toXML`,
|
||||||
|
which is unambiguous and can easily be processed with the
|
||||||
|
appropriate tools. For instance, in the example an XSLT stylesheet
|
||||||
|
(at point ②) is applied to it (at point ①) to generate the XML
|
||||||
|
configuration file for the Jetty server. The XML representation
|
||||||
|
produced at point ③ by `toXML` is as follows:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<expr>
|
||||||
|
<list>
|
||||||
|
<attrs>
|
||||||
|
<attr name="path">
|
||||||
|
<string value="/bugtracker" />
|
||||||
|
</attr>
|
||||||
|
<attr name="war">
|
||||||
|
<path value="/nix/store/d1jh9pasa7k2...-jira/lib/atlassian-jira.war" />
|
||||||
|
</attr>
|
||||||
|
</attrs>
|
||||||
|
<attrs>
|
||||||
|
<attr name="path">
|
||||||
|
<string value="/wiki" />
|
||||||
|
</attr>
|
||||||
|
<attr name="war">
|
||||||
|
<path value="/nix/store/y6423b1yi4sx...-uberwiki/uberwiki.war" />
|
||||||
|
</attr>
|
||||||
|
</attrs>
|
||||||
|
</list>
|
||||||
|
</expr>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we used the `toFile` built-in to write the builder and
|
||||||
|
the stylesheet “inline” in the Nix expression. The path of the
|
||||||
|
stylesheet is spliced into the builder using the syntax `xsltproc
|
||||||
|
${stylesheet}`.
|
||||||
|
)",
|
||||||
|
.fun = prim_toXML,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
245
src/libexpr/primops/types.cc
Normal file
245
src/libexpr/primops/types.cc
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
#include <stdlib.h> // for abort
|
||||||
|
#include <string> // for basic_string, char_traits, string
|
||||||
|
#include "attr-set.hh" // for BindingsBuilder
|
||||||
|
#include "eval-error.hh" // for TypeError, EvalErrorBuilder
|
||||||
|
#include "eval.hh" // for PrimOp, EvalState
|
||||||
|
#include "nixexpr.hh" // for ExprLambda, Formal, Formals
|
||||||
|
#include "pos-idx.hh" // for PosIdx
|
||||||
|
#include "symbol-table.hh" // for Symbol
|
||||||
|
#include "value.hh" // for Value, nAttrs, nBool, nFloat, nFunction
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic isType function
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline auto prim_isType(auto n)
|
||||||
|
{
|
||||||
|
return [n](EvalState & state, const PosIdx pos, Value ** args, Value & v) {
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
v.mkBool(args[0]->type() == n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.functionArgs
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_functionArgs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) {
|
||||||
|
v.mkAttrs(&state.emptyBindings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!args[0]->isLambda()) {
|
||||||
|
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[0]->lambda.fun->hasFormals()) {
|
||||||
|
v.mkAttrs(&state.emptyBindings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
|
||||||
|
for (auto & i : args[0]->lambda.fun->formals->formals) {
|
||||||
|
// !!! should optimise booleans (allocate only once)
|
||||||
|
attrs.alloc(i.name, i.pos).mkBool(i.def);
|
||||||
|
}
|
||||||
|
v.mkAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_functionArgs({
|
||||||
|
.name = "__functionArgs",
|
||||||
|
.args = {"f"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a set containing the names of the formal arguments expected
|
||||||
|
by the function *f*. The value of each attribute is a Boolean
|
||||||
|
denoting whether the corresponding argument has a default value. For
|
||||||
|
instance, `functionArgs ({ x, y ? 123}: ...) = { x = false; y =
|
||||||
|
true; }`.
|
||||||
|
|
||||||
|
"Formal argument" here refers to the attributes pattern-matched by
|
||||||
|
the function. Plain lambdas are not included, e.g. `functionArgs (x:
|
||||||
|
...) = { }`.
|
||||||
|
)",
|
||||||
|
.fun = prim_functionArgs,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isAttrs
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isAttrs({
|
||||||
|
.name = "__isAttrs",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a set, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nAttrs),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isBool
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isBool({
|
||||||
|
.name = "__isBool",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a bool, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nBool),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.Float
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isFloat({
|
||||||
|
.name = "__isFloat",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a float, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nFloat),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isFunction
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isFunction({
|
||||||
|
.name = "__isFunction",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a function, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nFunction),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isInt
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isInt({
|
||||||
|
.name = "__isInt",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to an integer, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nInt),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isList
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isList({
|
||||||
|
.name = "__isList",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a list, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nList),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isNull
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isNull({
|
||||||
|
.name = "isNull",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to `null`, and `false` otherwise.
|
||||||
|
|
||||||
|
This is equivalent to `e == null`.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nNull),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isPath
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isPath({
|
||||||
|
.name = "__isPath",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a path, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nPath),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.isString
|
||||||
|
*/
|
||||||
|
|
||||||
|
PrimOp primop_isString({
|
||||||
|
.name = "__isString",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return `true` if *e* evaluates to a string, and `false` otherwise.
|
||||||
|
)",
|
||||||
|
.fun = prim_isType(nString),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* builtins.typeOf
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceValue(*args[0], pos);
|
||||||
|
std::string t;
|
||||||
|
switch (args[0]->type()) {
|
||||||
|
case nInt:
|
||||||
|
t = "int";
|
||||||
|
break;
|
||||||
|
case nBool:
|
||||||
|
t = "bool";
|
||||||
|
break;
|
||||||
|
case nString:
|
||||||
|
t = "string";
|
||||||
|
break;
|
||||||
|
case nPath:
|
||||||
|
t = "path";
|
||||||
|
break;
|
||||||
|
case nNull:
|
||||||
|
t = "null";
|
||||||
|
break;
|
||||||
|
case nAttrs:
|
||||||
|
t = "set";
|
||||||
|
break;
|
||||||
|
case nList:
|
||||||
|
t = "list";
|
||||||
|
break;
|
||||||
|
case nFunction:
|
||||||
|
t = "lambda";
|
||||||
|
break;
|
||||||
|
case nExternal:
|
||||||
|
t = args[0]->external->typeOf();
|
||||||
|
break;
|
||||||
|
case nFloat:
|
||||||
|
t = "float";
|
||||||
|
break;
|
||||||
|
case nThunk:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
v.mkString(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimOp primop_typeOf({
|
||||||
|
.name = "__typeOf",
|
||||||
|
.args = {"e"},
|
||||||
|
.doc = R"(
|
||||||
|
Return a string representing the type of the value *e*, namely
|
||||||
|
`"int"`, `"bool"`, `"string"`, `"path"`, `"null"`, `"set"`,
|
||||||
|
`"list"`, `"lambda"` or `"float"`.
|
||||||
|
)",
|
||||||
|
.fun = prim_typeOf,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue