Closes #496 When running nix-build ../nixpkgs --arg config.allowUnfree true -A hello-unfree the package `hello-unfree` is now built rather than getting an eval-error rejecting instantiation. This is because `config.allowUnfree` is now interpreted as nested attribute-set declaration, similar to how it's done in `nix repl`. To prevent sudden breakage, this behavior was carefully deprecated with Nix throwing an error if the identifier for `--arg` is not a pure identifier, but an expression as above. Any kind of merging is rejected. I.e. doing nix-build ../nixpkgs --arg config '{cudaSupport = true;}' --arg config.allowUnfree true is prohibited. That way we don't have to think about merge semantics for cases like this (or even worse `--arg config 'rec { ... }'`). Another nice side-effect of this is that we don't need to create an EvalState to force the values and implement merging. Change-Id: I8b560883a4468a3f32f915764b08f5fdd8fe71bb
276 lines
8.9 KiB
C++
276 lines
8.9 KiB
C++
#include "libexpr/value.hh"
|
|
#include "libutil/strings.hh"
|
|
#include "lix/libexpr/attr-path.hh"
|
|
#include "lix/libexpr/eval-settings.hh"
|
|
#include "lix/libcmd/common-eval-args.hh"
|
|
#include "lix/libmain/shared.hh"
|
|
#include "lix/libstore/filetransfer.hh"
|
|
#include "lix/libexpr/eval.hh"
|
|
#include "lix/libfetchers/fetchers.hh"
|
|
#include "lix/libfetchers/registry.hh"
|
|
#include "lix/libexpr/flake/flakeref.hh"
|
|
#include "lix/libstore/store-api.hh"
|
|
#include "lix/libcmd/command.hh"
|
|
#include "lix/libutil/async.hh"
|
|
#include "lix/libutil/error.hh"
|
|
#include <deque>
|
|
|
|
namespace nix {
|
|
|
|
MixEvalArgs::MixEvalArgs()
|
|
{
|
|
addFlag(
|
|
{.longName = "arg",
|
|
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
|
.category = category,
|
|
.labels = {"name", "expr"},
|
|
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = ExprArgument(expr); }}}
|
|
);
|
|
|
|
addFlag({
|
|
.longName = "argstr",
|
|
.description = "Pass the string *string* as the argument *name* to Nix functions.",
|
|
.category = category,
|
|
.labels = {"name", "string"},
|
|
.handler = {[&](std::string name, std::string s) { autoArgs[name] = StringArgument(s); }},
|
|
});
|
|
|
|
addFlag({
|
|
.longName = "include",
|
|
.shortName = 'I',
|
|
.description = R"(
|
|
Add *path* to the Nix search path. The Nix search path is
|
|
initialized from the colon-separated [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH) environment
|
|
variable, and is used to look up the location of Nix expressions using [paths](@docroot@/language/values.md#type-path) enclosed in angle
|
|
brackets (i.e., `<nixpkgs>`).
|
|
|
|
For instance, passing
|
|
|
|
```
|
|
-I /home/eelco/Dev
|
|
-I /etc/nixos
|
|
```
|
|
|
|
will cause Lix to look for paths relative to `/home/eelco/Dev` and
|
|
`/etc/nixos`, in that order. This is equivalent to setting the
|
|
`NIX_PATH` environment variable to
|
|
|
|
```
|
|
/home/eelco/Dev:/etc/nixos
|
|
```
|
|
|
|
It is also possible to match paths against a prefix. For example,
|
|
passing
|
|
|
|
```
|
|
-I nixpkgs=/home/eelco/Dev/nixpkgs-branch
|
|
-I /etc/nixos
|
|
```
|
|
|
|
will cause Lix to search for `<nixpkgs/path>` in
|
|
`/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`.
|
|
|
|
If a path in the Nix search path starts with `http://` or `https://`,
|
|
it is interpreted as the URL of a tarball that will be downloaded and
|
|
unpacked to a temporary location. The tarball must consist of a single
|
|
top-level directory. For example, passing
|
|
|
|
```
|
|
-I nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz
|
|
```
|
|
|
|
tells Lix to download and use the current contents of the `master`
|
|
branch in the `nixpkgs` repository.
|
|
|
|
The URLs of the tarballs from the official `nixos.org` channels
|
|
(see [the manual page for `nix-channel`](../nix-channel.md)) can be
|
|
abbreviated as `channel:<channel-name>`. For instance, the
|
|
following two flags are equivalent:
|
|
|
|
```
|
|
-I nixpkgs=channel:nixos-21.05
|
|
-I nixpkgs=https://channels.nixos.org/nixos-21.05/nixexprs.tar.xz
|
|
```
|
|
|
|
You can also fetch source trees using [flake URLs](./nix3-flake.md#url-like-syntax) and add them to the
|
|
search path. For instance,
|
|
|
|
```
|
|
-I nixpkgs=flake:nixpkgs
|
|
```
|
|
|
|
specifies that the prefix `nixpkgs` shall refer to the source tree
|
|
downloaded from the `nixpkgs` entry in the flake registry. Similarly,
|
|
|
|
```
|
|
-I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05
|
|
```
|
|
|
|
makes `<nixpkgs>` refer to a particular branch of the
|
|
`NixOS/nixpkgs` repository on GitHub.
|
|
)",
|
|
.category = category,
|
|
.labels = {"path"},
|
|
.handler = {[&](std::string s) {
|
|
searchPath.elements.emplace_back(SearchPath::Elem::parse(s));
|
|
}}
|
|
});
|
|
|
|
addFlag({
|
|
.longName = "impure",
|
|
.description = "Allow access to mutable paths and repositories.",
|
|
.category = category,
|
|
.handler = {[&]() {
|
|
evalSettings.pureEval.override(false);
|
|
}},
|
|
});
|
|
|
|
addFlag({
|
|
.longName = "override-flake",
|
|
.description = "Override the flake registries, redirecting *original-ref* to *resolved-ref*.",
|
|
.category = category,
|
|
.labels = {"original-ref", "resolved-ref"},
|
|
.handler = {[&](std::string _from, std::string _to) {
|
|
auto from = parseFlakeRef(_from, absPath("."));
|
|
auto to = parseFlakeRef(_to, absPath("."));
|
|
fetchers::Attrs extraAttrs;
|
|
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
|
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
|
}},
|
|
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
|
completeFlakeRef(aio(), completions, aio().blockOn(openStore()), prefix);
|
|
}}
|
|
});
|
|
|
|
addFlag({
|
|
.longName = "eval-store",
|
|
.description =
|
|
R"(
|
|
The [URL of the Nix store](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
|
|
to use for evaluation, i.e. to store derivations (`.drv` files) and inputs referenced by them.
|
|
)",
|
|
.category = category,
|
|
.labels = {"store-url"},
|
|
.handler = {&evalStoreUrl},
|
|
});
|
|
}
|
|
|
|
struct AutoArgsContainer
|
|
{
|
|
std::map<Symbol, std::variant<Value, AutoArgsContainer>> data;
|
|
|
|
Bindings * toBindings(Evaluator & state)
|
|
{
|
|
auto bb = state.buildBindings(data.size());
|
|
|
|
for (auto & [sym, v] : data) {
|
|
bb.insert(
|
|
sym,
|
|
std::visit(
|
|
overloaded{
|
|
[&](Value & v) { return v; },
|
|
[&](AutoArgsContainer & aac) -> Value {
|
|
return {NewValueAs::attrs, aac.toBindings(state)};
|
|
}
|
|
},
|
|
v
|
|
)
|
|
);
|
|
}
|
|
|
|
return bb.finish();
|
|
}
|
|
};
|
|
|
|
static void addAutoArgRecursive(
|
|
AutoArgsContainer & container,
|
|
Evaluator & state,
|
|
std::vector<std::string> && path,
|
|
Value & val,
|
|
const std::string_view pathStr
|
|
)
|
|
{
|
|
auto * data = &container.data;
|
|
auto size = path.size();
|
|
for (auto [i, pathCmp] : enumerate(path)) {
|
|
auto next = state.symbols.create(pathCmp);
|
|
auto entry = data->find(next);
|
|
|
|
if (entry == data->end()) {
|
|
if (i == size - 1) {
|
|
(*data)[next] = val;
|
|
} else {
|
|
(*data)[next] = AutoArgsContainer{};
|
|
data = &std::get<AutoArgsContainer>((*data)[next]).data;
|
|
}
|
|
} else {
|
|
std::visit(
|
|
overloaded{
|
|
[&](Value & v) {
|
|
throw Error(
|
|
"Cannot set %s via --arg/--argstr when it's the path-extension of another "
|
|
"auto-argument!",
|
|
pathStr
|
|
);
|
|
},
|
|
[&](AutoArgsContainer & v) { data = &v.data; }
|
|
},
|
|
entry->second
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
|
|
{
|
|
AutoArgsContainer aac;
|
|
for (auto & [name, value] : autoArgs) {
|
|
Value v = std::visit(
|
|
overloaded{
|
|
[&](StringArgument & str) -> Value { return {NewValueAs::string, (std::string_view) str.value}; },
|
|
[&](ExprArgument & e) -> Value {
|
|
return state.evalLazily(state.parseExprFromString(e.expr, CanonPath::fromCwd()));
|
|
}
|
|
},
|
|
value
|
|
);
|
|
|
|
addAutoArgRecursive(aac, state, parseAttrPath(name, false), v, name);
|
|
}
|
|
|
|
return aac.toBindings(state);
|
|
}
|
|
|
|
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
|
|
lookupFileArg(Evaluator & state, std::string_view fileArg)
|
|
try {
|
|
if (EvalSettings::isPseudoUrl(fileArg)) {
|
|
auto const url = EvalSettings::resolvePseudoUrl(fileArg);
|
|
auto const downloaded = TRY_AWAIT(fetchers::downloadTarball(
|
|
state.store,
|
|
url,
|
|
/* name */ "source",
|
|
/* locked */ false
|
|
));
|
|
StorePath const storePath = downloaded.tree.storePath;
|
|
co_return SourcePath(CanonPath(state.store->toRealPath(storePath)));
|
|
} else if (fileArg.starts_with("flake:")) {
|
|
experimentalFeatureSettings.require(Xp::Flakes);
|
|
static constexpr size_t FLAKE_LEN = std::string_view("flake:").size();
|
|
auto flakeRef = parseFlakeRef(std::string(fileArg.substr(FLAKE_LEN)), {}, true, false);
|
|
auto storePath = TRY_AWAIT(TRY_AWAIT(flakeRef.resolve(state.store)).fetchTree(state.store))
|
|
.first.storePath;
|
|
co_return SourcePath(CanonPath(state.store->toRealPath(storePath)));
|
|
} else if (fileArg.size() > 2 && fileArg.at(0) == '<' && fileArg.at(fileArg.size() - 1) == '>')
|
|
{
|
|
Path p(fileArg.substr(1, fileArg.size() - 2));
|
|
co_return TRY_AWAIT(state.paths.findFile(p));
|
|
} else {
|
|
co_return SourcePath(CanonPath::fromCwd(fileArg));
|
|
}
|
|
} catch (...) {
|
|
co_return result::current_exception();
|
|
}
|
|
|
|
}
|