1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-02 13:31:48 +02:00

Merge remote-tracking branch 'upstream/master' into overlayfs-store

This commit is contained in:
John Ericson 2023-07-09 20:30:23 -04:00
commit 28398e6d02
65 changed files with 1481 additions and 612 deletions

View file

@ -151,7 +151,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
},
ExtraPathInfoFlake::Flake {
.originalRef = flakeRef,
.resolvedRef = getLockedFlake()->flake.lockedRef,
.lockedRef = getLockedFlake()->flake.lockedRef,
}),
}};
}

View file

@ -19,7 +19,7 @@ struct ExtraPathInfoFlake : ExtraPathInfoValue
*/
struct Flake {
FlakeRef originalRef;
FlakeRef resolvedRef;
FlakeRef lockedRef;
};
Flake flake;

View file

@ -95,11 +95,16 @@ RootValue allocRootValue(Value * v)
#endif
}
void Value::print(const SymbolTable & symbols, std::ostream & str,
std::set<const void *> * seen) const
void Value::print(const SymbolTable &symbols, std::ostream &str,
std::set<const void *> *seen, int depth) const
{
checkInterrupt();
if (depth <= 0) {
str << "«too deep»";
return;
}
switch (internalType) {
case tInt:
str << integer;
@ -123,7 +128,7 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
str << "{ ";
for (auto & i : attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
i->value->print(symbols, str, seen);
i->value->print(symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
@ -139,7 +144,7 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
str << "[ ";
for (auto v2 : listItems()) {
if (v2)
v2->print(symbols, str, seen);
v2->print(symbols, str, seen, depth - 1);
else
str << "(nullptr)";
str << " ";
@ -181,11 +186,10 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
}
}
void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const
{
void Value::print(const SymbolTable &symbols, std::ostream &str,
bool showRepeated, int depth) const {
std::set<const void *> seen;
print(symbols, str, showRepeated ? nullptr : &seen);
print(symbols, str, showRepeated ? nullptr : &seen, depth);
}
// Pretty print types for assertion errors
@ -211,20 +215,21 @@ const Value * getPrimOp(const Value &v) {
return primOp;
}
std::string_view showType(ValueType type)
std::string_view showType(ValueType type, bool withArticle)
{
#define WA(a, w) withArticle ? a " " w : w
switch (type) {
case nInt: return "an integer";
case nBool: return "a Boolean";
case nString: return "a string";
case nPath: return "a path";
case nInt: return WA("an", "integer");
case nBool: return WA("a", "Boolean");
case nString: return WA("a", "string");
case nPath: return WA("a", "path");
case nNull: return "null";
case nAttrs: return "a set";
case nList: return "a list";
case nFunction: return "a function";
case nExternal: return "an external value";
case nFloat: return "a float";
case nThunk: return "a thunk";
case nAttrs: return WA("a", "set");
case nList: return WA("a", "list");
case nFunction: return WA("a", "function");
case nExternal: return WA("an", "external value");
case nFloat: return WA("a", "float");
case nThunk: return WA("a", "thunk");
}
abort();
}
@ -702,28 +707,34 @@ Path EvalState::toRealPath(const Path & path, const NixStringContext & context)
}
Value * EvalState::addConstant(const std::string & name, Value & v)
Value * EvalState::addConstant(const std::string & name, Value & v, Constant info)
{
Value * v2 = allocValue();
*v2 = v;
addConstant(name, v2);
addConstant(name, v2, info);
return v2;
}
void EvalState::addConstant(const std::string & name, Value * v)
void EvalState::addConstant(const std::string & name, Value * v, Constant info)
{
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
constantInfos.push_back({name2, info});
Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp)
{
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
if (!(evalSettings.pureEval && info.impureOnly)) {
/* Check the type, if possible.
We might know the type of a thunk in advance, so be allowed
to just write it down in that case. */
if (auto gotType = v->type(true); gotType != nThunk)
assert(info.type == gotType);
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
}
@ -737,7 +748,10 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
vPrimOp->mkPrimOp(new PrimOp(primOp));
Value v;
v.mkApp(vPrimOp, vPrimOp);
return addConstant(primOp.name, v);
return addConstant(primOp.name, v, {
.type = nThunk, // FIXME
.doc = primOp.doc,
});
}
auto envName = symbols.create(primOp.name);
@ -763,13 +777,13 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
{
if (v.isPrimOp()) {
auto v2 = &v;
if (v2->primOp->doc)
if (auto * doc = v2->primOp->doc)
return Doc {
.pos = {},
.name = v2->primOp->name,
.arity = v2->primOp->arity,
.args = v2->primOp->args,
.doc = v2->primOp->doc,
.doc = doc,
};
}
return {};

View file

@ -25,15 +25,72 @@ struct DerivedPath;
enum RepairFlag : bool;
/**
* Function that implements a primop.
*/
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
/**
* Info about a primitive operation, and its implementation
*/
struct PrimOp
{
PrimOpFun fun;
size_t arity;
/**
* Name of the primop. `__` prefix is treated specially.
*/
std::string name;
/**
* Names of the parameters of a primop, for primops that take a
* fixed number of arguments to be substituted for these parameters.
*/
std::vector<std::string> args;
/**
* Aritiy of the primop.
*
* If `args` is not empty, this field will be computed from that
* field instead, so it doesn't need to be manually set.
*/
size_t arity = 0;
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr;
/**
* Implementation of the primop.
*/
PrimOpFun fun;
/**
* Optional experimental for this to be gated on.
*/
std::optional<ExperimentalFeature> experimentalFeature;
};
/**
* Info about a constant
*/
struct Constant
{
/**
* Optional type of the constant (known since it is a fixed value).
*
* @todo we should use an enum for this.
*/
ValueType type = nThunk;
/**
* Optional free-form documentation about the constant.
*/
const char * doc = nullptr;
/**
* Whether the constant is impure, and not available in pure mode.
*/
bool impureOnly = false;
};
#if HAVE_BOEHMGC
@ -65,8 +122,12 @@ std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);
// FIXME: maybe change this to an std::variant<SourcePath, URL>.
typedef std::pair<std::string, std::string> SearchPathElem;
struct SearchPathElem
{
std::string prefix;
// FIXME: maybe change this to an std::variant<SourcePath, URL>.
std::string path;
};
typedef std::list<SearchPathElem> SearchPath;
@ -509,18 +570,23 @@ public:
*/
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
/**
* Name and documentation about every constant.
*
* Constants from primops are hard to crawl, and their docs will go
* here too.
*/
std::vector<std::pair<std::string, Constant>> constantInfos;
private:
unsigned int baseEnvDispl = 0;
void createBaseEnv();
Value * addConstant(const std::string & name, Value & v);
Value * addConstant(const std::string & name, Value & v, Constant info);
void addConstant(const std::string & name, Value * v);
Value * addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp);
void addConstant(const std::string & name, Value * v, Constant info);
Value * addPrimOp(PrimOp && primOp);
@ -534,6 +600,10 @@ public:
std::optional<std::string> name;
size_t arity;
std::vector<std::string> args;
/**
* Unlike the other `doc` fields in this file, this one should never be
* `null`.
*/
const char * doc;
};
@ -700,8 +770,11 @@ struct DebugTraceStacker {
/**
* @return A string representing the type of the value `v`.
*
* @param withArticle Whether to begin with an english article, e.g. "an
* integer" vs "integer".
*/
std::string_view showType(ValueType type);
std::string_view showType(ValueType type, bool withArticle = true);
std::string showType(const Value & v);
/**
@ -733,7 +806,12 @@ struct EvalSettings : Config
Setting<Strings> nixPath{
this, getDefaultNixPath(), "nix-path",
"List of directories to be searched for `<...>` file references."};
R"(
List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtin-constants-nixPath).
)"};
Setting<bool> restrictEval{
this, false, "restrict-eval",

View file

@ -788,9 +788,6 @@ static RegisterPrimOp r2({
```nix
(builtins.getFlake "github:edolstra/dwarffs").rev
```
This function is only available if you enable the experimental feature
`flakes`.
)",
.fun = prim_getFlake,
.experimentalFeature = Xp::Flakes,

View file

@ -36,7 +36,7 @@ static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
#define CUR_POS makeCurPos(*yylloc, data)
// backup to recover from yyless(0)
YYLTYPE prev_yylloc;
thread_local YYLTYPE prev_yylloc;
static void initLoc(YYLTYPE * loc)
{

View file

@ -275,7 +275,12 @@ static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
}
/* If this is a single string, then don't do a concatenation. */
return es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second) ? (*es2)[0].second : new ExprConcatStrings(pos, true, es2);
if (es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second)) {
auto *const result = (*es2)[0].second;
delete es2;
return result;
}
return new ExprConcatStrings(pos, true, es2);
}
@ -330,7 +335,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
%type <ind_string_parts> ind_string_parts
%type <e> path_start string_parts string_attr
%type <id> attr
%token <id> ID ATTRPATH
%token <id> ID
%token <str> STR IND_STR
%token <n> INT
%token <nf> FLOAT
@ -741,7 +746,10 @@ void EvalState::addToSearchPath(const std::string & s)
path = std::string(s, pos + 1);
}
searchPath.emplace_back(prefix, path);
searchPath.emplace_back(SearchPathElem {
.prefix = prefix,
.path = path,
});
}
@ -755,11 +763,11 @@ SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view p
{
for (auto & i : searchPath) {
std::string suffix;
if (i.first.empty())
if (i.prefix.empty())
suffix = concatStrings("/", path);
else {
auto s = i.first.size();
if (path.compare(0, s, i.first) != 0 ||
auto s = i.prefix.size();
if (path.compare(0, s, i.prefix) != 0 ||
(path.size() > s && path[s] != '/'))
continue;
suffix = path.size() == s ? "" : concatStrings("/", path.substr(s));
@ -785,47 +793,47 @@ SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view p
std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathElem & elem)
{
auto i = searchPathResolved.find(elem.second);
auto i = searchPathResolved.find(elem.path);
if (i != searchPathResolved.end()) return i->second;
std::pair<bool, std::string> res;
if (EvalSettings::isPseudoUrl(elem.second)) {
if (EvalSettings::isPseudoUrl(elem.path)) {
try {
auto storePath = fetchers::downloadTarball(
store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).tree.storePath;
store, EvalSettings::resolvePseudoUrl(elem.path), "source", false).tree.storePath;
res = { true, store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.path)
});
res = { false, "" };
}
}
else if (hasPrefix(elem.second, "flake:")) {
else if (hasPrefix(elem.path, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", elem.second);
auto flakeRef = parseFlakeRef(elem.path.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", elem.path);
auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
res = { true, store->toRealPath(storePath) };
}
else {
auto path = absPath(elem.second);
auto path = absPath(elem.path);
if (pathExists(path))
res = { true, path };
else {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' does not exist, ignoring", elem.second)
.msg = hintfmt("Nix search path entry '%1%' does not exist, ignoring", elem.path)
});
res = { false, "" };
}
}
debug("resolved search path element '%s' to '%s'", elem.second, res.second);
debug("resolved search path element '%s' to '%s'", elem.path, res.second);
searchPathResolved[elem.second] = res;
searchPathResolved[elem.path] = res;
return res;
}

View file

@ -238,7 +238,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
}
}
static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info {
static RegisterPrimOp primop_scopedImport(PrimOp {
.name = "scopedImport",
.arity = 2,
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
@ -692,7 +692,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
v.listElems()[n++] = i;
}
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
static RegisterPrimOp primop_genericClosure(PrimOp {
.name = "__genericClosure",
.args = {"attrset"},
.arity = 1,
@ -809,7 +809,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
}
}
static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info {
static RegisterPrimOp primop_addErrorContext(PrimOp {
.name = "__addErrorContext",
.arity = 2,
.fun = prim_addErrorContext,
@ -1400,7 +1400,7 @@ drvName, Bindings * attrs, Value & v)
v.mkAttrs(result);
}
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
static RegisterPrimOp primop_derivationStrict(PrimOp {
.name = "derivationStrict",
.arity = 1,
.fun = prim_derivationStrict,
@ -1656,7 +1656,10 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
}));
}
searchPath.emplace_back(prefix, path);
searchPath.emplace_back(SearchPathElem {
.prefix = prefix,
.path = path,
});
}
auto path = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
@ -1664,9 +1667,52 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
}
static RegisterPrimOp primop_findFile(RegisterPrimOp::Info {
static RegisterPrimOp primop_findFile(PrimOp {
.name = "__findFile",
.arity = 2,
.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,
});
@ -2385,7 +2431,7 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * *
state.mkPos(v, i->pos);
}
static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info {
static RegisterPrimOp primop_unsafeGetAttrPos(PrimOp {
.name = "__unsafeGetAttrPos",
.arity = 2,
.fun = prim_unsafeGetAttrPos,
@ -4058,10 +4104,10 @@ static RegisterPrimOp primop_splitVersion({
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
RegisterPrimOp::RegisterPrimOp(Info && info)
RegisterPrimOp::RegisterPrimOp(PrimOp && primOp)
{
if (!primOps) primOps = new PrimOps;
primOps->push_back(std::move(info));
primOps->push_back(std::move(primOp));
}
@ -4074,54 +4120,202 @@ void EvalState::createBaseEnv()
/* `builtins' must be first! */
v.mkAttrs(buildBindings(128).finish());
addConstant("builtins", v);
addConstant("builtins", v, {
.type = nAttrs,
.doc = R"(
Contains all the [built-in functions](@docroot@/language/builtins.md) and values.
Since built-in functions were added over time, [testing for attributes](./operators.md#has-attribute) in `builtins` can be used for graceful fallback on older Nix installations:
```nix
# if hasContext is not available, we assume `s` has a context
if builtins ? hasContext then builtins.hasContext s else true
```
)",
});
v.mkBool(true);
addConstant("true", v);
addConstant("true", v, {
.type = nBool,
.doc = R"(
Primitive value.
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
The name `true` is not special, and can be shadowed:
```nix-repl
nix-repl> let true = 1; in true
1
```
)",
});
v.mkBool(false);
addConstant("false", v);
addConstant("false", v, {
.type = nBool,
.doc = R"(
Primitive value.
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
The name `false` is not special, and can be shadowed:
```nix-repl
nix-repl> let false = 1; in false
1
```
)",
});
v.mkNull();
addConstant("null", v);
addConstant("null", v, {
.type = nNull,
.doc = R"(
Primitive value.
The name `null` is not special, and can be shadowed:
```nix-repl
nix-repl> let null = 1; in null
1
```
)",
});
if (!evalSettings.pureEval) {
v.mkInt(time(0));
addConstant("__currentTime", v);
v.mkString(settings.thisSystem.get());
addConstant("__currentSystem", v);
}
addConstant("__currentTime", v, {
.type = nInt,
.doc = R"(
Return the [Unix time](https://en.wikipedia.org/wiki/Unix_time) at first evaluation.
Repeated references to that name will re-use the initially obtained value.
Example:
```console
$ nix repl
Welcome to Nix 2.15.1 Type :? for help.
nix-repl> builtins.currentTime
1683705525
nix-repl> builtins.currentTime
1683705525
```
The [store path](@docroot@/glossary.md#gloss-store-path) of a derivation depending on `currentTime` will differ for each evaluation, unless both evaluate `builtins.currentTime` in the same second.
)",
.impureOnly = true,
});
if (!evalSettings.pureEval) {
v.mkString(settings.thisSystem.get());
}
addConstant("__currentSystem", v, {
.type = nString,
.doc = R"(
The value of the [`system` configuration option](@docroot@/command-ref/conf-file.md#conf-pure-eval).
It can be used to set the `system` attribute for [`builtins.derivation`](@docroot@/language/derivations.md) such that the resulting derivation can be built on the same system that evaluates the Nix expression:
```nix
builtins.derivation {
# ...
system = builtins.currentSystem;
}
```
It can be overridden in order to create derivations for different system than the current one:
```console
$ nix-instantiate --system "mips64-linux" --eval --expr 'builtins.currentSystem'
"mips64-linux"
```
)",
.impureOnly = true,
});
v.mkString(nixVersion);
addConstant("__nixVersion", v);
addConstant("__nixVersion", v, {
.type = nString,
.doc = R"(
The version of Nix.
For example, where the command line returns the current Nix version,
```shell-session
$ nix --version
nix (Nix) 2.16.0
```
the Nix language evaluator returns the same value:
```nix-repl
nix-repl> builtins.nixVersion
"2.16.0"
```
)",
});
v.mkString(store->storeDir);
addConstant("__storeDir", v);
addConstant("__storeDir", v, {
.type = nString,
.doc = R"(
Logical file system location of the [Nix store](@docroot@/glossary.md#gloss-store) currently in use.
This value is determined by the `store` parameter in [Store URLs](@docroot@/command-ref/new-cli/nix3-help-stores.md):
```shell-session
$ nix-instantiate --store 'dummy://?store=/blah' --eval --expr builtins.storeDir
"/blah"
```
)",
});
/* Language version. This should be increased every time a new
language feature gets added. It's not necessary to increase it
when primops get added, because you can just use `builtins ?
primOp' to check. */
v.mkInt(6);
addConstant("__langVersion", v);
addConstant("__langVersion", v, {
.type = nInt,
.doc = R"(
The current version of the Nix language.
)",
});
// Miscellaneous
if (evalSettings.enableNativeCode) {
addPrimOp("__importNative", 2, prim_importNative);
addPrimOp("__exec", 1, prim_exec);
addPrimOp({
.name = "__importNative",
.arity = 2,
.fun = prim_importNative,
});
addPrimOp({
.name = "__exec",
.arity = 1,
.fun = prim_exec,
});
}
addPrimOp({
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
.arity = 2,
.name = "__traceVerbose",
.args = { "e1", "e2" },
.arity = 2,
.doc = R"(
Evaluate *e1* and print its abstract syntax representation on standard
error if `--trace-verbose` is enabled. Then return *e2*. This function
is useful for debugging.
)",
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
});
/* Add a value containing the current Nix expression search path. */
@ -4129,30 +4323,50 @@ void EvalState::createBaseEnv()
int n = 0;
for (auto & i : searchPath) {
auto attrs = buildBindings(2);
attrs.alloc("path").mkString(i.second);
attrs.alloc("prefix").mkString(i.first);
attrs.alloc("path").mkString(i.path);
attrs.alloc("prefix").mkString(i.prefix);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs);
}
addConstant("__nixPath", v);
addConstant("__nixPath", v, {
.type = nList,
.doc = R"(
The search path used to resolve angle bracket path lookups.
Angle bracket expressions can be
[desugared](https://en.wikipedia.org/wiki/Syntactic_sugar)
using this and
[`builtins.findFile`](./builtins.html#builtins-findFile):
```nix
<nixpkgs>
```
is equivalent to:
```nix
builtins.findFile builtins.nixPath "nixpkgs"
```
)",
});
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.experimentalFeature
|| experimentalFeatureSettings.isEnabled(*primOp.experimentalFeature))
if (experimentalFeatureSettings.isEnabled(primOp.experimentalFeature))
{
addPrimOp({
.fun = primOp.fun,
.arity = std::max(primOp.args.size(), primOp.arity),
.name = primOp.name,
.args = primOp.args,
.doc = primOp.doc,
});
auto primOpAdjusted = primOp;
primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
addPrimOp(std::move(primOpAdjusted));
}
/* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */
`drvPath' and `outPath' attributes lazily.
Null docs because it is documented separately.
*/
auto vDerivation = allocValue();
addConstant("derivation", vDerivation);
addConstant("derivation", vDerivation, {
.type = nFunction,
});
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */

View file

@ -10,17 +10,7 @@ namespace nix {
struct RegisterPrimOp
{
struct Info
{
std::string name;
std::vector<std::string> args;
size_t arity = 0;
const char * doc;
PrimOpFun fun;
std::optional<ExperimentalFeature> experimentalFeature;
};
typedef std::vector<Info> PrimOps;
typedef std::vector<PrimOp> PrimOps;
static PrimOps * primOps;
/**
@ -28,7 +18,7 @@ struct RegisterPrimOp
* will get called during EvalState initialization, so there
* may be primops not yet added and builtins is not yet sorted.
*/
RegisterPrimOp(Info && info);
RegisterPrimOp(PrimOp && primOp);
};
/* These primops are disabled without enableNativeCode, but plugins

View file

@ -154,9 +154,6 @@ static RegisterPrimOp primop_fetchClosure({
specifying a binary cache from which the path can be fetched.
Also, requiring a content-addressed final store path avoids the
need for users to configure binary cache public keys.
This function is only available if you enable the experimental
feature `fetch-closure`.
)",
.fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure,

View file

@ -22,7 +22,7 @@ void emitTreeAttrs(
{
assert(input.isLocked());
auto attrs = state.buildBindings(8);
auto attrs = state.buildBindings(10);
state.mkStorePathString(tree.storePath, attrs.alloc(state.sOutPath));
@ -56,6 +56,11 @@ void emitTreeAttrs(
}
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
attrs.alloc("dirtyRev").mkString(*dirtyRev);
attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev"));
}
if (auto lastModified = input.getLastModified()) {
attrs.alloc("lastModified").mkInt(*lastModified);
attrs.alloc("lastModifiedDate").mkString(

View file

@ -0,0 +1,236 @@
#include "tests/libexpr.hh"
#include "value.hh"
namespace nix {
using namespace testing;
struct ValuePrintingTests : LibExprTest
{
template<class... A>
void test(Value v, std::string_view expected, A... args)
{
std::stringstream out;
v.print(state.symbols, out, args...);
ASSERT_EQ(out.str(), expected);
}
};
TEST_F(ValuePrintingTests, tInt)
{
Value vInt;
vInt.mkInt(10);
test(vInt, "10");
}
TEST_F(ValuePrintingTests, tBool)
{
Value vBool;
vBool.mkBool(true);
test(vBool, "true");
}
TEST_F(ValuePrintingTests, tString)
{
Value vString;
vString.mkString("some-string");
test(vString, "\"some-string\"");
}
TEST_F(ValuePrintingTests, tPath)
{
Value vPath;
vPath.mkString("/foo");
test(vPath, "\"/foo\"");
}
TEST_F(ValuePrintingTests, tNull)
{
Value vNull;
vNull.mkNull();
test(vNull, "null");
}
TEST_F(ValuePrintingTests, tAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs, "{ one = 1; two = 2; }");
}
TEST_F(ValuePrintingTests, tList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
Value vList;
state.mkList(vList, 5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
test(vList, "[ 1 2 (nullptr) ]");
}
TEST_F(ValuePrintingTests, vThunk)
{
Value vThunk;
vThunk.mkThunk(nullptr, nullptr);
test(vThunk, "<CODE>");
}
TEST_F(ValuePrintingTests, vApp)
{
Value vApp;
vApp.mkApp(nullptr, nullptr);
test(vApp, "<CODE>");
}
TEST_F(ValuePrintingTests, vLambda)
{
Value vLambda;
vLambda.mkLambda(nullptr, nullptr);
test(vLambda, "<LAMBDA>");
}
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
vPrimOp.mkPrimOp(nullptr);
test(vPrimOp, "<PRIMOP>");
}
TEST_F(ValuePrintingTests, vPrimOpApp)
{
Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(nullptr, nullptr);
test(vPrimOpApp, "<PRIMOP-APP>");
}
TEST_F(ValuePrintingTests, vExternal)
{
struct MyExternal : ExternalValueBase
{
public:
std::string showType() const override
{
return "";
}
std::string typeOf() const override
{
return "";
}
virtual std::ostream & print(std::ostream & str) const override
{
str << "testing-external!";
return str;
}
} myExternal;
Value vExternal;
vExternal.mkExternal(&myExternal);
test(vExternal, "testing-external!");
}
TEST_F(ValuePrintingTests, vFloat)
{
Value vFloat;
vFloat.mkFloat(2.0);
test(vFloat, "2");
}
TEST_F(ValuePrintingTests, vBlackhole)
{
Value vBlackhole;
vBlackhole.mkBlackhole();
test(vBlackhole, "«potential infinite recursion»");
}
TEST_F(ValuePrintingTests, depthAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2(state, state.allocBindings(10));
builder2.insert(state.symbols.create("one"), &vOne);
builder2.insert(state.symbols.create("two"), &vTwo);
builder2.insert(state.symbols.create("nested"), &vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
test(vNested, "{ nested = «too deep»; one = «too deep»; two = «too deep»; }", false, 1);
test(vNested, "{ nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; }", false, 2);
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 3);
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 4);
}
TEST_F(ValuePrintingTests, depthList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2(state, state.allocBindings(10));
builder2.insert(state.symbols.create("one"), &vOne);
builder2.insert(state.symbols.create("two"), &vTwo);
builder2.insert(state.symbols.create("nested"), &vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
Value vList;
state.mkList(vList, 5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3;
test(vList, "[ «too deep» «too deep» «too deep» ]", false, 1);
test(vList, "[ 1 2 { nested = «too deep»; one = «too deep»; two = «too deep»; } ]", false, 2);
test(vList, "[ 1 2 { nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; } ]", false, 3);
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 4);
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 5);
}
} // namespace nix

View file

@ -2,6 +2,7 @@
///@file
#include <cassert>
#include <climits>
#include "symbol-table.hh"
#include "value/context.hh"
@ -137,11 +138,11 @@ private:
friend std::string showType(const Value & v);
void print(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen) const;
void print(const SymbolTable &symbols, std::ostream &str, std::set<const void *> *seen, int depth) const;
public:
void print(const SymbolTable & symbols, std::ostream & str, bool showRepeated = false) const;
void print(const SymbolTable &symbols, std::ostream &str, bool showRepeated = false, int depth = INT_MAX) const;
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
@ -218,8 +219,11 @@ public:
/**
* Returns the normal type of a Value. This only returns nThunk if
* the Value hasn't been forceValue'd
*
* @param invalidIsThunk Instead of aborting an an invalid (probably
* 0, so uninitialized) internal type, return `nThunk`.
*/
inline ValueType type() const
inline ValueType type(bool invalidIsThunk = false) const
{
switch (internalType) {
case tInt: return nInt;
@ -234,7 +238,10 @@ public:
case tFloat: return nFloat;
case tThunk: case tApp: case tBlackhole: return nThunk;
}
abort();
if (invalidIsThunk)
return nThunk;
else
abort();
}
/**

View file

@ -243,6 +243,13 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
"lastModified",
workdirInfo.hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
if (workdirInfo.hasHead) {
input.attrs.insert_or_assign("dirtyRev", chomp(
runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "rev-parse", "--verify", "HEAD" })) + "-dirty");
input.attrs.insert_or_assign("dirtyShortRev", chomp(
runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "rev-parse", "--verify", "--short", "HEAD" })) + "-dirty");
}
return {std::move(storePath), input};
}
} // end namespace
@ -283,7 +290,7 @@ struct GitInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "git") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name")
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "dirtyRev" && name != "dirtyShortRev")
throw Error("unsupported Git input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url"));

View file

@ -31,11 +31,11 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
}
if (failed.size() == 1 && ex) {
ex->status = worker.exitStatus();
ex->status = worker.failingExitStatus();
throw std::move(*ex);
} else if (!failed.empty()) {
if (ex) logError(ex->info());
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
throw Error(worker.failingExitStatus(), "build of %s failed", showPaths(failed));
}
}
@ -102,10 +102,10 @@ void Store::ensurePath(const StorePath & path)
if (goal->exitCode != Goal::ecSuccess) {
if (goal->ex) {
goal->ex->status = worker.exitStatus();
goal->ex->status = worker.failingExitStatus();
throw std::move(*goal->ex);
} else
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
}
}
@ -128,7 +128,7 @@ void Store::repairPath(const StorePath & path)
goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
worker.run(goals);
} else
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));
}
}

View file

@ -5,14 +5,14 @@ namespace nix {
HookInstance::HookInstance()
{
debug("starting build hook '%s'", settings.buildHook);
debug("starting build hook '%s'", concatStringsSep(" ", settings.buildHook.get()));
auto buildHookArgs = tokenizeString<std::list<std::string>>(settings.buildHook.get());
auto buildHookArgs = settings.buildHook.get();
if (buildHookArgs.empty())
throw Error("'build-hook' setting is empty");
auto buildHook = buildHookArgs.front();
auto buildHook = canonPath(buildHookArgs.front());
buildHookArgs.pop_front();
Strings args;

View file

@ -64,8 +64,9 @@ void handleDiffHook(
const Path & tryA, const Path & tryB,
const Path & drvPath, const Path & tmpDir)
{
auto diffHook = settings.diffHook;
if (diffHook != "" && settings.runDiffHook) {
auto & diffHookOpt = settings.diffHook.get();
if (diffHookOpt && settings.runDiffHook) {
auto & diffHook = *diffHookOpt;
try {
auto diffRes = runProgram(RunOptions {
.program = diffHook,
@ -394,8 +395,9 @@ static void linkOrCopy(const Path & from, const Path & to)
bind-mount in this case?
It can also fail with EPERM in BeegFS v7 and earlier versions
or fail with EXDEV in OpenAFS
which don't allow hard-links to other directories */
if (errno != EMLINK && errno != EPERM)
if (errno != EMLINK && errno != EPERM && errno != EXDEV)
throw SysError("linking '%s' to '%s'", to, from);
copyPath(from, to);
}
@ -1422,7 +1424,8 @@ void LocalDerivationGoal::startDaemon()
Store::Params params;
params["path-info-cache-size"] = "0";
params["store"] = worker.store.storeDir;
params["root"] = getLocalStore().rootDir;
if (auto & optRoot = getLocalStore().rootDir.get())
params["root"] = *optRoot;
params["state"] = "/no-such-path";
params["log"] = "/no-such-path";
auto store = make_ref<RestrictedStore>(params,

View file

@ -468,16 +468,9 @@ void Worker::waitForInput()
}
unsigned int Worker::exitStatus()
unsigned int Worker::failingExitStatus()
{
/*
* 1100100
* ^^^^
* |||`- timeout
* ||`-- output hash mismatch
* |`--- build failure
* `---- not deterministic
*/
// See API docs in header for explanation
unsigned int mask = 0;
bool buildFailure = permanentFailure || timedOut || hashMismatch;
if (buildFailure)

View file

@ -280,7 +280,28 @@ public:
*/
void waitForInput();
unsigned int exitStatus();
/***
* The exit status in case of failure.
*
* In the case of a build failure, returned value follows this
* bitmask:
*
* ```
* 0b1100100
* ^^^^
* |||`- timeout
* ||`-- output hash mismatch
* |`--- build failure
* `---- not deterministic
* ```
*
* In other words, the failure code is at least 100 (0b1100100), but
* might also be greater.
*
* Otherwise (no build failure, but some other sort of failure by
* assumption), this returned value is 1.
*/
unsigned int failingExitStatus();
/**
* Check whether the given valid path exists and has the right

View file

@ -864,8 +864,6 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto path = store->parseStorePath(readString(from));
StringSet sigs = readStrings<StringSet>(from);
logger->startWork();
if (!trusted)
throw Error("you are not privileged to add signatures");
store->addSignatures(path, sigs);
logger->stopWork();
to << 1;

View file

@ -100,7 +100,10 @@ Settings::Settings()
if (!pathExists(nixExePath)) {
nixExePath = getSelfExe().value_or("nix");
}
buildHook = nixExePath + " __build-remote";
buildHook = {
nixExePath,
"__build-remote",
};
}
void loadConfFile()

View file

@ -236,7 +236,7 @@ public:
)",
{"build-timeout"}};
PathSetting buildHook{this, true, "", "build-hook",
Setting<Strings> buildHook{this, {}, "build-hook",
R"(
The path to the helper program that executes remote builds.
@ -575,8 +575,8 @@ public:
line.
)"};
PathSetting diffHook{
this, true, "", "diff-hook",
OptionalPathSetting diffHook{
this, std::nullopt, "diff-hook",
R"(
Absolute path to an executable capable of diffing build
results. The hook is executed if `run-diff-hook` is true, and the
@ -719,8 +719,8 @@ public:
At least one of the following conditions must be met for Nix to use a substituter:
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
- the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
- The substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
- The user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
In addition, each store path should be trusted as described in [`trusted-public-keys`](#conf-trusted-public-keys)
)",
@ -729,12 +729,10 @@ public:
Setting<StringSet> trustedSubstituters{
this, {}, "trusted-substituters",
R"(
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format),
separated by whitespace. These are
not used by default, but can be enabled by users of the Nix daemon
by specifying `--option substituters urls` on the command
line. Unprivileged users are only allowed to pass a subset of the
URLs listed in `substituters` and `trusted-substituters`.
A list of [Nix store URLs](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format), separated by whitespace.
These are not used by default, but users of the Nix daemon can enable them by specifying [`substituters`](#conf-substituters).
Unprivileged users (those set in only [`allowed-users`](#conf-allowed-users) but not [`trusted-users`](#conf-trusted-users)) can pass as `substituters` only those URLs listed in `trusted-substituters`.
)",
{"trusted-binary-caches"}};

View file

@ -15,22 +15,22 @@ struct LocalFSStoreConfig : virtual StoreConfig
// it to omit the call to the Setting constructor. Clang works fine
// either way.
const PathSetting rootDir{(StoreConfig*) this, true, "",
const OptionalPathSetting rootDir{(StoreConfig*) this, std::nullopt,
"root",
"Directory prefixed to all other paths."};
const PathSetting stateDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
const PathSetting stateDir{(StoreConfig*) this,
rootDir.get() ? *rootDir.get() + "/nix/var/nix" : settings.nixStateDir,
"state",
"Directory where Nix will store state."};
const PathSetting logDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
const PathSetting logDir{(StoreConfig*) this,
rootDir.get() ? *rootDir.get() + "/nix/var/log/nix" : settings.nixLogDir,
"log",
"directory where Nix will store log files."};
const PathSetting realStoreDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
const PathSetting realStoreDir{(StoreConfig*) this,
rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real",
"Physical path of the Nix store."};
};

View file

@ -114,7 +114,7 @@ struct StoreConfig : public Config
return "";
}
const PathSetting storeDir_{this, false, settings.nixStore,
const PathSetting storeDir_{this, settings.nixStore,
"store",
R"(
Logical location of the Nix store, usually

View file

@ -3,6 +3,7 @@
#include <nlohmann/json.hpp>
#include "config.hh"
#include "json-utils.hh"
namespace nix {
template<typename T>

View file

@ -1,10 +1,9 @@
#include "args.hh"
#include "hash.hh"
#include "json-utils.hh"
#include <glob.h>
#include <nlohmann/json.hpp>
namespace nix {
void Args::addFlag(Flag && flag_)
@ -247,11 +246,7 @@ nlohmann::json Args::toJSON()
j["arity"] = flag->handler.arity;
if (!flag->labels.empty())
j["labels"] = flag->labels;
// TODO With C++23 use `std::optional::tranform`
if (auto & xp = flag->experimentalFeature)
j["experimental-feature"] = showExperimentalFeature(*xp);
else
j["experimental-feature"] = nullptr;
j["experimental-feature"] = flag->experimentalFeature;
flags[name] = std::move(j);
}
@ -416,11 +411,7 @@ nlohmann::json MultiCommand::toJSON()
cat["id"] = command->category();
cat["description"] = trim(categories[command->category()]);
j["category"] = std::move(cat);
// TODO With C++23 use `std::optional::tranform`
if (auto xp = command->experimentalFeature())
cat["experimental-feature"] = showExperimentalFeature(*xp);
else
cat["experimental-feature"] = nullptr;
cat["experimental-feature"] = command->experimentalFeature();
cmds[name] = std::move(j);
}

View file

@ -53,8 +53,11 @@ template<> void BaseSetting<std::set<ExperimentalFeature>>::appendOrSet(std::set
template<typename T>
void BaseSetting<T>::appendOrSet(T && newValue, bool append)
{
static_assert(!trait::appendable, "using default `appendOrSet` implementation with an appendable type");
static_assert(
!trait::appendable,
"using default `appendOrSet` implementation with an appendable type");
assert(!append);
value = std::move(newValue);
}
@ -71,4 +74,60 @@ void BaseSetting<T>::set(const std::string & str, bool append)
}
}
template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & category);
template<typename T>
void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
{
args.addFlag({
.longName = name,
.description = fmt("Set the `%s` setting.", name),
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s); }},
.experimentalFeature = experimentalFeature,
});
if (isAppendable())
args.addFlag({
.longName = "extra-" + name,
.description = fmt("Append to the `%s` setting.", name),
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
.experimentalFeature = experimentalFeature,
});
}
#define DECLARE_CONFIG_SERIALISER(TY) \
template<> TY BaseSetting< TY >::parse(const std::string & str) const; \
template<> std::string BaseSetting< TY >::to_string() const;
DECLARE_CONFIG_SERIALISER(std::string)
DECLARE_CONFIG_SERIALISER(std::optional<std::string>)
DECLARE_CONFIG_SERIALISER(bool)
DECLARE_CONFIG_SERIALISER(Strings)
DECLARE_CONFIG_SERIALISER(StringSet)
DECLARE_CONFIG_SERIALISER(StringMap)
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
template<typename T>
T BaseSetting<T>::parse(const std::string & str) const
{
static_assert(std::is_integral<T>::value, "Integer required.");
if (auto n = string2Int<T>(str))
return *n;
else
throw UsageError("setting '%s' has invalid value '%s'", name, str);
}
template<typename T>
std::string BaseSetting<T>::to_string() const
{
static_assert(std::is_integral<T>::value, "Integer required.");
return std::to_string(value);
}
}

View file

@ -219,29 +219,6 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category)
{
}
template<typename T>
void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
{
args.addFlag({
.longName = name,
.description = fmt("Set the `%s` setting.", name),
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s); }},
.experimentalFeature = experimentalFeature,
});
if (isAppendable())
args.addFlag({
.longName = "extra-" + name,
.description = fmt("Append to the `%s` setting.", name),
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
.experimentalFeature = experimentalFeature,
});
}
template<> std::string BaseSetting<std::string>::parse(const std::string & str) const
{
return str;
@ -252,21 +229,17 @@ template<> std::string BaseSetting<std::string>::to_string() const
return value;
}
template<typename T>
T BaseSetting<T>::parse(const std::string & str) const
template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str) const
{
static_assert(std::is_integral<T>::value, "Integer required.");
if (auto n = string2Int<T>(str))
return *n;
if (str == "")
return std::nullopt;
else
throw UsageError("setting '%s' has invalid value '%s'", name, str);
return { str };
}
template<typename T>
std::string BaseSetting<T>::to_string() const
template<> std::string BaseSetting<std::optional<std::string>>::to_string() const
{
static_assert(std::is_integral<T>::value, "Integer required.");
return std::to_string(value);
return value ? *value : "";
}
template<> bool BaseSetting<bool>::parse(const std::string & str) const
@ -403,15 +376,25 @@ template class BaseSetting<StringSet>;
template class BaseSetting<StringMap>;
template class BaseSetting<std::set<ExperimentalFeature>>;
static Path parsePath(const AbstractSetting & s, const std::string & str)
{
if (str == "")
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
else
return canonPath(str);
}
Path PathSetting::parse(const std::string & str) const
{
if (str == "") {
if (allowEmpty)
return "";
else
throw UsageError("setting '%s' cannot be empty", name);
} else
return canonPath(str);
return parsePath(*this, str);
}
std::optional<Path> OptionalPathSetting::parse(const std::string & str) const
{
if (str == "")
return std::nullopt;
else
return parsePath(*this, str);
}
bool GlobalConfig::set(const std::string & name, const std::string & value)

View file

@ -353,21 +353,20 @@ public:
/**
* A special setting for Paths. These are automatically canonicalised
* (e.g. "/foo//bar/" becomes "/foo/bar").
*
* It is mandatory to specify a path; i.e. the empty string is not
* permitted.
*/
class PathSetting : public BaseSetting<Path>
{
bool allowEmpty;
public:
PathSetting(Config * options,
bool allowEmpty,
const Path & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {})
: BaseSetting<Path>(def, true, name, description, aliases)
, allowEmpty(allowEmpty)
{
options->addSetting(this);
}
@ -379,6 +378,30 @@ public:
void operator =(const Path & v) { this->assign(v); }
};
/**
* Like `PathSetting`, but the absence of a path is also allowed.
*
* `std::optional` is used instead of the empty string for clarity.
*/
class OptionalPathSetting : public BaseSetting<std::optional<Path>>
{
public:
OptionalPathSetting(Config * options,
const std::optional<Path> & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {})
: BaseSetting<std::optional<Path>>(def, true, name, description, aliases)
{
options->addSetting(this);
}
std::optional<Path> parse(const std::string & str) const override;
void operator =(const std::optional<Path> & v) { this->assign(v); }
};
struct GlobalConfig : public AbstractConfig
{
typedef std::vector<Config*> ConfigRegistrations;

View file

@ -3,7 +3,7 @@
#include "comparator.hh"
#include "error.hh"
#include "nlohmann/json_fwd.hpp"
#include "json-utils.hh"
#include "types.hh"
namespace nix {
@ -94,4 +94,10 @@ public:
void to_json(nlohmann::json &, const ExperimentalFeature &);
void from_json(const nlohmann::json &, ExperimentalFeature &);
/**
* It is always rendered as a string
*/
template<>
struct json_avoids_null<ExperimentalFeature> : std::true_type {};
}

19
src/libutil/json-utils.cc Normal file
View file

@ -0,0 +1,19 @@
#include "json-utils.hh"
namespace nix {
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
nlohmann::json * get(nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
}

View file

@ -2,21 +2,77 @@
///@file
#include <nlohmann/json.hpp>
#include <list>
namespace nix {
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
const nlohmann::json * get(const nlohmann::json & map, const std::string & key);
nlohmann::json * get(nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
nlohmann::json * get(nlohmann::json & map, const std::string & key);
/**
* For `adl_serializer<std::optional<T>>` below, we need to track what
* types are not already using `null`. Only for them can we use `null`
* to represent `std::nullopt`.
*/
template<typename T>
struct json_avoids_null;
/**
* Handle numbers in default impl
*/
template<typename T>
struct json_avoids_null : std::bool_constant<std::is_integral<T>::value> {};
template<>
struct json_avoids_null<std::nullptr_t> : std::false_type {};
template<>
struct json_avoids_null<bool> : std::true_type {};
template<>
struct json_avoids_null<std::string> : std::true_type {};
template<typename T>
struct json_avoids_null<std::vector<T>> : std::true_type {};
template<typename T>
struct json_avoids_null<std::list<T>> : std::true_type {};
template<typename K, typename V>
struct json_avoids_null<std::map<K, V>> : std::true_type {};
}
namespace nlohmann {
/**
* This "instance" is widely requested, see
* https://github.com/nlohmann/json/issues/1749, but momentum has stalled
* out. Writing there here in Nix as a stop-gap.
*
* We need to make sure the underlying type does not use `null` for this to
* round trip. We do that with a static assert.
*/
template<typename T>
struct adl_serializer<std::optional<T>> {
static std::optional<T> from_json(const json & json) {
static_assert(
nix::json_avoids_null<T>::value,
"null is already in use for underlying type's JSON");
return json.is_null()
? std::nullopt
: std::optional { adl_serializer<T>::from_json(json) };
}
static void to_json(json & json, std::optional<T> t) {
static_assert(
nix::json_avoids_null<T>::value,
"null is already in use for underlying type's JSON");
if (t)
adl_serializer<T>::to_json(json, *t);
else
json = nullptr;
}
};
}

View file

@ -56,19 +56,16 @@ struct AuthorizationSettings : Config {
Setting<Strings> trustedUsers{
this, {"root"}, "trusted-users",
R"(
A list of names of users (separated by whitespace) that have
additional rights when connecting to the Nix daemon, such as the
ability to specify additional binary caches, or to import unsigned
NARs. You can also specify groups by prefixing them with `@`; for
instance, `@wheel` means all users in the `wheel` group. The default
is `root`.
A list of user names, separated by whitespace.
These users will have additional rights when connecting to the Nix daemon, such as the ability to specify additional [substituters](#conf-substituters), or to import unsigned [NARs](@docroot@/glossary.md#gloss-nar).
You can also specify groups by prefixing names with `@`.
For instance, `@wheel` means all users in the `wheel` group.
> **Warning**
>
> Adding a user to `trusted-users` is essentially equivalent to
> giving that user root access to the system. For example, the user
> can set `sandbox-paths` and thereby obtain read access to
> directories that are otherwise inacessible to them.
> Adding a user to `trusted-users` is essentially equivalent to giving that user root access to the system.
> For example, the user can access or replace store path contents that are critical for system security.
)"};
/**
@ -77,12 +74,16 @@ struct AuthorizationSettings : Config {
Setting<Strings> allowedUsers{
this, {"*"}, "allowed-users",
R"(
A list of names of users (separated by whitespace) that are allowed
to connect to the Nix daemon. As with the `trusted-users` option,
you can specify groups by prefixing them with `@`. Also, you can
allow all users by specifying `*`. The default is `*`.
A list user names, separated by whitespace.
These users are allowed to connect to the Nix daemon.
Note that trusted users are always allowed to connect.
You can specify groups by prefixing names with `@`.
For instance, `@wheel` means all users in the `wheel` group.
Also, you can allow all users by specifying `*`.
> **Note**
>
> Trusted users (set in [`trusted-users`](#conf-trusted-users)) can always connect to the Nix daemon.
)"};
};

View file

@ -179,6 +179,8 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
j["locked"] = fetchers::attrsToJSON(flake.lockedRef.toAttrs());
if (auto rev = flake.lockedRef.input.getRev())
j["revision"] = rev->to_string(Base16, false);
if (auto dirtyRev = fetchers::maybeGetStrAttr(flake.lockedRef.toAttrs(), "dirtyRev"))
j["dirtyRevision"] = *dirtyRev;
if (auto revCount = flake.lockedRef.input.getRevCount())
j["revCount"] = *revCount;
if (auto lastModified = flake.lockedRef.input.getLastModified())
@ -204,6 +206,10 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
logger->cout(
ANSI_BOLD "Revision:" ANSI_NORMAL " %s",
rev->to_string(Base16, false));
if (auto dirtyRev = fetchers::maybeGetStrAttr(flake.lockedRef.toAttrs(), "dirtyRev"))
logger->cout(
ANSI_BOLD "Revision:" ANSI_NORMAL " %s",
*dirtyRev);
if (auto revCount = flake.lockedRef.input.getRevCount())
logger->cout(
ANSI_BOLD "Revisions:" ANSI_NORMAL " %s",
@ -380,8 +386,10 @@ struct CmdFlakeCheck : FlakeCommand
auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceValue(v, pos);
if (!v.isLambda()
|| v.lambda.fun->hasFormals()
if (!v.isLambda()) {
throw Error("overlay is not a function, but %s instead", showType(v));
}
if (v.lambda.fun->hasFormals()
|| !argHasName(v.lambda.fun->arg, "final"))
throw Error("overlay does not take an argument named 'final'");
auto body = dynamic_cast<ExprLambda *>(v.lambda.fun->body);

View file

@ -71,8 +71,6 @@ inputs.nixpkgs = {
Here are some examples of flake references in their URL-like representation:
* `.`: The flake in the current directory.
* `/home/alice/src/patchelf`: A flake in some other directory.
* `nixpkgs`: The `nixpkgs` entry in the flake registry.
* `nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293`: The `nixpkgs`
entry in the flake registry, with its Git revision overridden to a
@ -93,6 +91,23 @@ Here are some examples of flake references in their URL-like representation:
* `https://github.com/NixOS/patchelf/archive/master.tar.gz`: A tarball
flake.
## Path-like syntax
Flakes corresponding to a local path can also be referred to by a direct path reference, either `/absolute/path/to/the/flake` or `./relative/path/to/the/flake` (note that the leading `./` is mandatory for relative paths to avoid any ambiguity).
The semantic of such a path is as follows:
* If the directory is part of a Git repository, then the input will be treated as a `git+file:` URL, otherwise it will be treated as a `path:` url;
* If the directory doesn't contain a `flake.nix` file, then Nix will search for such a file upwards in the file system hierarchy until it finds any of:
1. The Git repository root, or
2. The filesystem root (/), or
3. A folder on a different mount point.
### Examples
* `.`: The flake to which the current directory belongs to.
* `/home/alice/src/patchelf`: A flake in some other directory.
## Flake reference attributes
The following generic flake reference attributes are supported:

View file

@ -352,7 +352,7 @@ void mainWrapped(int argc, char * * argv)
return;
}
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
if (argc == 2 && std::string(argv[1]) == "__dump-language") {
experimentalFeatureSettings.experimentalFeatures = {
Xp::Flakes,
Xp::FetchClosure,
@ -360,17 +360,34 @@ void mainWrapped(int argc, char * * argv)
evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://"));
auto res = nlohmann::json::object();
auto builtins = state.baseEnv.values[0]->attrs;
for (auto & builtin : *builtins) {
auto b = nlohmann::json::object();
if (!builtin.value->isPrimOp()) continue;
auto primOp = builtin.value->primOp;
if (!primOp->doc) continue;
b["arity"] = primOp->arity;
b["args"] = primOp->args;
b["doc"] = trim(stripIndentation(primOp->doc));
res[state.symbols[builtin.name]] = std::move(b);
}
res["builtins"] = ({
auto builtinsJson = nlohmann::json::object();
auto builtins = state.baseEnv.values[0]->attrs;
for (auto & builtin : *builtins) {
auto b = nlohmann::json::object();
if (!builtin.value->isPrimOp()) continue;
auto primOp = builtin.value->primOp;
if (!primOp->doc) continue;
b["arity"] = primOp->arity;
b["args"] = primOp->args;
b["doc"] = trim(stripIndentation(primOp->doc));
b["experimental-feature"] = primOp->experimentalFeature;
builtinsJson[state.symbols[builtin.name]] = std::move(b);
}
std::move(builtinsJson);
});
res["constants"] = ({
auto constantsJson = nlohmann::json::object();
for (auto & [name, info] : state.constantInfos) {
auto c = nlohmann::json::object();
if (!info.doc) continue;
c["doc"] = trim(stripIndentation(info.doc));
c["type"] = showType(info.type, false);
c["impure-only"] = info.impureOnly;
constantsJson[name] = std::move(c);
}
std::move(constantsJson);
});
logger->cout("%s", res);
return;
}

View file

@ -6,26 +6,48 @@ R""(
```console
# nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.spotify /nix/store/akpdsid105phbbvknjsdh7hl4v3fhjkr-spotify-1.1.46.916.g416cacf1
1 flake:nixpkgs#legacyPackages.x86_64-linux.zoom-us github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.zoom-us /nix/store/89pmjmbih5qpi7accgacd17ybpgp4xfm-zoom-us-5.4.53350.1027
2 flake:blender-bin#packages.x86_64-linux.default github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#packages.x86_64-linux.default /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0
Index: 0
Flake attribute: legacyPackages.x86_64-linux.gdb
Original flake URL: flake:nixpkgs
Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1
Index: 1
Flake attribute: packages.x86_64-linux.default
Original flake URL: flake:blender-bin
Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender
Store paths: /nix/store/i798sxl3j40wpdi1rgf391id1b5klw7g-blender-bin-3.1.2
```
Note that you can unambiguously rebuild a package from a profile
through its locked flake URL and flake attribute, e.g.
```console
# nix build github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender#packages.x86_64-linux.default
```
will build the package with index 1 shown above.
# Description
This command shows what packages are currently installed in a
profile. The output consists of one line per package, with the
following fields:
profile. For each installed package, it shows the following
information:
* An integer that can be used to unambiguously identify the package in
invocations of `nix profile remove` and `nix profile upgrade`.
* `Index`: An integer that can be used to unambiguously identify the
package in invocations of `nix profile remove` and `nix profile
upgrade`.
* The original ("unlocked") flake reference and output attribute path
used at installation time.
* `Flake attribute`: The flake output attribute path that provides the
package (e.g. `packages.x86_64-linux.hello`).
* The locked flake reference to which the unlocked flake reference was
resolved.
* `Original flake URL`: The original ("unlocked") flake reference
specified by the user when the package was first installed via `nix
profile install`.
* The store path(s) of the package.
* `Locked flake URL`: The locked flake reference to which the original
flake reference was resolved.
* `Store paths`: The store path(s) of the package.
)""

View file

@ -21,7 +21,7 @@ struct ProfileElementSource
{
FlakeRef originalRef;
// FIXME: record original attrpath.
FlakeRef resolvedRef;
FlakeRef lockedRef;
std::string attrPath;
ExtendedOutputsSpec outputs;
@ -168,7 +168,7 @@ struct ProfileManifest
}
}
std::string toJSON(Store & store) const
nlohmann::json toJSON(Store & store) const
{
auto array = nlohmann::json::array();
for (auto & element : elements) {
@ -181,7 +181,7 @@ struct ProfileManifest
obj["priority"] = element.priority;
if (element.source) {
obj["originalUrl"] = element.source->originalRef.to_string();
obj["url"] = element.source->resolvedRef.to_string();
obj["url"] = element.source->lockedRef.to_string();
obj["attrPath"] = element.source->attrPath;
obj["outputs"] = element.source->outputs;
}
@ -190,7 +190,7 @@ struct ProfileManifest
nlohmann::json json;
json["version"] = 2;
json["elements"] = array;
return json.dump();
return json;
}
StorePath build(ref<Store> store)
@ -210,7 +210,7 @@ struct ProfileManifest
buildProfile(tempDir, std::move(pkgs));
writeFile(tempDir + "/manifest.json", toJSON(*store));
writeFile(tempDir + "/manifest.json", toJSON(*store).dump());
/* Add the symlink tree to the store. */
StringSink sink;
@ -349,7 +349,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
if (auto * info2 = dynamic_cast<ExtraPathInfoFlake *>(&*info)) {
element.source = ProfileElementSource {
.originalRef = info2->flake.originalRef,
.resolvedRef = info2->flake.resolvedRef,
.lockedRef = info2->flake.lockedRef,
.attrPath = info2->value.attrPath,
.outputs = info2->value.extendedOutputsSpec,
};
@ -588,14 +588,14 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
assert(infop);
auto & info = *infop;
if (element.source->resolvedRef == info.flake.resolvedRef) continue;
if (element.source->lockedRef == info.flake.lockedRef) continue;
printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->resolvedRef, info.flake.resolvedRef);
element.source->attrPath, element.source->lockedRef, info.flake.lockedRef);
element.source = ProfileElementSource {
.originalRef = installable->flakeRef,
.resolvedRef = info.flake.resolvedRef,
.lockedRef = info.flake.lockedRef,
.attrPath = info.value.attrPath,
.outputs = installable->extendedOutputsSpec,
};
@ -635,7 +635,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
}
};
struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile, MixJSON
{
std::string description() override
{
@ -653,12 +653,22 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
{
ProfileManifest manifest(*getEvalState(), *profile);
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
logger->cout("%d %s %s %s", i,
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
if (json) {
std::cout << manifest.toJSON(*store).dump() << "\n";
} else {
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
if (i) logger->cout("");
logger->cout("Index: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
i,
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
if (element.source) {
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
logger->cout("Original flake URL: %s", element.source->originalRef.to_string());
logger->cout("Locked flake URL: %s", element.source->lockedRef.to_string());
}
logger->cout("Store paths: %s", concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
}
}
}
};