aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/app.cc17
-rw-r--r--src/nix/build.cc64
-rw-r--r--src/nix/build.md7
-rw-r--r--src/nix/bundle.cc14
-rw-r--r--src/nix/bundle.md2
-rw-r--r--src/nix/daemon.cc2
-rw-r--r--src/nix/daemon.md2
-rw-r--r--src/nix/develop.cc53
-rw-r--r--src/nix/develop.md10
-rw-r--r--src/nix/edit.cc18
-rw-r--r--src/nix/eval.cc30
-rw-r--r--src/nix/flake-update.md4
-rw-r--r--src/nix/flake.cc385
-rw-r--r--src/nix/flake.md101
-rw-r--r--src/nix/fmt.cc3
-rw-r--r--src/nix/get-env.sh5
-rw-r--r--src/nix/key-generate-secret.md2
-rw-r--r--src/nix/local.mk2
-rw-r--r--src/nix/ls.cc5
-rw-r--r--src/nix/main.cc32
-rw-r--r--src/nix/make-content-addressed.cc14
-rw-r--r--src/nix/make-content-addressed.md2
-rw-r--r--src/nix/nix.md74
-rw-r--r--src/nix/path-from-hash-part.cc39
-rw-r--r--src/nix/path-from-hash-part.md20
-rw-r--r--src/nix/path-info.cc8
-rw-r--r--src/nix/path-info.md4
-rw-r--r--src/nix/prefetch.cc22
-rw-r--r--src/nix/profile-install.md7
-rw-r--r--src/nix/profile-list.md6
-rw-r--r--src/nix/profile-upgrade.md6
-rw-r--r--src/nix/profile.cc63
-rw-r--r--src/nix/profile.md5
-rw-r--r--src/nix/registry.cc8
-rw-r--r--src/nix/registry.md2
-rw-r--r--src/nix/repl.cc913
-rw-r--r--src/nix/repl.md30
-rw-r--r--src/nix/run.cc15
-rw-r--r--src/nix/run.hh3
-rw-r--r--src/nix/search.cc74
-rw-r--r--src/nix/search.md13
-rw-r--r--src/nix/shell.md6
-rw-r--r--src/nix/show-derivation.cc67
-rw-r--r--src/nix/show-derivation.md6
-rw-r--r--src/nix/store-copy-log.md4
-rw-r--r--src/nix/upgrade-nix.cc4
-rw-r--r--src/nix/upgrade-nix.md9
-rw-r--r--src/nix/verify.cc2
-rw-r--r--src/nix/why-depends.cc29
49 files changed, 874 insertions, 1339 deletions
diff --git a/src/nix/app.cc b/src/nix/app.cc
index df7303e15..5658f2a52 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -37,11 +37,13 @@ struct InstallableDerivedPath : Installable
* Return the rewrites that are needed to resolve a string whose context is
* included in `dependencies`.
*/
-StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies)
+StringPairs resolveRewrites(
+ Store & store,
+ const std::vector<BuiltPathWithResult> & dependencies)
{
StringPairs res;
for (auto & dep : dependencies)
- if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep))
+ if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path))
for (auto & [ outputName, outputPath ] : drvDep->outputs)
res.emplace(
downstreamPlaceholder(store, drvDep->drvPath, outputName),
@@ -53,7 +55,10 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies)
/**
* Resolve the given string assuming the given context.
*/
-std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies)
+std::string resolveString(
+ Store & store,
+ const std::string & toResolve,
+ const std::vector<BuiltPathWithResult> & dependencies)
{
auto rewrites = resolveRewrites(store, dependencies);
return rewriteStrings(toResolve, rewrites);
@@ -66,7 +71,9 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto type = cursor->getAttr("type")->getString();
- std::string expected = !attrPath.empty() && attrPath[0] == "apps" ? "app" : "derivation";
+ std::string expected = !attrPath.empty() &&
+ (state.symbols[attrPath[0]] == "apps" || state.symbols[attrPath[0]] == "defaultApp")
+ ? "app" : "derivation";
if (type != expected)
throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
@@ -89,7 +96,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto outputName = cursor->getAttr(state.sOutputName)->getString();
auto name = cursor->getAttr(state.sName)->getString();
auto aPname = cursor->maybeGetAttr("pname");
- auto aMeta = cursor->maybeGetAttr("meta");
+ auto aMeta = cursor->maybeGetAttr(state.sMeta);
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
auto mainProgram =
aMainProgram
diff --git a/src/nix/build.cc b/src/nix/build.cc
index 840c7ca38..94b169167 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -4,14 +4,47 @@
#include "shared.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
+#include "progress-bar.hh"
#include <nlohmann/json.hpp>
using namespace nix;
+nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref<Store> store)
+{
+ auto res = nlohmann::json::array();
+ for (auto & t : paths) {
+ std::visit([&res, store](const auto & t) {
+ res.push_back(t.toJSON(store));
+ }, t.raw());
+ }
+ return res;
+}
+
+nlohmann::json builtPathsWithResultToJSON(const std::vector<BuiltPathWithResult> & buildables, ref<Store> store)
+{
+ auto res = nlohmann::json::array();
+ for (auto & b : buildables) {
+ std::visit([&](const auto & t) {
+ auto j = t.toJSON(store);
+ if (b.result) {
+ j["startTime"] = b.result->startTime;
+ j["stopTime"] = b.result->stopTime;
+ if (b.result->cpuUser)
+ j["cpuUser"] = ((double) b.result->cpuUser->count()) / 1000000;
+ if (b.result->cpuSystem)
+ j["cpuSystem"] = ((double) b.result->cpuSystem->count()) / 1000000;
+ }
+ res.push_back(j);
+ }, b.path.raw());
+ }
+ return res;
+}
+
struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
{
Path outLink = "result";
+ bool printOutputPaths = false;
BuildMode buildMode = bmNormal;
CmdBuild()
@@ -32,6 +65,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
});
addFlag({
+ .longName = "print-out-paths",
+ .description = "Print the resulting output paths",
+ .handler = {&printOutputPaths, true},
+ });
+
+ addFlag({
.longName = "rebuild",
.description = "Rebuild an already built package and compare the result to the existing store paths.",
.handler = {&buildMode, bmCheck},
@@ -70,7 +109,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
Realise::Outputs,
installables, buildMode);
- if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump());
+ if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, store).dump());
if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
@@ -90,10 +129,29 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
store2->addPermRoot(output.second, absPath(symlink));
}
},
- }, buildable.raw());
+ }, buildable.path.raw());
}
- updateProfile(buildables);
+ if (printOutputPaths) {
+ stopProgressBar();
+ for (auto & buildable : buildables) {
+ std::visit(overloaded {
+ [&](const BuiltPath::Opaque & bo) {
+ std::cout << store->printStorePath(bo.path) << std::endl;
+ },
+ [&](const BuiltPath::Built & bfd) {
+ for (auto & output : bfd.outputs) {
+ std::cout << store->printStorePath(output.second) << std::endl;
+ }
+ },
+ }, buildable.path.raw());
+ }
+ }
+
+ BuiltPaths buildables2;
+ for (auto & b : buildables)
+ buildables2.push_back(b.path);
+ updateProfile(buildables2);
}
};
diff --git a/src/nix/build.md b/src/nix/build.md
index 20138b7e0..6a79f308c 100644
--- a/src/nix/build.md
+++ b/src/nix/build.md
@@ -25,6 +25,13 @@ R""(
lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2
```
+* Build GNU Hello and print the resulting store path.
+
+ ```console
+ # nix build nixpkgs#hello --print-out-paths
+ /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
+ ```
+
* Build a specific output:
```console
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 81fb8464a..74a7973b0 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand
auto val = installable->toValue(*evalState).first;
- auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
+ auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false };
InstallableFlake bundler{this,
- evalState, std::move(bundlerFlakeRef), bundlerName,
+ evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec,
{"bundlers." + settings.thisSystem.get() + ".default",
"defaultBundler." + settings.thisSystem.get()
},
@@ -97,21 +97,23 @@ struct CmdBundle : InstallableCommand
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2;
- auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2);
+ auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
- auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2);
+ auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
store->buildPaths({ DerivedPath::Built { drvPath } });
auto outPathS = store->printStorePath(outPath);
if (!outLink) {
- auto &attr = vRes->attrs->need(evalState->sName);
- outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos);
+ auto * attr = vRes->attrs->get(evalState->sName);
+ if (!attr)
+ throw Error("attribute 'name' missing");
+ outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
}
// TODO: will crash if not a localFSStore?
diff --git a/src/nix/bundle.md b/src/nix/bundle.md
index 2bb70711f..a18161a3c 100644
--- a/src/nix/bundle.md
+++ b/src/nix/bundle.md
@@ -44,7 +44,7 @@ flake output attributes:
* `bundlers.<system>.default`
-If an attribute *name* is given, `nix run` tries the following flake
+If an attribute *name* is given, `nix bundle` tries the following flake
output attributes:
* `bundlers.<system>.<name>`
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
index 940923d3b..c527fdb0a 100644
--- a/src/nix/daemon.cc
+++ b/src/nix/daemon.cc
@@ -257,7 +257,7 @@ static void daemonLoop()
} catch (Interrupted & e) {
return;
} catch (Error & error) {
- ErrorInfo ei = error.info();
+ auto ei = error.info();
// FIXME: add to trace?
ei.msg = hintfmt("error processing connection: %1%", ei.msg.str());
logError(ei);
diff --git a/src/nix/daemon.md b/src/nix/daemon.md
index e97016a94..d5cdadf08 100644
--- a/src/nix/daemon.md
+++ b/src/nix/daemon.md
@@ -11,7 +11,7 @@ R""(
# Description
This command runs the Nix daemon, which is a required component in
-multi-user Nix installations. It performs build actions and other
+multi-user Nix installations. It runs build tasks and other
operations on the Nix store on behalf of non-root users. Usually you
don't run the daemon directly; instead it's managed by a service
management framework such as `systemd`.
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 7fc74d34e..1d90d1dac 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -18,6 +18,9 @@ struct DevelopSettings : Config
Setting<std::string> bashPrompt{this, "", "bash-prompt",
"The bash prompt (`PS1`) in `nix develop` shells."};
+ Setting<std::string> bashPromptPrefix{this, "", "bash-prompt-prefix",
+ "Prefix prepended to the `PS1` environment variable in `nix develop` shells."};
+
Setting<std::string> bashPromptSuffix{this, "", "bash-prompt-suffix",
"Suffix appended to the `PS1` environment variable in `nix develop` shells."};
};
@@ -161,6 +164,14 @@ struct BuildEnvironment
{
return vars == other.vars && bashFunctions == other.bashFunctions;
}
+
+ std::string getSystem() const
+ {
+ if (auto v = get(vars, "system"))
+ return getString(*v);
+ else
+ return settings.thisSystem;
+ }
};
const static std::string getEnvSh =
@@ -189,10 +200,12 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
drv.env.erase("allowedRequisites");
drv.env.erase("disallowedReferences");
drv.env.erase("disallowedRequisites");
+ drv.env.erase("name");
/* Rehash and write the derivation. FIXME: would be nice to use
'buildDerivation', but that's privileged. */
drv.name += "-env";
+ drv.env.emplace("name", drv.name);
drv.inputSrcs.insert(std::move(getEnvShPath));
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) {
@@ -243,6 +256,7 @@ struct Common : InstallableCommand, MixProfile
"NIX_LOG_FD",
"NIX_REMOTE",
"PPID",
+ "SHELL",
"SHELLOPTS",
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
"TEMP",
@@ -273,15 +287,27 @@ struct Common : InstallableCommand, MixProfile
const BuildEnvironment & buildEnvironment,
const Path & outputsDir = absPath(".") + "/outputs")
{
+ // A list of colon-separated environment variables that should be
+ // prepended to, rather than overwritten, in order to keep the shell usable.
+ // Please keep this list minimal in order to avoid impurities.
+ static const char * const savedVars[] = {
+ "PATH", // for commands
+ "XDG_DATA_DIRS", // for loadable completion
+ };
+
std::ostringstream out;
out << "unset shellHook\n";
- out << "nix_saved_PATH=\"$PATH\"\n";
+ for (auto & var : savedVars) {
+ out << fmt("%s=${%s:-}\n", var, var);
+ out << fmt("nix_saved_%s=\"$%s\"\n", var, var);
+ }
buildEnvironment.toBash(out, ignoreVars);
- out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
+ for (auto & var : savedVars)
+ out << fmt("%s=\"$%s:$nix_saved_%s\"\n", var, var, var);
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
@@ -482,6 +508,9 @@ struct CmdDevelop : Common, MixEnvironment
if (developSettings.bashPrompt != "")
script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n",
shellEscape(developSettings.bashPrompt.get()));
+ if (developSettings.bashPromptPrefix != "")
+ script += fmt("[ -n \"$PS1\" ] && PS1=%s\"$PS1\";\n",
+ shellEscape(developSettings.bashPromptPrefix.get()));
if (developSettings.bashPromptSuffix != "")
script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n",
shellEscape(developSettings.bashPromptSuffix.get()));
@@ -507,13 +536,25 @@ struct CmdDevelop : Common, MixEnvironment
state,
installable->nixpkgsFlakeRef(),
"bashInteractive",
+ DefaultOutputs(),
Strings{},
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
nixpkgsLockFlags);
- shell = store->printStorePath(
- Installable::toStorePath(getEvalStore(), store, Realise::Outputs, OperateOn::Output, bashInstallable))
- + "/bin/bash";
+ bool found = false;
+
+ for (auto & path : Installable::toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, {bashInstallable})) {
+ auto s = store->printStorePath(path) + "/bin/bash";
+ if (pathExists(s)) {
+ shell = s;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ throw Error("package 'nixpkgs#bashInteractive' does not provide a 'bin/bash'");
+
} catch (Error &) {
ignoreException();
}
@@ -537,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment
}
}
- runProgramInStore(store, shell, args);
+ runProgramInStore(store, shell, args, buildEnvironment.getSystem());
}
};
diff --git a/src/nix/develop.md b/src/nix/develop.md
index 8bcff66c9..4e8542d1b 100644
--- a/src/nix/develop.md
+++ b/src/nix/develop.md
@@ -66,6 +66,12 @@ R""(
`nixpkgs#glibc` in `~/my-glibc` and want to compile another package
against it.
+* Run a series of script commands:
+
+ ```console
+ # nix develop --command bash -c "mkdir build && cmake .. && make"
+ ```
+
# Description
`nix develop` starts a `bash` shell that provides an interactive build
@@ -80,8 +86,8 @@ initialised by `stdenv` and exits. This build environment can be
recorded into a profile using `--profile`.
The prompt used by the `bash` shell can be customised by setting the
-`bash-prompt` and `bash-prompt-suffix` settings in `nix.conf` or in
-the flake's `nixConfig` attribute.
+`bash-prompt`, `bash-prompt-prefix`, and `bash-prompt-suffix` settings in
+`nix.conf` or in the flake's `nixConfig` attribute.
# Flake output attributes
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index fc48db0d7..76a134b1f 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -28,19 +28,19 @@ struct CmdEdit : InstallableCommand
{
auto state = getEvalState();
- auto [v, pos] = installable->toValue(*state);
+ const auto [file, line] = [&] {
+ auto [v, pos] = installable->toValue(*state);
- try {
- pos = findPackageFilename(*state, *v, installable->what());
- } catch (NoPositionInfo &) {
- }
-
- if (pos == noPos)
- throw Error("cannot find position information for '%s", installable->what());
+ try {
+ return findPackageFilename(*state, *v, installable->what());
+ } catch (NoPositionInfo &) {
+ throw Error("cannot find position information for '%s", installable->what());
+ }
+ }();
stopProgressBar();
- auto args = editorFor(pos);
+ auto args = editorFor(file, line);
restoreProcessContext();
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 733b93661..ccee074e9 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -4,10 +4,11 @@
#include "store-api.hh"
#include "eval.hh"
#include "eval-inline.hh"
-#include "json.hh"
#include "value-to-json.hh"
#include "progress-bar.hh"
+#include <nlohmann/json.hpp>
+
using namespace nix;
struct CmdEval : MixJSON, InstallableCommand
@@ -77,9 +78,9 @@ struct CmdEval : MixJSON, InstallableCommand
if (pathExists(*writeTo))
throw Error("path '%s' already exists", *writeTo);
- std::function<void(Value & v, const Pos & pos, const Path & path)> recurse;
+ std::function<void(Value & v, const PosIdx pos, const Path & path)> recurse;
- recurse = [&](Value & v, const Pos & pos, const Path & path)
+ recurse = [&](Value & v, const PosIdx pos, const Path & path)
{
state->forceValue(v, pos);
if (v.type() == nString)
@@ -88,18 +89,22 @@ struct CmdEval : MixJSON, InstallableCommand
else if (v.type() == nAttrs) {
if (mkdir(path.c_str(), 0777) == -1)
throw SysError("creating directory '%s'", path);
- for (auto & attr : *v.attrs)
+ for (auto & attr : *v.attrs) {
+ std::string_view name = state->symbols[attr.name];
try {
- if (attr.name == "." || attr.name == "..")
- throw Error("invalid file name '%s'", attr.name);
- recurse(*attr.value, *attr.pos, path + "/" + std::string(attr.name));
+ if (name == "." || name == "..")
+ throw Error("invalid file name '%s'", name);
+ recurse(*attr.value, attr.pos, concatStrings(path, "/", name));
} catch (Error & e) {
- e.addTrace(*attr.pos, hintfmt("while evaluating the attribute '%s'", attr.name));
+ e.addTrace(
+ state->positions[attr.pos],
+ hintfmt("while evaluating the attribute '%s'", name));
throw;
}
+ }
}
else
- throw TypeError("value at '%s' is not a string or an attribute set", pos);
+ throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]);
};
recurse(*v, pos, *writeTo);
@@ -107,17 +112,16 @@ struct CmdEval : MixJSON, InstallableCommand
else if (raw) {
stopProgressBar();
- std::cout << *state->coerceToString(noPos, *v, context);
+ std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output");
}
else if (json) {
- JSONPlaceholder jsonOut(std::cout);
- printValueAsJSON(*state, true, *v, pos, jsonOut, context);
+ std::cout << printValueAsJSON(*state, true, *v, pos, context, false).dump() << std::endl;
}
else {
state->forceValueDeep(*v);
- logger->cout("%s", *v);
+ logger->cout("%s", printValue(*state, *v));
}
}
};
diff --git a/src/nix/flake-update.md b/src/nix/flake-update.md
index 03b50e38e..8c6042d94 100644
--- a/src/nix/flake-update.md
+++ b/src/nix/flake-update.md
@@ -6,7 +6,7 @@ R""(
lock file:
```console
- # nix flake update
+ # nix flake update --commit-lock-file
* Updated 'nix': 'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' -> 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c'
* Updated 'nixpkgs': 'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' -> 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293'
@@ -16,7 +16,7 @@ R""(
# Description
This command recreates the lock file of a flake (`flake.lock`), thus
-updating the lock for every mutable input (like `nixpkgs`) to its
+updating the lock for every unlocked input (like `nixpkgs`) to its
current version. This is equivalent to passing `--recreate-lock-file`
to any command that operates on a flake. That is,
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 04b23ed0f..9b4cdf35a 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -11,7 +11,6 @@
#include "attr-path.hh"
#include "fetchers.hh"
#include "registry.hh"
-#include "json.hh"
#include "eval-cache.hh"
#include "markdown.hh"
@@ -21,6 +20,7 @@
using namespace nix;
using namespace nix::flake;
+using json = nlohmann::json;
class FlakeCommand : virtual Args, public MixFlakeOptions
{
@@ -50,9 +50,9 @@ public:
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
}
- std::optional<FlakeRef> getFlakeRefForCompletion() override
+ std::vector<std::string> getFlakesForCompletion() override
{
- return getFlakeRef();
+ return {flakeUrl};
}
};
@@ -123,15 +123,15 @@ struct CmdFlakeLock : FlakeCommand
};
static void enumerateOutputs(EvalState & state, Value & vFlake,
- std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback)
+ std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
{
auto pos = vFlake.determinePos(noPos);
- state.forceAttrs(vFlake, pos);
+ state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
assert(aOutputs);
- state.forceAttrs(*aOutputs->value, pos);
+ state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
auto sHydraJobs = state.symbols.create("hydraJobs");
@@ -139,11 +139,11 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
else. This way we can disable IFD for hydraJobs and then enable
it for other outputs. */
if (auto attr = aOutputs->value->attrs->get(sHydraJobs))
- callback(attr->name, *attr->value, *attr->pos);
+ callback(state.symbols[attr->name], *attr->value, attr->pos);
for (auto & attr : *aOutputs->value->attrs) {
if (attr.name != sHydraJobs)
- callback(attr.name, *attr.value, *attr.pos);
+ callback(state.symbols[attr.name], *attr.value, attr.pos);
}
}
@@ -212,9 +212,10 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
std::put_time(std::localtime(&*lastModified), "%F %T"));
- logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
+ if (!lockedFlake.lockFile.root->inputs.empty())
+ logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
- std::unordered_set<std::shared_ptr<Node>> visited;
+ std::set<ref<Node>> visited;
std::function<void(const Node & node, const std::string & prefix)> recurse;
@@ -226,7 +227,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
if (auto lockedNode = std::get_if<0>(&input.second)) {
logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
prefix + (last ? treeLast : treeConn), input.first,
- *lockedNode ? (*lockedNode)->lockedRef : flake.lockedRef);
+ (*lockedNode)->lockedRef);
bool firstVisit = visited.insert(*lockedNode).second;
@@ -254,14 +255,6 @@ struct CmdFlakeInfo : CmdFlakeMetadata
}
};
-static bool argHasName(std::string_view arg, std::string_view expected)
-{
- return
- arg == expected
- || arg == "_"
- || (hasPrefix(arg, "_") && arg.substr(1) == expected);
-}
-
struct CmdFlakeCheck : FlakeCommand
{
bool build = true;
@@ -315,13 +308,25 @@ struct CmdFlakeCheck : FlakeCommand
// FIXME: rewrite to use EvalCache.
- auto checkSystemName = [&](const std::string & system, const Pos & pos) {
+ auto resolve = [&] (PosIdx p) {
+ return state->positions[p];
+ };
+
+ auto argHasName = [&] (Symbol arg, std::string_view expected) {
+ std::string_view name = state->symbols[arg];
+ return
+ name == expected
+ || name == "_"
+ || (hasPrefix(name, "_") && name.substr(1) == expected);
+ };
+
+ auto checkSystemName = [&](const std::string & system, const PosIdx pos) {
// FIXME: what's the format of "system"?
if (system.find('-') == std::string::npos)
- reportError(Error("'%s' is not a valid system type, at %s", system, pos));
+ reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos)));
};
- auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) -> std::optional<StorePath> {
+ auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional<StorePath> {
try {
auto drvInfo = getDerivation(*state, v, false);
if (!drvInfo)
@@ -329,7 +334,7 @@ struct CmdFlakeCheck : FlakeCommand
// FIXME: check meta attributes
return drvInfo->queryDrvPath();
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the derivation '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the derivation '%s'", attrPath));
reportError(e);
}
return std::nullopt;
@@ -337,7 +342,7 @@ struct CmdFlakeCheck : FlakeCommand
std::vector<DerivedPath> drvPaths;
- auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
#if 0
// FIXME
@@ -348,12 +353,12 @@ struct CmdFlakeCheck : FlakeCommand
}
#endif
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the app definition '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the app definition '%s'", attrPath));
reportError(e);
}
};
- auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceValue(v, pos);
if (!v.isLambda()
@@ -368,87 +373,72 @@ struct CmdFlakeCheck : FlakeCommand
// FIXME: if we have a 'nixpkgs' input, use it to
// evaluate the overlay.
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the overlay '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the overlay '%s'", attrPath));
reportError(e);
}
};
- auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceValue(v, pos);
- if (v.isLambda()) {
- if (!v.lambda.fun->hasFormals() || !v.lambda.fun->formals->ellipsis)
- throw Error("module must match an open attribute set ('{ config, ... }')");
- } else if (v.type() == nAttrs) {
- for (auto & attr : *v.attrs)
- try {
- state->forceValue(*attr.value, *attr.pos);
- } catch (Error & e) {
- e.addTrace(*attr.pos, hintfmt("while evaluating the option '%s'", attr.name));
- throw;
- }
- } else
- throw Error("module must be a function or an attribute set");
- // FIXME: if we have a 'nixpkgs' input, use it to
- // check the module.
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the NixOS module '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath));
reportError(e);
}
};
- std::function<void(const std::string & attrPath, Value & v, const Pos & pos)> checkHydraJobs;
+ std::function<void(const std::string & attrPath, Value & v, const PosIdx pos)> checkHydraJobs;
- checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
- state->forceAttrs(v, pos);
+ state->forceAttrs(v, pos, "");
if (state->isDerivation(v))
throw Error("jobset should not be a derivation at top-level");
for (auto & attr : *v.attrs) {
- state->forceAttrs(*attr.value, *attr.pos);
- auto attrPath2 = attrPath + "." + (std::string) attr.name;
+ state->forceAttrs(*attr.value, attr.pos, "");
+ auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
if (state->isDerivation(*attr.value)) {
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking Hydra job '%s'", attrPath2));
- checkDerivation(attrPath2, *attr.value, *attr.pos);
+ checkDerivation(attrPath2, *attr.value, attr.pos);
} else
- checkHydraJobs(attrPath2, *attr.value, *attr.pos);
+ checkHydraJobs(attrPath2, *attr.value, attr.pos);
}
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the Hydra jobset '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the Hydra jobset '%s'", attrPath));
reportError(e);
}
};
- auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking NixOS configuration '%s'", attrPath));
Bindings & bindings(*state->allocBindings(0));
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
- state->forceAttrs(*vToplevel, pos);
+ state->forceValue(*vToplevel, pos);
if (!state->isDerivation(*vToplevel))
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the NixOS configuration '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the NixOS configuration '%s'", attrPath));
reportError(e);
}
};
- auto checkTemplate = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkTemplate = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking template '%s'", attrPath));
- state->forceAttrs(v, pos);
+ state->forceAttrs(v, pos, "");
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) {
PathSet context;
- auto path = state->coerceToPath(*attr->pos, *attr->value, context);
+ auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
if (!store->isInStore(path))
throw Error("template '%s' has a bad 'path' attribute");
// TODO: recursively check the flake in 'path'.
@@ -457,29 +447,29 @@ struct CmdFlakeCheck : FlakeCommand
throw Error("template '%s' lacks attribute 'path'", attrPath);
if (auto attr = v.attrs->get(state->symbols.create("description")))
- state->forceStringNoCtx(*attr->value, *attr->pos);
+ state->forceStringNoCtx(*attr->value, attr->pos, "");
else
throw Error("template '%s' lacks attribute 'description'", attrPath);
for (auto & attr : *v.attrs) {
- std::string name(attr.name);
+ std::string_view name(state->symbols[attr.name]);
if (name != "path" && name != "description" && name != "welcomeText")
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
}
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath));
reportError(e);
}
};
- auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkBundler = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceValue(v, pos);
if (!v.isLambda())
throw Error("bundler must be a function");
// TODO: check types of inputs/outputs?
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
+ e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath));
reportError(e);
}
};
@@ -492,7 +482,7 @@ struct CmdFlakeCheck : FlakeCommand
enumerateOutputs(*state,
*vFlake,
- [&](const std::string & name, Value & vOutput, const Pos & pos) {
+ [&](const std::string & name, Value & vOutput, const PosIdx pos) {
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking flake output '%s'", name));
@@ -503,7 +493,7 @@ struct CmdFlakeCheck : FlakeCommand
std::string_view replacement =
name == "defaultPackage" ? "packages.<system>.default" :
- name == "defaultApps" ? "apps.<system>.default" :
+ name == "defaultApp" ? "apps.<system>.default" :
name == "defaultTemplate" ? "templates.default" :
name == "defaultBundler" ? "bundlers.<system>.default" :
name == "overlay" ? "overlays.default" :
@@ -514,78 +504,84 @@ struct CmdFlakeCheck : FlakeCommand
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
if (name == "checks") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
- state->forceAttrs(*attr.value, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
+ state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) {
auto drvPath = checkDerivation(
- fmt("%s.%s.%s", name, attr.name, attr2.name),
- *attr2.value, *attr2.pos);
- if (drvPath && (std::string) attr.name == settings.thisSystem.get())
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
+ if (drvPath && attr_name == settings.thisSystem.get())
drvPaths.push_back(DerivedPath::Built{*drvPath});
}
}
}
else if (name == "formatter") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
checkApp(
- fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
}
}
else if (name == "packages" || name == "devShells") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
- state->forceAttrs(*attr.value, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
+ state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs)
checkDerivation(
- fmt("%s.%s.%s", name, attr.name, attr2.name),
- *attr2.value, *attr2.pos);
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
}
}
else if (name == "apps") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
- state->forceAttrs(*attr.value, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
+ state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs)
checkApp(
- fmt("%s.%s.%s", name, attr.name, attr2.name),
- *attr2.value, *attr2.pos);
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
}
}
else if (name == "defaultPackage" || name == "devShell") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
checkDerivation(
- fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
}
}
else if (name == "defaultApp") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
checkApp(
- fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
}
}
else if (name == "legacyPackages") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
+ checkSystemName(state->symbols[attr.name], attr.pos);
// FIXME: do getDerivations?
}
}
@@ -594,27 +590,27 @@ struct CmdFlakeCheck : FlakeCommand
checkOverlay(name, vOutput, pos);
else if (name == "overlays") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
- checkOverlay(fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
+ *attr.value, attr.pos);
}
else if (name == "nixosModule")
checkModule(name, vOutput, pos);
else if (name == "nixosModules") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
- checkModule(fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
+ *attr.value, attr.pos);
}
else if (name == "nixosConfigurations") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
- checkNixOSConfiguration(fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
+ *attr.value, attr.pos);
}
else if (name == "hydraJobs")
@@ -624,31 +620,33 @@ struct CmdFlakeCheck : FlakeCommand
checkTemplate(name, vOutput, pos);
else if (name == "templates") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
- checkTemplate(fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
+ *attr.value, attr.pos);
}
else if (name == "defaultBundler") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
checkBundler(
- fmt("%s.%s", name, attr.name),
- *attr.value, *attr.pos);
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
}
}
else if (name == "bundlers") {
- state->forceAttrs(vOutput, pos);
+ state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
- checkSystemName(attr.name, *attr.pos);
- state->forceAttrs(*attr.value, *attr.pos);
+ const auto & attr_name = state->symbols[attr.name];
+ checkSystemName(attr_name, attr.pos);
+ state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) {
checkBundler(
- fmt("%s.%s.%s", name, attr.name, attr2.name),
- *attr2.value, *attr2.pos);
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
}
}
}
@@ -657,7 +655,7 @@ struct CmdFlakeCheck : FlakeCommand
warn("unknown flake output '%s'", name);
} catch (Error & e) {
- e.addTrace(pos, hintfmt("while checking flake output '%s'", name));
+ e.addTrace(resolve(pos), hintfmt("while checking flake output '%s'", name));
reportError(e);
}
});
@@ -710,7 +708,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
auto installable = InstallableFlake(nullptr,
- evalState, std::move(templateFlakeRef), templateName,
+ evalState, std::move(templateFlakeRef), templateName, DefaultOutputs(),
defaultTemplateAttrPaths,
defaultTemplateAttrPathsPrefixes,
lockFlags);
@@ -726,7 +724,8 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
"If you've set '%s' to a string, try using a path instead.",
templateDir, templateDirAttr->getAttrPathStr());
- std::vector<Path> files;
+ std::vector<Path> changedFiles;
+ std::vector<Path> conflictedFiles;
std::function<void(const Path & from, const Path & to)> copyDir;
copyDir = [&](const Path & from, const Path & to)
@@ -743,31 +742,41 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto contents = readFile(from2);
if (pathExists(to2)) {
auto contents2 = readFile(to2);
- if (contents != contents2)
- throw Error("refusing to overwrite existing file '%s'", to2);
+ if (contents != contents2) {
+ printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2, from2);
+ conflictedFiles.push_back(to2);
+ } else {
+ notice("skipping identical file: %s", from2);
+ }
+ continue;
} else
writeFile(to2, contents);
}
else if (S_ISLNK(st.st_mode)) {
auto target = readLink(from2);
if (pathExists(to2)) {
- if (readLink(to2) != target)
- throw Error("refusing to overwrite existing symlink '%s'", to2);
+ if (readLink(to2) != target) {
+ printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2, from2);
+ conflictedFiles.push_back(to2);
+ } else {
+ notice("skipping identical file: %s", from2);
+ }
+ continue;
} else
createSymlink(target, to2);
}
else
throw Error("file '%s' has unsupported type", from2);
- files.push_back(to2);
+ changedFiles.push_back(to2);
notice("wrote: %s", to2);
}
};
copyDir(templateDir, flakeDir);
- if (pathExists(flakeDir + "/.git")) {
+ if (!changedFiles.empty() && pathExists(flakeDir + "/.git")) {
Strings args = { "-C", flakeDir, "add", "--intent-to-add", "--force", "--" };
- for (auto & s : files) args.push_back(s);
+ for (auto & s : changedFiles) args.push_back(s);
runProgram("git", true, args);
}
auto welcomeText = cursor->maybeGetAttr("welcomeText");
@@ -775,6 +784,9 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
notice("\n");
notice(renderMarkdownToTerminal(welcomeText->getString()));
}
+
+ if (!conflictedFiles.empty())
+ throw Error("Encountered %d conflicts - see above", conflictedFiles.size());
}
};
@@ -888,35 +900,44 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
{
auto flake = lockFlake();
- auto jsonRoot = json ? std::optional<JSONObject>(std::cout) : std::nullopt;
-
StorePathSet sources;
sources.insert(flake.flake.sourceInfo->storePath);
- if (jsonRoot)
- jsonRoot->attr("path", store->printStorePath(flake.flake.sourceInfo->storePath));
// FIXME: use graph output, handle cycles.
- std::function<void(const Node & node, std::optional<JSONObject> & jsonObj)> traverse;
- traverse = [&](const Node & node, std::optional<JSONObject> & jsonObj)
+ std::function<nlohmann::json(const Node & node)> traverse;
+ traverse = [&](const Node & node)
{
- auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional<JSONObject>();
+ nlohmann::json jsonObj2 = json ? json::object() : nlohmann::json(nullptr);
for (auto & [inputName, input] : node.inputs) {
if (auto inputNode = std::get_if<0>(&input)) {
- auto jsonObj3 = jsonObj2 ? jsonObj2->object(inputName) : std::optional<JSONObject>();
auto storePath =
dryRun
? (*inputNode)->lockedRef.input.computeStorePath(*store)
: (*inputNode)->lockedRef.input.fetch(store).first.storePath;
- if (jsonObj3)
- jsonObj3->attr("path", store->printStorePath(storePath));
- sources.insert(std::move(storePath));
- traverse(**inputNode, jsonObj3);
+ if (json) {
+ auto& jsonObj3 = jsonObj2[inputName];
+ jsonObj3["path"] = store->printStorePath(storePath);
+ sources.insert(std::move(storePath));
+ jsonObj3["inputs"] = traverse(**inputNode);
+ } else {
+ sources.insert(std::move(storePath));
+ traverse(**inputNode);
+ }
}
}
+ return jsonObj2;
};
- traverse(*flake.lockFile.root, jsonRoot);
+ if (json) {
+ nlohmann::json jsonRoot = {
+ {"path", store->printStorePath(flake.flake.sourceInfo->storePath)},
+ {"inputs", traverse(*flake.lockFile.root)},
+ };
+ std::cout << jsonRoot.dump() << std::endl;
+ } else {
+ traverse(*flake.lockFile.root);
+ }
if (!dryRun && !dstUri.empty()) {
ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
@@ -972,8 +993,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
{
auto j = nlohmann::json::object();
+ auto attrPathS = state->symbols.resolve(attrPath);
+
Activity act(*logger, lvlInfo, actUnknown,
- fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
+ fmt("evaluating '%s'", concatStringsSep(".", attrPathS)));
+
try {
auto recurse = [&]()
{
@@ -981,14 +1005,15 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
logger->cout("%s", headerPrefix);
auto attrs = visitor.getAttrs();
for (const auto & [i, attr] : enumerate(attrs)) {
+ const auto & attrName = state->symbols[attr];
bool last = i + 1 == attrs.size();
- auto visitor2 = visitor.getAttr(attr);
+ auto visitor2 = visitor.getAttr(attrName);
auto attrPath2(attrPath);
attrPath2.push_back(attr);
auto j2 = visit(*visitor2, attrPath2,
- fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr),
+ fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attrName),
nextPrefix + (last ? treeNull : treeLine));
- if (json) j.emplace(attr, std::move(j2));
+ if (json) j.emplace(attrName, std::move(j2));
}
};
@@ -997,8 +1022,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto name = visitor.getAttr(state->sName)->getString();
if (json) {
std::optional<std::string> description;
- if (auto aMeta = visitor.maybeGetAttr("meta")) {
- if (auto aDescription = aMeta->maybeGetAttr("description"))
+ if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
+ if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
description = aDescription->getString();
}
j.emplace("type", "derivation");
@@ -1008,10 +1033,10 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
} else {
logger->cout("%s: %s '%s'",
headerPrefix,
- attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" :
- attrPath.size() >= 2 && attrPath[0] == "devShells" ? "development environment" :
- attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" :
- attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" :
+ attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" :
+ attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" :
+ attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" :
+ attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" :
"package",
name);
}
@@ -1019,27 +1044,27 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
if (attrPath.size() == 0
|| (attrPath.size() == 1 && (
- attrPath[0] == "defaultPackage"
- || attrPath[0] == "devShell"
- || attrPath[0] == "formatter"
- || attrPath[0] == "nixosConfigurations"
- || attrPath[0] == "nixosModules"
- || attrPath[0] == "defaultApp"
- || attrPath[0] == "templates"
- || attrPath[0] == "overlays"))
+ attrPathS[0] == "defaultPackage"
+ || attrPathS[0] == "devShell"
+ || attrPathS[0] == "formatter"
+ || attrPathS[0] == "nixosConfigurations"
+ || attrPathS[0] == "nixosModules"
+ || attrPathS[0] == "defaultApp"
+ || attrPathS[0] == "templates"
+ || attrPathS[0] == "overlays"))
|| ((attrPath.size() == 1 || attrPath.size() == 2)
- && (attrPath[0] == "checks"
- || attrPath[0] == "packages"
- || attrPath[0] == "devShells"
- || attrPath[0] == "apps"))
+ && (attrPathS[0] == "checks"
+ || attrPathS[0] == "packages"
+ || attrPathS[0] == "devShells"
+ || attrPathS[0] == "apps"))
)
{
recurse();
}
else if (
- (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" || attrPath[0] == "formatter"))
- || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells"))
+ (attrPath.size() == 2 && (attrPathS[0] == "defaultPackage" || attrPathS[0] == "devShell" || attrPathS[0] == "formatter"))
+ || (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells"))
)
{
if (visitor.isDerivation())
@@ -1048,19 +1073,23 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
throw Error("expected a derivation");
}
- else if (attrPath.size() > 0 && attrPath[0] == "hydraJobs") {
+ else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") {
if (visitor.isDerivation())
showDerivation();
else
recurse();
}
- else if (attrPath.size() > 0 && attrPath[0] == "legacyPackages") {
+ else if (attrPath.size() > 0 && attrPathS[0] == "legacyPackages") {
if (attrPath.size() == 1)
recurse();
- else if (!showLegacy)
- logger->warn(fmt("%s: " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix));
- else {
+ else if (!showLegacy){
+ if (!json)
+ logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix));
+ else {
+ logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPathS)));
+ }
+ } else {
if (visitor.isDerivation())
showDerivation();
else if (attrPath.size() <= 2)
@@ -1070,8 +1099,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
}
else if (
- (attrPath.size() == 2 && attrPath[0] == "defaultApp") ||
- (attrPath.size() == 3 && attrPath[0] == "apps"))
+ (attrPath.size() == 2 && attrPathS[0] == "defaultApp") ||
+ (attrPath.size() == 3 && attrPathS[0] == "apps"))
{
auto aType = visitor.maybeGetAttr("type");
if (!aType || aType->getString() != "app")
@@ -1084,8 +1113,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
}
else if (
- (attrPath.size() == 1 && attrPath[0] == "defaultTemplate") ||
- (attrPath.size() == 2 && attrPath[0] == "templates"))
+ (attrPath.size() == 1 && attrPathS[0] == "defaultTemplate") ||
+ (attrPath.size() == 2 && attrPathS[0] == "templates"))
{
auto description = visitor.getAttr("description")->getString();
if (json) {
@@ -1098,11 +1127,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
else {
auto [type, description] =
- (attrPath.size() == 1 && attrPath[0] == "overlay")
- || (attrPath.size() == 2 && attrPath[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") :
- attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") :
- (attrPath.size() == 1 && attrPath[0] == "nixosModule")
- || (attrPath.size() == 2 && attrPath[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") :
+ (attrPath.size() == 1 && attrPathS[0] == "overlay")
+ || (attrPath.size() == 2 && attrPathS[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") :
+ attrPath.size() == 2 && attrPathS[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") :
+ (attrPath.size() == 1 && attrPathS[0] == "nixosModule")
+ || (attrPath.size() == 2 && attrPathS[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") :
std::make_pair("unknown", "unknown");
if (json) {
j.emplace("type", type);
@@ -1111,7 +1140,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
}
}
} catch (EvalError & e) {
- if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
+ if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages"))
throw;
}
diff --git a/src/nix/flake.md b/src/nix/flake.md
index 7d179a6c4..810e9ebea 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -18,51 +18,56 @@ values such as packages or NixOS modules provided by the flake).
Flake references (*flakerefs*) are a way to specify the location of a
flake. These have two different forms:
-* An attribute set representation, e.g.
- ```nix
- {
- type = "github";
- owner = "NixOS";
- repo = "nixpkgs";
- }
- ```
+## Attribute set representation
- The only required attribute is `type`. The supported types are
- listed below.
+Example:
-* A URL-like syntax, e.g.
+```nix
+{
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+}
+```
- ```
- github:NixOS/nixpkgs
- ```
+The only required attribute is `type`. The supported types are
+listed below.
- These are used on the command line as a more convenient alternative
- to the attribute set representation. For instance, in the command
+## URL-like syntax
- ```console
- # nix build github:NixOS/nixpkgs#hello
- ```
+Example:
- `github:NixOS/nixpkgs` is a flake reference (while `hello` is an
- output attribute). They are also allowed in the `inputs` attribute
- of a flake, e.g.
+```
+github:NixOS/nixpkgs
+```
- ```nix
- inputs.nixpkgs.url = github:NixOS/nixpkgs;
- ```
+These are used on the command line as a more convenient alternative
+to the attribute set representation. For instance, in the command
- is equivalent to
+```console
+# nix build github:NixOS/nixpkgs#hello
+```
- ```nix
- inputs.nixpkgs = {
- type = "github";
- owner = "NixOS";
- repo = "nixpkgs";
- };
- ```
+`github:NixOS/nixpkgs` is a flake reference (while `hello` is an
+output attribute). They are also allowed in the `inputs` attribute
+of a flake, e.g.
+
+```nix
+inputs.nixpkgs.url = github:NixOS/nixpkgs;
+```
-## Examples
+is equivalent to
+
+```nix
+inputs.nixpkgs = {
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+};
+```
+
+### Examples
Here are some examples of flake references in their URL-like representation:
@@ -153,7 +158,7 @@ Currently the `type` attribute can be one of the following:
git(+http|+https|+ssh|+git|+file|):(//<server>)?<path>(\?<params>)?
```
- The `ref` attribute defaults to `master`.
+ The `ref` attribute defaults to resolving the `HEAD` reference.
The `rev` attribute must denote a commit that exists in the branch
or tag specified by the `ref` attribute, since Nix doesn't do a full
@@ -161,6 +166,11 @@ Currently the `type` attribute can be one of the following:
doesn't allow fetching a `rev` without a known `ref`). The default
is the commit currently pointed to by `ref`.
+ When `git+file` is used without specifying `ref` or `rev`, files are
+ fetched directly from the local `path` as long as they have been added
+ to the Git repository. If there are uncommitted changes, the reference
+ is treated as dirty and a warning is printed.
+
For example, the following are valid Git flake references:
* `git+https://example.org/my/repo`
@@ -176,9 +186,17 @@ Currently the `type` attribute can be one of the following:
* `tarball`: Tarballs. The location of the tarball is specified by the
attribute `url`.
- In URL form, the schema must be `http://`, `https://` or `file://`
- URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`,
- `.tar.xz`, `.tar.bz2` or `.tar.zst`.
+ In URL form, the schema must be `tarball+http://`, `tarball+https://` or `tarball+file://`.
+ If the extension corresponds to a known archive format (`.zip`, `.tar`,
+ `.tgz`, `.tar.gz`, `.tar.xz`, `.tar.bz2` or `.tar.zst`), then the `tarball+`
+ can be dropped.
+
+* `file`: Plain files or directory tarballs, either over http(s) or from the local
+ disk.
+
+ In URL form, the schema must be `file+http://`, `file+https://` or `file+file://`.
+ If the extension doesn’t correspond to a known archive format (as defined by the
+ `tarball` fetcher), then the `file+` prefix can be dropped.
* `github`: A more efficient way to fetch repositories from
GitHub. The following attributes are required:
@@ -326,9 +344,10 @@ The following attributes are supported in `flake.nix`:
* `nixConfig`: a set of `nix.conf` options to be set when evaluating any
part of a flake. In the interests of security, only a small set of
- whitelisted options (currently `bash-prompt`, `bash-prompt-suffix`,
- and `flake-registry`) are allowed to be set without confirmation so long as
- `accept-flake-config` is not set in the global configuration.
+ whitelisted options (currently `bash-prompt`, `bash-prompt-prefix`,
+ `bash-prompt-suffix`, and `flake-registry`) are allowed to be set without
+ confirmation so long as `accept-flake-config` is not set in the global
+ configuration.
## Flake inputs
diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc
index e5d44bd38..6f6a4a632 100644
--- a/src/nix/fmt.cc
+++ b/src/nix/fmt.cc
@@ -26,7 +26,8 @@ struct CmdFmt : SourceExprCommand {
Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; }
- void run(ref<Store> store) {
+ void run(ref<Store> store) override
+ {
auto evalState = getEvalState();
auto evalStore = getEvalStore();
diff --git a/src/nix/get-env.sh b/src/nix/get-env.sh
index 42c806450..a7a8a01b9 100644
--- a/src/nix/get-env.sh
+++ b/src/nix/get-env.sh
@@ -43,6 +43,7 @@ __dumpEnv() {
local __var_name="${BASH_REMATCH[2]}"
if [[ $__var_name =~ ^BASH_ || \
+ $__var_name =~ ^COMP_ || \
$__var_name = _ || \
$__var_name = DIRSTACK || \
$__var_name = EUID || \
@@ -54,7 +55,9 @@ __dumpEnv() {
$__var_name = PWD || \
$__var_name = RANDOM || \
$__var_name = SHLVL || \
- $__var_name = SECONDS \
+ $__var_name = SECONDS || \
+ $__var_name = EPOCHREALTIME || \
+ $__var_name = EPOCHSECONDS \
]]; then continue; fi
if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
diff --git a/src/nix/key-generate-secret.md b/src/nix/key-generate-secret.md
index 4938f637c..609b1abcc 100644
--- a/src/nix/key-generate-secret.md
+++ b/src/nix/key-generate-secret.md
@@ -30,7 +30,7 @@ convert-secret-to-public` to get the corresponding public key for
verifying signed store paths.
The mandatory argument `--key-name` specifies a key name (such as
-`cache.example.org-1). It is used to look up keys on the client when
+`cache.example.org-1`). It is used to look up keys on the client when
it verifies signatures. It can be anything, but it’s suggested to use
the host name of your cache (e.g. `cache.example.org`) with a suffix
denoting the number of the key (to be incremented every time you need
diff --git a/src/nix/local.mk b/src/nix/local.mk
index e4ec7634d..0f2f016ec 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -18,7 +18,7 @@ nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr
nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd
-nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -llowdown
+nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) $(LOWDOWN_LIBS)
$(foreach name, \
nix-build nix-channel nix-collect-garbage nix-copy-closure nix-daemon nix-env nix-hash nix-instantiate nix-prefetch-url nix-shell nix-store, \
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index 07554994b..e964b01b3 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -3,7 +3,7 @@
#include "fs-accessor.hh"
#include "nar-accessor.hh"
#include "common-args.hh"
-#include "json.hh"
+#include <nlohmann/json.hpp>
using namespace nix;
@@ -91,10 +91,9 @@ struct MixLs : virtual Args, MixJSON
if (path == "/") path = "";
if (json) {
- JSONPlaceholder jsonRoot(std::cout);
if (showDirectory)
throw UsageError("'--directory' is useless with '--json'");
- listNar(jsonRoot, accessor, path, recursive);
+ std::cout << listNar(accessor, path, recursive);
} else
listText(accessor);
}
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 6198681e7..d3d2f5b16 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -53,7 +53,6 @@ static bool haveInternet()
}
std::string programPath;
-char * * savedArgv;
struct HelpRequested { };
@@ -74,6 +73,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
.description = "Show usage information.",
+ .category = miscCategory,
.handler = {[&]() { throw HelpRequested(); }},
});
@@ -82,12 +82,13 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.shortName = 'L',
.description = "Print full build logs on standard error.",
.category = loggingCategory,
- .handler = {[&]() {setLogFormat(LogFormat::barWithLogs); }},
+ .handler = {[&]() { logger->setPrintBuildLogs(true); }},
});
addFlag({
.longName = "version",
.description = "Show version information.",
+ .category = miscCategory,
.handler = {[&]() { showVersion = true; }},
});
@@ -95,12 +96,14 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.longName = "offline",
.aliases = {"no-net"}, // FIXME: remove
.description = "Disable substituters and consider all previously downloaded files up-to-date.",
+ .category = miscCategory,
.handler = {[&]() { useNet = false; }},
});
addFlag({
.longName = "refresh",
.description = "Consider all previously downloaded files out-of-date.",
+ .category = miscCategory,
.handler = {[&]() { refresh = true; }},
});
}
@@ -187,7 +190,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
*vUtils);
auto attrs = state.buildBindings(16);
- attrs.alloc("command").mkString(toplevel.toJSON().dump());
+ attrs.alloc("toplevel").mkString(toplevel.toJSON().dump());
auto vRes = state.allocValue();
state.callFunction(*vGenerateManpage, state.allocValue()->mkAttrs(attrs), *vRes, noPos);
@@ -196,7 +199,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
if (!attr)
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
- auto markdown = state.forceString(*attr->value);
+ auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text");
RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n";
@@ -261,9 +264,16 @@ void mainWrapped(int argc, char * * argv)
}
#endif
+ Finally f([] { logger->stop(); });
+
programPath = argv[0];
auto programName = std::string(baseNameOf(programPath));
+ if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
+ programName = "build-remote";
+ argv++; argc--;
+ }
+
{
auto legacy = (*RegisterLegacyCommand::commands)[programName];
if (legacy) return legacy(argc, argv);
@@ -279,8 +289,6 @@ void mainWrapped(int argc, char * * argv)
verbosity = lvlInfo;
}
- Finally f([] { logger->stop(); });
-
NixArgs args;
if (argc == 2 && std::string(argv[1]) == "__dump-args") {
@@ -302,7 +310,7 @@ void mainWrapped(int argc, char * * argv)
b["arity"] = primOp->arity;
b["args"] = primOp->args;
b["doc"] = trim(stripIndentation(primOp->doc));
- res[(std::string) builtin.name] = std::move(b);
+ res[state.symbols[builtin.name]] = std::move(b);
}
std::cout << res.dump() << "\n";
return;
@@ -320,7 +328,7 @@ void mainWrapped(int argc, char * * argv)
std::cout << "attrs\n"; break;
}
for (auto & s : *completions)
- std::cout << s.completion << "\t" << s.description << "\n";
+ std::cout << s.completion << "\t" << trim(s.description) << "\n";
}
});
@@ -342,7 +350,10 @@ void mainWrapped(int argc, char * * argv)
if (!completions) throw;
}
- if (completions) return;
+ if (completions) {
+ args.completionHook();
+ return;
+ }
if (args.showVersion) {
printVersion(programName);
@@ -380,6 +391,9 @@ void mainWrapped(int argc, char * * argv)
settings.ttlPositiveNarInfoCache = 0;
}
+ if (args.command->second->forceImpureByDefault() && !evalSettings.pureEval.overridden) {
+ evalSettings.pureEval = false;
+ }
args.command->second->prepare();
args.command->second->run();
}
diff --git a/src/nix/make-content-addressed.cc b/src/nix/make-content-addressed.cc
index 34860c38f..d86b90fc7 100644
--- a/src/nix/make-content-addressed.cc
+++ b/src/nix/make-content-addressed.cc
@@ -2,10 +2,13 @@
#include "store-api.hh"
#include "make-content-addressed.hh"
#include "common-args.hh"
-#include "json.hh"
+
+#include <nlohmann/json.hpp>
using namespace nix;
+using nlohmann::json;
+
struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, MixJSON
{
CmdMakeContentAddressed()
@@ -25,6 +28,7 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand,
;
}
+ using StorePathsCommand::run;
void run(ref<Store> srcStore, StorePaths && storePaths) override
{
auto dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
@@ -33,13 +37,15 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand,
StorePathSet(storePaths.begin(), storePaths.end()));
if (json) {
- JSONObject jsonRoot(std::cout);
- JSONObject jsonRewrites(jsonRoot.object("rewrites"));
+ auto jsonRewrites = json::object();
for (auto & path : storePaths) {
auto i = remappings.find(path);
assert(i != remappings.end());
- jsonRewrites.attr(srcStore->printStorePath(path), srcStore->printStorePath(i->second));
+ jsonRewrites[srcStore->printStorePath(path)] = srcStore->printStorePath(i->second);
}
+ auto json = json::object();
+ json["rewrites"] = jsonRewrites;
+ std::cout << json.dump();
} else {
for (auto & path : storePaths) {
auto i = remappings.find(path);
diff --git a/src/nix/make-content-addressed.md b/src/nix/make-content-addressed.md
index 215683e6d..32eecc880 100644
--- a/src/nix/make-content-addressed.md
+++ b/src/nix/make-content-addressed.md
@@ -22,7 +22,7 @@ R""(
```console
# nix copy --to /tmp/nix --trusted-public-keys '' nixpkgs#hello
- cannot add path '/nix/store/zy9wbxwcygrwnh8n2w9qbbcr6zk87m26-libunistring-0.9.10' because it lacks a valid signature
+ cannot add path '/nix/store/zy9wbxwcygrwnh8n2w9qbbcr6zk87m26-libunistring-0.9.10' because it lacks a signature by a trusted key
```
* Create a content-addressed representation of the current NixOS
diff --git a/src/nix/nix.md b/src/nix/nix.md
index 0dacadee6..db60c59ff 100644
--- a/src/nix/nix.md
+++ b/src/nix/nix.md
@@ -115,12 +115,11 @@ the Nix store. Here are the recognised types of installables:
* **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv`
- Store derivations are store paths with extension `.drv` and are a
- low-level representation of a build-time dependency graph used
- internally by Nix. By default, if you pass a store derivation to a
- `nix` subcommand, it will operate on the *output paths* of the
- derivation. For example, `nix path-info` prints information about
- the output paths:
+ By default, if you pass a [store derivation] path to a `nix` subcommand, the command will operate on the [output path]s of the derivation.
+
+ [output path]: ../../glossary.md#gloss-output-path
+
+ For example, `nix path-info` prints information about the output paths:
```console
# nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv
@@ -146,6 +145,69 @@ For most commands, if no installable is specified, the default is `.`,
i.e. Nix will operate on the default flake output attribute of the
flake in the current directory.
+## Derivation output selection
+
+Derivations can have multiple outputs, each corresponding to a
+different store path. For instance, a package can have a `bin` output
+that contains programs, and a `dev` output that provides development
+artifacts like C/C++ header files. The outputs on which `nix` commands
+operate are determined as follows:
+
+* You can explicitly specify the desired outputs using the syntax
+ *installable*`^`*output1*`,`*...*`,`*outputN*. For example, you can
+ obtain the `dev` and `static` outputs of the `glibc` package:
+
+ ```console
+ # nix build 'nixpkgs#glibc^dev,static'
+ # ls ./result-dev/include/ ./result-static/lib/
+ …
+ ```
+
+ and likewise, using a store path to a "drv" file to specify the derivation:
+
+ ```console
+ # nix build '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev,static'
+ …
+ ```
+
+* You can also specify that *all* outputs should be used using the
+ syntax *installable*`^*`. For example, the following shows the size
+ of all outputs of the `glibc` package in the binary cache:
+
+ ```console
+ # nix path-info -S --eval-store auto --store https://cache.nixos.org 'nixpkgs#glibc^*'
+ /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123 33208200
+ /nix/store/851dp95qqiisjifi639r0zzg5l465ny4-glibc-2.33-123-bin 36142896
+ /nix/store/kdgs3q6r7xdff1p7a9hnjr43xw2404z7-glibc-2.33-123-debug 155787312
+ /nix/store/n4xa8h6pbmqmwnq0mmsz08l38abb06zc-glibc-2.33-123-static 42488328
+ /nix/store/q6580lr01jpcsqs4r5arlh4ki2c1m9rv-glibc-2.33-123-dev 44200560
+ ```
+
+ and likewise, using a store path to a "drv" file to specify the derivation:
+
+ ```console
+ # nix path-info -S '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*'
+ …
+ ```
+* If you didn't specify the desired outputs, but the derivation has an
+ attribute `meta.outputsToInstall`, Nix will use those outputs. For
+ example, since the package `nixpkgs#libxml2` has this attribute:
+
+ ```console
+ # nix eval 'nixpkgs#libxml2.meta.outputsToInstall'
+ [ "bin" "man" ]
+ ```
+
+ a command like `nix shell nixpkgs#libxml2` will provide only those
+ two outputs by default.
+
+ Note that a [store derivation] (given by its `.drv` file store path) doesn't have
+ any attributes like `meta`, and thus this case doesn't apply to it.
+
+ [store derivation]: ../../glossary.md#gloss-store-derivation
+
+* Otherwise, Nix will use all outputs of the derivation.
+
# Nix stores
Most `nix` subcommands operate on a *Nix store*.
diff --git a/src/nix/path-from-hash-part.cc b/src/nix/path-from-hash-part.cc
new file mode 100644
index 000000000..7f7cda8d3
--- /dev/null
+++ b/src/nix/path-from-hash-part.cc
@@ -0,0 +1,39 @@
+#include "command.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdPathFromHashPart : StoreCommand
+{
+ std::string hashPart;
+
+ CmdPathFromHashPart()
+ {
+ expectArgs({
+ .label = "hash-part",
+ .handler = {&hashPart},
+ });
+ }
+
+ std::string description() override
+ {
+ return "get a store path from its hash part";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "path-from-hash-part.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ if (auto storePath = store->queryPathFromHashPart(hashPart))
+ logger->cout(store->printStorePath(*storePath));
+ else
+ throw Error("there is no store path corresponding to '%s'", hashPart);
+ }
+};
+
+static auto rCmdPathFromHashPart = registerCommand2<CmdPathFromHashPart>({"store", "path-from-hash-part"});
diff --git a/src/nix/path-from-hash-part.md b/src/nix/path-from-hash-part.md
new file mode 100644
index 000000000..788e13ab6
--- /dev/null
+++ b/src/nix/path-from-hash-part.md
@@ -0,0 +1,20 @@
+R""(
+
+# Examples
+
+* Return the full store path with the given hash part:
+
+ ```console
+ # nix store path-from-hash-part --store https://cache.nixos.org/ 0i2jd68mp5g6h2sa5k9c85rb80sn8hi9
+ /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10
+ ```
+
+# Description
+
+Given the hash part of a store path (that is, the 32 characters
+following `/nix/store/`), return the full store path. This is
+primarily useful in the implementation of binary caches, where a
+request for a `.narinfo` file only supplies the hash part
+(e.g. `https://cache.nixos.org/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9.narinfo`).
+
+)""
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index d690fe594..613c5b191 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -1,12 +1,13 @@
#include "command.hh"
#include "shared.hh"
#include "store-api.hh"
-#include "json.hh"
#include "common-args.hh"
#include <algorithm>
#include <array>
+#include <nlohmann/json.hpp>
+
using namespace nix;
struct CmdPathInfo : StorePathsCommand, MixJSON
@@ -86,11 +87,10 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
pathLen = std::max(pathLen, store->printStorePath(storePath).size());
if (json) {
- JSONPlaceholder jsonRoot(std::cout);
- store->pathInfoToJSON(jsonRoot,
+ std::cout << store->pathInfoToJSON(
// FIXME: preserve order?
StorePathSet(storePaths.begin(), storePaths.end()),
- true, showClosureSize, SRI, AllowInvalid);
+ true, showClosureSize, SRI, AllowInvalid).dump();
}
else {
diff --git a/src/nix/path-info.md b/src/nix/path-info.md
index 7a1714ba4..b30898ac0 100644
--- a/src/nix/path-info.md
+++ b/src/nix/path-info.md
@@ -68,7 +68,9 @@ R""(
]
```
-* Print the path of the store derivation produced by `nixpkgs#hello`:
+* Print the path of the [store derivation] produced by `nixpkgs#hello`:
+
+ [store derivation]: ../../glossary.md#gloss-store-derivation
```console
# nix path-info --derivation nixpkgs#hello
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
index 35faa7a88..aa302efc1 100644
--- a/src/nix/prefetch.cc
+++ b/src/nix/prefetch.cc
@@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors;
// FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
- state.forceAttrs(vMirrors, noPos);
+ state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end())
throw Error("unknown mirror name '%s'", mirrorName);
- state.forceList(*mirrorList->value, noPos);
+ state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
if (mirrorList->value->listSize() < 1)
throw Error("mirror URL '%s' did not expand to anything", url);
- std::string mirror(state.forceString(*mirrorList->value->listElems()[0]));
+ std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
}
@@ -202,27 +202,29 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Value vRoot;
state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
- state->forceAttrs(v, noPos);
+ state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
/* Extract the URL. */
- auto & attr = v.attrs->need(state->symbols.create("urls"));
- state->forceList(*attr.value, noPos);
- if (attr.value->listSize() < 1)
+ auto * attr = v.attrs->get(state->symbols.create("urls"));
+ if (!attr)
+ throw Error("attribute 'urls' missing");
+ state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
+ if (attr->value->listSize() < 1)
throw Error("'urls' list is empty");
- url = state->forceString(*attr.value->listElems()[0]);
+ url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
/* Extract the hash mode. */
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
if (!attr2)
printInfo("warning: this does not look like a fetchurl call");
else
- unpack = state->forceString(*attr2->value) == "recursive";
+ unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
/* Extract the name. */
if (!name) {
auto attr3 = v.attrs->get(state->symbols.create("name"));
if (!attr3)
- name = state->forceString(*attr3->value);
+ name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
}
}
diff --git a/src/nix/profile-install.md b/src/nix/profile-install.md
index e3009491e..aed414963 100644
--- a/src/nix/profile-install.md
+++ b/src/nix/profile-install.md
@@ -20,6 +20,13 @@ R""(
# nix profile install nixpkgs/d73407e8e6002646acfdef0e39ace088bacc83da#hello
```
+* Install a specific output of a package:
+
+ ```console
+ # nix profile install nixpkgs#bash^man
+ ```
+
+
# Description
This command adds *installables* to a Nix profile.
diff --git a/src/nix/profile-list.md b/src/nix/profile-list.md
index bdab9a208..fa786162f 100644
--- a/src/nix/profile-list.md
+++ b/src/nix/profile-list.md
@@ -20,11 +20,11 @@ following fields:
* An integer that can be used to unambiguously identify the package in
invocations of `nix profile remove` and `nix profile upgrade`.
-* The original ("mutable") flake reference and output attribute path
+* The original ("unlocked") flake reference and output attribute path
used at installation time.
-* The immutable flake reference to which the mutable flake reference
- was resolved.
+* The locked flake reference to which the unlocked flake reference was
+ resolved.
* The store path(s) of the package.
diff --git a/src/nix/profile-upgrade.md b/src/nix/profile-upgrade.md
index e06e74abe..39cca428b 100644
--- a/src/nix/profile-upgrade.md
+++ b/src/nix/profile-upgrade.md
@@ -2,7 +2,7 @@ R""(
# Examples
-* Upgrade all packages that were installed using a mutable flake
+* Upgrade all packages that were installed using an unlocked flake
reference:
```console
@@ -32,9 +32,9 @@ the package was installed.
> **Warning**
>
-> This only works if you used a *mutable* flake reference at
+> This only works if you used an *unlocked* flake reference at
> installation time, e.g. `nixpkgs#hello`. It does not work if you
-> used an *immutable* flake reference
+> used a *locked* flake reference
> (e.g. `github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a#hello`),
> since in that case the "latest version" is always the same.
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index 3a32a7b55..024849e3b 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -22,13 +22,13 @@ struct ProfileElementSource
// FIXME: record original attrpath.
FlakeRef resolvedRef;
std::string attrPath;
- // FIXME: output names
+ OutputsSpec outputs;
bool operator < (const ProfileElementSource & other) const
{
return
- std::pair(originalRef.to_string(), attrPath) <
- std::pair(other.originalRef.to_string(), other.attrPath);
+ std::tuple(originalRef.to_string(), attrPath, outputs) <
+ std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
}
};
@@ -37,12 +37,12 @@ struct ProfileElement
StorePathSet storePaths;
std::optional<ProfileElementSource> source;
bool active = true;
- // FIXME: priority
+ int priority = 5;
std::string describe() const
{
if (source)
- return fmt("%s#%s", source->originalRef, source->attrPath);
+ return fmt("%s#%s%s", source->originalRef, source->attrPath, printOutputsSpec(source->outputs));
StringSet names;
for (auto & path : storePaths)
names.insert(DrvName(path.name()).name);
@@ -67,7 +67,6 @@ struct ProfileElement
ref<Store> store,
const BuiltPaths & builtPaths)
{
- // FIXME: respect meta.outputsToInstall
storePaths.clear();
for (auto & buildable : builtPaths) {
std::visit(overloaded {
@@ -99,7 +98,7 @@ struct ProfileManifest
auto version = json.value("version", 0);
std::string sUrl;
std::string sOriginalUrl;
- switch(version){
+ switch (version) {
case 1:
sUrl = "uri";
sOriginalUrl = "originalUri";
@@ -117,11 +116,15 @@ struct ProfileManifest
for (auto & p : e["storePaths"])
element.storePaths.insert(state.store->parseStorePath((std::string) p));
element.active = e["active"];
- if (e.value(sUrl,"") != "") {
- element.source = ProfileElementSource{
+ if(e.contains("priority")) {
+ element.priority = e["priority"];
+ }
+ if (e.value(sUrl, "") != "") {
+ element.source = ProfileElementSource {
parseFlakeRef(e[sOriginalUrl]),
parseFlakeRef(e[sUrl]),
- e["attrPath"]
+ e["attrPath"],
+ e["outputs"].get<OutputsSpec>()
};
}
elements.emplace_back(std::move(element));
@@ -153,10 +156,12 @@ struct ProfileManifest
nlohmann::json obj;
obj["storePaths"] = paths;
obj["active"] = element.active;
+ obj["priority"] = element.priority;
if (element.source) {
obj["originalUrl"] = element.source->originalRef.to_string();
obj["url"] = element.source->resolvedRef.to_string();
obj["attrPath"] = element.source->attrPath;
+ obj["outputs"] = element.source->outputs;
}
array.push_back(obj);
}
@@ -176,7 +181,7 @@ struct ProfileManifest
for (auto & element : elements) {
for (auto & path : element.storePaths) {
if (element.active)
- pkgs.emplace_back(store->printStorePath(path), true, 5);
+ pkgs.emplace_back(store->printStorePath(path), true, element.priority);
references.insert(path);
}
}
@@ -257,16 +262,27 @@ struct ProfileManifest
static std::map<Installable *, BuiltPaths>
builtPathsPerInstallable(
- const std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> & builtPaths)
+ const std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> & builtPaths)
{
std::map<Installable *, BuiltPaths> res;
for (auto & [installable, builtPath] : builtPaths)
- res[installable.get()].push_back(builtPath);
+ res[installable.get()].push_back(builtPath.path);
return res;
}
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
{
+ std::optional<int64_t> priority;
+
+ CmdProfileInstall() {
+ addFlag({
+ .longName = "priority",
+ .description = "The priority of the package to install.",
+ .labels = {"priority"},
+ .handler = {&priority},
+ });
+ };
+
std::string description() override
{
return "install a package into a profile";
@@ -290,16 +306,27 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
for (auto & installable : installables) {
ProfileElement element;
+
+
if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) {
// FIXME: make build() return this?
auto [attrPath, resolvedRef, drv] = installable2->toDerivation();
- element.source = ProfileElementSource{
+ element.source = ProfileElementSource {
installable2->flakeRef,
resolvedRef,
attrPath,
+ installable2->outputsSpec
};
+
+ if(drv.priority) {
+ element.priority = *drv.priority;
+ }
}
+ if(priority) { // if --priority was specified we want to override the priority of the installable
+ element.priority = *priority;
+ };
+
element.updateStorePaths(getEvalStore(), store, builtPaths[installable.get()]);
manifest.elements.push_back(std::move(element));
@@ -453,6 +480,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
getEvalState(),
FlakeRef(element.source->originalRef),
"",
+ element.source->outputs,
Strings{element.source->attrPath},
Strings{},
lockFlags);
@@ -464,10 +492,11 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->resolvedRef, resolvedRef);
- element.source = ProfileElementSource{
+ element.source = ProfileElementSource {
installable->flakeRef,
resolvedRef,
attrPath,
+ installable->outputsSpec
};
installables.push_back(installable);
@@ -523,8 +552,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
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 ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
+ element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
+ element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-",
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
}
}
diff --git a/src/nix/profile.md b/src/nix/profile.md
index 8dade051d..273e02280 100644
--- a/src/nix/profile.md
+++ b/src/nix/profile.md
@@ -11,7 +11,7 @@ them to be rolled back easily.
The default profile used by `nix profile` is `$HOME/.nix-profile`,
which, if it does not exist, is created as a symlink to
-`/nix/var/nix/profiles/per-user/default` if Nix is invoked by the
+`/nix/var/nix/profiles/default` if Nix is invoked by the
`root` user, or `/nix/var/nix/profiles/per-user/`*username* otherwise.
You can specify another profile location using `--profile` *path*.
@@ -88,8 +88,7 @@ has the following fields:
the user at the time of installation (e.g. `nixpkgs`). This is also
the flake reference that will be used by `nix profile upgrade`.
-* `uri`: The immutable flake reference to which `originalUrl`
- resolved.
+* `uri`: The locked flake reference to which `originalUrl` resolved.
* `attrPath`: The flake output attribute that provided this
package. Note that this is not necessarily the attribute that the
diff --git a/src/nix/registry.cc b/src/nix/registry.cc
index c496f94f8..b5bdfba95 100644
--- a/src/nix/registry.cc
+++ b/src/nix/registry.cc
@@ -183,14 +183,12 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand
void run(nix::ref<nix::Store> store) override
{
- if (locked.empty()) {
- locked = url;
- }
+ if (locked.empty()) locked = url;
auto registry = getRegistry();
auto ref = parseFlakeRef(url);
- auto locked_ref = parseFlakeRef(locked);
+ auto lockedRef = parseFlakeRef(locked);
registry->remove(ref.input);
- auto [tree, resolved] = locked_ref.resolve(store).input.fetch(store);
+ auto [tree, resolved] = lockedRef.resolve(store).input.fetch(store);
fetchers::Attrs extraAttrs;
if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
registry->add(ref.input, resolved, extraAttrs);
diff --git a/src/nix/registry.md b/src/nix/registry.md
index d5c9ef442..bd3575d1b 100644
--- a/src/nix/registry.md
+++ b/src/nix/registry.md
@@ -29,7 +29,7 @@ highest precedence:
can be specified using the NixOS option `nix.registry`.
* The user registry `~/.config/nix/registry.json`. This registry can
- be modified by commands such as `nix flake pin`.
+ be modified by commands such as `nix registry pin`.
* Overrides specified on the command line using the option
`--override-flake`.
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
deleted file mode 100644
index 1f9d4fb4e..000000000
--- a/src/nix/repl.cc
+++ /dev/null
@@ -1,913 +0,0 @@
-#include <iostream>
-#include <cstdlib>
-#include <cstring>
-#include <climits>
-
-#include <setjmp.h>
-
-#ifdef READLINE
-#include <readline/history.h>
-#include <readline/readline.h>
-#else
-// editline < 1.15.2 don't wrap their API for C++ usage
-// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
-// This results in linker errors due to to name-mangling of editline C symbols.
-// For compatibility with these versions, we wrap the API here
-// (wrapping multiple times on newer versions is no problem).
-extern "C" {
-#include <editline.h>
-}
-#endif
-
-#include "ansicolor.hh"
-#include "shared.hh"
-#include "eval.hh"
-#include "eval-inline.hh"
-#include "attr-path.hh"
-#include "store-api.hh"
-#include "log-store.hh"
-#include "common-eval-args.hh"
-#include "get-drvs.hh"
-#include "derivations.hh"
-#include "globals.hh"
-#include "command.hh"
-#include "finally.hh"
-#include "markdown.hh"
-
-#if HAVE_BOEHMGC
-#define GC_INCLUDE_NEW
-#include <gc/gc_cpp.h>
-#endif
-
-namespace nix {
-
-struct NixRepl
- #if HAVE_BOEHMGC
- : gc
- #endif
-{
- std::string curDir;
- std::unique_ptr<EvalState> state;
- Bindings * autoArgs;
-
- Strings loadedFiles;
-
- const static int envSize = 32768;
- StaticEnv staticEnv;
- Env * env;
- int displ;
- StringSet varNames;
-
- const Path historyFile;
-
- NixRepl(const Strings & searchPath, nix::ref<Store> store);
- ~NixRepl();
- void mainLoop(const std::vector<std::string> & files);
- StringSet completePrefix(const std::string & prefix);
- bool getLine(std::string & input, const std::string &prompt);
- StorePath getDerivationPath(Value & v);
- bool processLine(std::string line);
- void loadFile(const Path & path);
- void loadFlake(const std::string & flakeRef);
- void initEnv();
- void reloadFiles();
- void addAttrsToScope(Value & attrs);
- void addVarToScope(const Symbol & name, Value & v);
- Expr * parseString(std::string s);
- void evalString(std::string s, Value & v);
-
- typedef std::set<Value *> ValuesSeen;
- std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
- std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
-};
-
-
-std::string removeWhitespace(std::string s)
-{
- s = chomp(s);
- size_t n = s.find_first_not_of(" \n\r\t");
- if (n != std::string::npos) s = std::string(s, n);
- return s;
-}
-
-
-NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store)
- : state(std::make_unique<EvalState>(searchPath, store))
- , staticEnv(false, &state->staticBaseEnv)
- , historyFile(getDataDir() + "/nix/repl-history")
-{
- curDir = absPath(".");
-}
-
-
-NixRepl::~NixRepl()
-{
- write_history(historyFile.c_str());
-}
-
-std::string runNix(Path program, const Strings & args,
- const std::optional<std::string> & input = {})
-{
- auto subprocessEnv = getEnv();
- subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
-
- auto res = runProgram(RunOptions {
- .program = settings.nixBinDir+ "/" + program,
- .args = args,
- .environment = subprocessEnv,
- .input = input,
- });
-
- if (!statusOk(res.first))
- throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first)));
-
- return res.second;
-}
-
-static NixRepl * curRepl; // ugly
-
-static char * completionCallback(char * s, int *match) {
- auto possible = curRepl->completePrefix(s);
- if (possible.size() == 1) {
- *match = 1;
- auto *res = strdup(possible.begin()->c_str() + strlen(s));
- if (!res) throw Error("allocation failure");
- return res;
- } else if (possible.size() > 1) {
- auto checkAllHaveSameAt = [&](size_t pos) {
- auto &first = *possible.begin();
- for (auto &p : possible) {
- if (p.size() <= pos || p[pos] != first[pos])
- return false;
- }
- return true;
- };
- size_t start = strlen(s);
- size_t len = 0;
- while (checkAllHaveSameAt(start + len)) ++len;
- if (len > 0) {
- *match = 1;
- auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
- if (!res) throw Error("allocation failure");
- return res;
- }
- }
-
- *match = 0;
- return nullptr;
-}
-
-static int listPossibleCallback(char *s, char ***avp) {
- auto possible = curRepl->completePrefix(s);
-
- if (possible.size() > (INT_MAX / sizeof(char*)))
- throw Error("too many completions");
-
- int ac = 0;
- char **vp = nullptr;
-
- auto check = [&](auto *p) {
- if (!p) {
- if (vp) {
- while (--ac >= 0)
- free(vp[ac]);
- free(vp);
- }
- throw Error("allocation failure");
- }
- return p;
- };
-
- vp = check((char **)malloc(possible.size() * sizeof(char*)));
-
- for (auto & p : possible)
- vp[ac++] = check(strdup(p.c_str()));
-
- *avp = vp;
-
- return ac;
-}
-
-namespace {
- // Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
- volatile sig_atomic_t g_signal_received = 0;
-
- void sigintHandler(int signo) {
- g_signal_received = signo;
- }
-}
-
-void NixRepl::mainLoop(const std::vector<std::string> & files)
-{
- std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
- notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
-
- for (auto & i : files)
- loadedFiles.push_back(i);
-
- reloadFiles();
- if (!loadedFiles.empty()) notice("");
-
- // Allow nix-repl specific settings in .inputrc
- rl_readline_name = "nix-repl";
- createDirs(dirOf(historyFile));
-#ifndef READLINE
- el_hist_size = 1000;
-#endif
- read_history(historyFile.c_str());
- curRepl = this;
-#ifndef READLINE
- rl_set_complete_func(completionCallback);
- rl_set_list_possib_func(listPossibleCallback);
-#endif
-
- std::string input;
-
- while (true) {
- // When continuing input from previous lines, don't print a prompt, just align to the same
- // number of chars as the prompt.
- if (!getLine(input, input.empty() ? "nix-repl> " : " "))
- break;
-
- try {
- if (!removeWhitespace(input).empty() && !processLine(input)) return;
- } catch (ParseError & e) {
- if (e.msg().find("unexpected end of file") != std::string::npos) {
- // For parse errors on incomplete input, we continue waiting for the next line of
- // input without clearing the input so far.
- continue;
- } else {
- printMsg(lvlError, e.msg());
- }
- } catch (Error & e) {
- printMsg(lvlError, e.msg());
- } catch (Interrupted & e) {
- printMsg(lvlError, e.msg());
- }
-
- // We handled the current input fully, so we should clear it
- // and read brand new input.
- input.clear();
- std::cout << std::endl;
- }
-}
-
-
-bool NixRepl::getLine(std::string & input, const std::string & prompt)
-{
- struct sigaction act, old;
- sigset_t savedSignalMask, set;
-
- auto setupSignals = [&]() {
- act.sa_handler = sigintHandler;
- sigfillset(&act.sa_mask);
- act.sa_flags = 0;
- if (sigaction(SIGINT, &act, &old))
- throw SysError("installing handler for SIGINT");
-
- sigemptyset(&set);
- sigaddset(&set, SIGINT);
- if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
- throw SysError("unblocking SIGINT");
- };
- auto restoreSignals = [&]() {
- if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
- throw SysError("restoring signals");
-
- if (sigaction(SIGINT, &old, 0))
- throw SysError("restoring handler for SIGINT");
- };
-
- setupSignals();
- Finally resetTerminal([&]() { rl_deprep_terminal(); });
- char * s = readline(prompt.c_str());
- Finally doFree([&]() { free(s); });
- restoreSignals();
-
- if (g_signal_received) {
- g_signal_received = 0;
- input.clear();
- return true;
- }
-
- if (!s)
- return false;
- input += s;
- input += '\n';
- return true;
-}
-
-
-StringSet NixRepl::completePrefix(const std::string & prefix)
-{
- StringSet completions;
-
- size_t start = prefix.find_last_of(" \n\r\t(){}[]");
- std::string prev, cur;
- if (start == std::string::npos) {
- prev = "";
- cur = prefix;
- } else {
- prev = std::string(prefix, 0, start + 1);
- cur = std::string(prefix, start + 1);
- }
-
- size_t slash, dot;
-
- if ((slash = cur.rfind('/')) != std::string::npos) {
- try {
- auto dir = std::string(cur, 0, slash);
- auto prefix2 = std::string(cur, slash + 1);
- for (auto & entry : readDirectory(dir == "" ? "/" : dir)) {
- if (entry.name[0] != '.' && hasPrefix(entry.name, prefix2))
- completions.insert(prev + dir + "/" + entry.name);
- }
- } catch (Error &) {
- }
- } else if ((dot = cur.rfind('.')) == std::string::npos) {
- /* This is a variable name; look it up in the current scope. */
- StringSet::iterator i = varNames.lower_bound(cur);
- while (i != varNames.end()) {
- if (i->substr(0, cur.size()) != cur) break;
- completions.insert(prev + *i);
- i++;
- }
- } else {
- try {
- /* This is an expression that should evaluate to an
- attribute set. Evaluate it to get the names of the
- attributes. */
- auto expr = cur.substr(0, dot);
- auto cur2 = cur.substr(dot + 1);
-
- Expr * e = parseString(expr);
- Value v;
- e->eval(*state, *env, v);
- state->forceAttrs(v, noPos);
-
- for (auto & i : *v.attrs) {
- std::string name = i.name;
- if (name.substr(0, cur2.size()) != cur2) continue;
- completions.insert(prev + expr + "." + name);
- }
-
- } catch (ParseError & e) {
- // Quietly ignore parse errors.
- } catch (EvalError & e) {
- // Quietly ignore evaluation errors.
- } catch (UndefinedVarError & e) {
- // Quietly ignore undefined variable errors.
- } catch (BadURL & e) {
- // Quietly ignore BadURL flake-related errors.
- }
- }
-
- return completions;
-}
-
-
-static bool isVarName(std::string_view s)
-{
- if (s.size() == 0) return false;
- char c = s[0];
- if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
- for (auto & i : s)
- if (!((i >= 'a' && i <= 'z') ||
- (i >= 'A' && i <= 'Z') ||
- (i >= '0' && i <= '9') ||
- i == '_' || i == '-' || i == '\''))
- return false;
- return true;
-}
-
-
-StorePath NixRepl::getDerivationPath(Value & v) {
- auto drvInfo = getDerivation(*state, v, false);
- if (!drvInfo)
- throw Error("expression does not evaluate to a derivation, so I can't build it");
- auto drvPath = drvInfo->queryDrvPath();
- if (!drvPath)
- throw Error("expression did not evaluate to a valid derivation (no 'drvPath' attribute)");
- if (!state->store->isValidPath(*drvPath))
- throw Error("expression evaluated to invalid derivation '%s'", state->store->printStorePath(*drvPath));
- return *drvPath;
-}
-
-
-bool NixRepl::processLine(std::string line)
-{
- line = trim(line);
- if (line == "") return true;
-
- _isInterrupted = false;
-
- std::string command, arg;
-
- if (line[0] == ':') {
- size_t p = line.find_first_of(" \n\r\t");
- command = line.substr(0, p);
- if (p != std::string::npos) arg = removeWhitespace(line.substr(p));
- } else {
- arg = line;
- }
-
- if (command == ":?" || command == ":help") {
- // FIXME: convert to Markdown, include in the 'nix repl' manpage.
- std::cout
- << "The following commands are available:\n"
- << "\n"
- << " <expr> Evaluate and print expression\n"
- << " <x> = <expr> Bind expression to variable\n"
- << " :a <expr> Add attributes from resulting set to scope\n"
- << " :b <expr> Build derivation\n"
- << " :e <expr> Open package or function in $EDITOR\n"
- << " :i <expr> Build derivation, then install result into current profile\n"
- << " :l <path> Load Nix expression and add it to scope\n"
- << " :lf <ref> Load Nix flake and add it to scope\n"
- << " :p <expr> Evaluate and print expression recursively\n"
- << " :q Exit nix-repl\n"
- << " :r Reload all files\n"
- << " :s <expr> Build dependencies of derivation, then start nix-shell\n"
- << " :t <expr> Describe result of evaluation\n"
- << " :u <expr> Build derivation, then start nix-shell\n"
- << " :doc <expr> Show documentation of a builtin function\n"
- << " :log <expr> Show logs for a derivation\n"
- << " :st [bool] Enable, disable or toggle showing traces for errors\n";
- }
-
- else if (command == ":a" || command == ":add") {
- Value v;
- evalString(arg, v);
- addAttrsToScope(v);
- }
-
- else if (command == ":l" || command == ":load") {
- state->resetFileCache();
- loadFile(arg);
- }
-
- else if (command == ":lf" || command == ":load-flake") {
- loadFlake(arg);
- }
-
- else if (command == ":r" || command == ":reload") {
- state->resetFileCache();
- reloadFiles();
- }
-
- else if (command == ":e" || command == ":edit") {
- Value v;
- evalString(arg, v);
-
- Pos pos;
-
- if (v.type() == nPath || v.type() == nString) {
- PathSet context;
- auto filename = state->coerceToString(noPos, v, context);
- pos.file = state->symbols.create(*filename);
- } else if (v.isLambda()) {
- pos = v.lambda.fun->pos;
- } else {
- // assume it's a derivation
- pos = findPackageFilename(*state, v, arg);
- }
-
- // Open in EDITOR
- auto args = editorFor(pos);
- auto editor = args.front();
- args.pop_front();
-
- // runProgram redirects stdout to a StringSink,
- // using runProgram2 to allow editors to display their UI
- runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args });
-
- // Reload right after exiting the editor
- state->resetFileCache();
- reloadFiles();
- }
-
- else if (command == ":t") {
- Value v;
- evalString(arg, v);
- logger->cout(showType(v));
- }
-
- else if (command == ":u") {
- Value v, f, result;
- evalString(arg, v);
- evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
- state->callFunction(f, v, result, Pos());
-
- StorePath drvPath = getDerivationPath(result);
- runNix("nix-shell", {state->store->printStorePath(drvPath)});
- }
-
- else if (command == ":b" || command == ":i" || command == ":s" || command == ":log") {
- Value v;
- evalString(arg, v);
- StorePath drvPath = getDerivationPath(v);
- Path drvPathRaw = state->store->printStorePath(drvPath);
-
- if (command == ":b") {
- state->store->buildPaths({DerivedPath::Built{drvPath}});
- auto drv = state->store->readDerivation(drvPath);
- logger->cout("\nThis derivation produced the following outputs:");
- for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath))
- logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath));
- } else if (command == ":i") {
- runNix("nix-env", {"-i", drvPathRaw});
- } else if (command == ":log") {
- settings.readOnlyMode = true;
- Finally roModeReset([&]() {
- settings.readOnlyMode = false;
- });
- auto subs = getDefaultSubstituters();
-
- subs.push_front(state->store);
-
- bool foundLog = false;
- RunPager pager;
- for (auto & sub : subs) {
- auto * logSubP = dynamic_cast<LogStore *>(&*sub);
- if (!logSubP) {
- printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
- continue;
- }
- auto & logSub = *logSubP;
-
- auto log = logSub.getBuildLog(drvPath);
- if (log) {
- printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
- logger->writeToStdout(*log);
- foundLog = true;
- break;
- }
- }
- if (!foundLog) throw Error("build log of '%s' is not available", drvPathRaw);
- } else {
- runNix("nix-shell", {drvPathRaw});
- }
- }
-
- else if (command == ":p" || command == ":print") {
- Value v;
- evalString(arg, v);
- printValue(std::cout, v, 1000000000) << std::endl;
- }
-
- else if (command == ":q" || command == ":quit")
- return false;
-
- else if (command == ":doc") {
- Value v;
- evalString(arg, v);
- if (auto doc = state->getDoc(v)) {
- std::string markdown;
-
- if (!doc->args.empty() && doc->name) {
- auto args = doc->args;
- for (auto & arg : args)
- arg = "*" + arg + "*";
-
- markdown +=
- "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
- + concatStringsSep(" ", args) + "\n\n";
- }
-
- markdown += stripIndentation(doc->doc);
-
- logger->cout(trim(renderMarkdownToTerminal(markdown)));
- } else
- throw Error("value does not have documentation");
- }
-
- else if (command == ":st" || command == ":show-trace") {
- if (arg == "false" || (arg == "" && loggerSettings.showTrace)) {
- std::cout << "not showing error traces\n";
- loggerSettings.showTrace = false;
- } else if (arg == "true" || (arg == "" && !loggerSettings.showTrace)) {
- std::cout << "showing error traces\n";
- loggerSettings.showTrace = true;
- } else {
- throw Error("unexpected argument '%s' to %s", arg, command);
- };
- }
-
- else if (command != "")
- throw Error("unknown command '%1%'", command);
-
- else {
- size_t p = line.find('=');
- std::string name;
- if (p != std::string::npos &&
- p < line.size() &&
- line[p + 1] != '=' &&
- isVarName(name = removeWhitespace(line.substr(0, p))))
- {
- Expr * e = parseString(line.substr(p + 1));
- Value & v(*state->allocValue());
- v.mkThunk(env, e);
- addVarToScope(state->symbols.create(name), v);
- } else {
- Value v;
- evalString(line, v);
- printValue(std::cout, v, 1) << std::endl;
- }
- }
-
- return true;
-}
-
-
-void NixRepl::loadFile(const Path & path)
-{
- loadedFiles.remove(path);
- loadedFiles.push_back(path);
- Value v, v2;
- state->evalFile(lookupFileArg(*state, path), v);
- state->autoCallFunction(*autoArgs, v, v2);
- addAttrsToScope(v2);
-}
-
-void NixRepl::loadFlake(const std::string & flakeRefS)
-{
- if (flakeRefS.empty())
- throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
-
- auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
- if (evalSettings.pureEval && !flakeRef.input.isLocked())
- throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
-
- Value v;
-
- flake::callFlake(*state,
- flake::lockFlake(*state, flakeRef,
- flake::LockFlags {
- .updateLockFile = false,
- .useRegistries = !evalSettings.pureEval,
- .allowMutable = !evalSettings.pureEval,
- }),
- v);
- addAttrsToScope(v);
-}
-
-
-void NixRepl::initEnv()
-{
- env = &state->allocEnv(envSize);
- env->up = &state->baseEnv;
- displ = 0;
- staticEnv.vars.clear();
-
- varNames.clear();
- for (auto & i : state->staticBaseEnv.vars)
- varNames.insert(i.first);
-}
-
-
-void NixRepl::reloadFiles()
-{
- initEnv();
-
- Strings old = loadedFiles;
- loadedFiles.clear();
-
- bool first = true;
- for (auto & i : old) {
- if (!first) notice("");
- first = false;
- notice("Loading '%1%'...", i);
- loadFile(i);
- }
-}
-
-
-void NixRepl::addAttrsToScope(Value & attrs)
-{
- state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); });
- if (displ + attrs.attrs->size() >= envSize)
- throw Error("environment full; cannot add more variables");
-
- for (auto & i : *attrs.attrs) {
- staticEnv.vars.emplace_back(i.name, displ);
- env->values[displ++] = i.value;
- varNames.insert((std::string) i.name);
- }
- staticEnv.sort();
- staticEnv.deduplicate();
- notice("Added %1% variables.", attrs.attrs->size());
-}
-
-
-void NixRepl::addVarToScope(const Symbol & name, Value & v)
-{
- if (displ >= envSize)
- throw Error("environment full; cannot add more variables");
- if (auto oldVar = staticEnv.find(name); oldVar != staticEnv.vars.end())
- staticEnv.vars.erase(oldVar);
- staticEnv.vars.emplace_back(name, displ);
- staticEnv.sort();
- env->values[displ++] = &v;
- varNames.insert((std::string) name);
-}
-
-
-Expr * NixRepl::parseString(std::string s)
-{
- Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
- return e;
-}
-
-
-void NixRepl::evalString(std::string s, Value & v)
-{
- Expr * e = parseString(s);
- e->eval(*state, *env, v);
- state->forceValue(v, [&]() { return v.determinePos(noPos); });
-}
-
-
-std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
-{
- ValuesSeen seen;
- return printValue(str, v, maxDepth, seen);
-}
-
-
-std::ostream & printStringValue(std::ostream & str, const char * string) {
- str << "\"";
- for (const char * i = string; *i; i++)
- if (*i == '\"' || *i == '\\') str << "\\" << *i;
- else if (*i == '\n') str << "\\n";
- else if (*i == '\r') str << "\\r";
- else if (*i == '\t') str << "\\t";
- else str << *i;
- str << "\"";
- return str;
-}
-
-
-// FIXME: lot of cut&paste from Nix's eval.cc.
-std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
-{
- str.flush();
- checkInterrupt();
-
- state->forceValue(v, [&]() { return v.determinePos(noPos); });
-
- switch (v.type()) {
-
- case nInt:
- str << ANSI_CYAN << v.integer << ANSI_NORMAL;
- break;
-
- case nBool:
- str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
- break;
-
- case nString:
- str << ANSI_WARNING;
- printStringValue(str, v.string.s);
- str << ANSI_NORMAL;
- break;
-
- case nPath:
- str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
- break;
-
- case nNull:
- str << ANSI_CYAN "null" ANSI_NORMAL;
- break;
-
- case nAttrs: {
- seen.insert(&v);
-
- bool isDrv = state->isDerivation(v);
-
- if (isDrv) {
- str << "«derivation ";
- Bindings::iterator i = v.attrs->find(state->sDrvPath);
- PathSet context;
- if (i != v.attrs->end())
- str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context));
- else
- str << "???";
- str << "»";
- }
-
- else if (maxDepth > 0) {
- str << "{ ";
-
- typedef std::map<std::string, Value *> Sorted;
- Sorted sorted;
- for (auto & i : *v.attrs)
- sorted[i.name] = i.value;
-
- for (auto & i : sorted) {
- if (isVarName(i.first))
- str << i.first;
- else
- printStringValue(str, i.first.c_str());
- str << " = ";
- if (seen.count(i.second))
- str << "«repeated»";
- else
- try {
- printValue(str, *i.second, maxDepth - 1, seen);
- } catch (AssertionError & e) {
- str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
- }
- str << "; ";
- }
-
- str << "}";
- } else
- str << "{ ... }";
-
- break;
- }
-
- case nList:
- seen.insert(&v);
-
- str << "[ ";
- if (maxDepth > 0)
- for (auto elem : v.listItems()) {
- if (seen.count(elem))
- str << "«repeated»";
- else
- try {
- printValue(str, *elem, maxDepth - 1, seen);
- } catch (AssertionError & e) {
- str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
- }
- str << " ";
- }
- else
- str << "... ";
- str << "]";
- break;
-
- case nFunction:
- if (v.isLambda()) {
- std::ostringstream s;
- s << v.lambda.fun->pos;
- str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
- } else if (v.isPrimOp()) {
- str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
- } else if (v.isPrimOpApp()) {
- str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
- } else {
- abort();
- }
- break;
-
- case nFloat:
- str << v.fpoint;
- break;
-
- default:
- str << ANSI_RED "«unknown»" ANSI_NORMAL;
- break;
- }
-
- return str;
-}
-
-struct CmdRepl : StoreCommand, MixEvalArgs
-{
- std::vector<std::string> files;
-
- CmdRepl()
- {
- expectArgs({
- .label = "files",
- .handler = {&files},
- .completer = completePath
- });
- }
-
- std::string description() override
- {
- return "start an interactive environment for evaluating Nix expressions";
- }
-
- std::string doc() override
- {
- return
- #include "repl.md"
- ;
- }
-
- void run(ref<Store> store) override
- {
- evalSettings.pureEval = false;
- auto repl = std::make_unique<NixRepl>(searchPath, openStore());
- repl->autoArgs = getAutoArgs(*repl->state);
- repl->mainLoop(files);
- }
-};
-
-static auto rCmdRepl = registerCommand<CmdRepl>("repl");
-
-}
diff --git a/src/nix/repl.md b/src/nix/repl.md
index 9b6f2bee3..c5113be61 100644
--- a/src/nix/repl.md
+++ b/src/nix/repl.md
@@ -24,10 +24,34 @@ R""(
* Interact with Nixpkgs in the REPL:
```console
- # nix repl '<nixpkgs>'
+ # nix repl --file example.nix
+ Loading Installable ''...
+ Added 3 variables.
- Loading '<nixpkgs>'...
- Added 12428 variables.
+ # nix repl --expr '{a={b=3;c=4;};}'
+ Loading Installable ''...
+ Added 1 variables.
+
+ # nix repl --expr '{a={b=3;c=4;};}' a
+ Loading Installable ''...
+ Added 1 variables.
+
+ # nix repl --extra-experimental-features 'flakes repl-flake' nixpkgs
+ Loading Installable 'flake:nixpkgs#'...
+ Added 5 variables.
+
+ nix-repl> legacyPackages.x86_64-linux.emacs.name
+ "emacs-27.1"
+
+ nix-repl> legacyPackages.x86_64-linux.emacs.name
+ "emacs-27.1"
+
+ nix-repl> :q
+
+ # nix repl --expr 'import <nixpkgs>{}'
+
+ Loading Installable ''...
+ Added 12439 variables.
nix-repl> emacs.name
"emacs-27.1"
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 25a8fa8d3..6fca68047 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -9,6 +9,7 @@
#include "fs-accessor.hh"
#include "progress-bar.hh"
#include "eval.hh"
+#include "build/personality.hh"
#if __linux__
#include <sys/mount.h>
@@ -24,7 +25,8 @@ namespace nix {
void runProgramInStore(ref<Store> store,
const std::string & program,
- const Strings & args)
+ const Strings & args,
+ std::optional<std::string_view> system)
{
stopProgressBar();
@@ -44,14 +46,17 @@ void runProgramInStore(ref<Store> store,
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
if (store->storeDir != store2->getRealStoreDir()) {
- Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
+ Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program };
for (auto & arg : args) helperArgs.push_back(arg);
- execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+ execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
throw SysError("could not execute chroot helper");
}
+ if (system)
+ setPersonality(*system);
+
execvp(program.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to execute '%s'", program);
@@ -199,6 +204,7 @@ void chrootHelper(int argc, char * * argv)
int p = 1;
std::string storeDir = argv[p++];
std::string realStoreDir = argv[p++];
+ std::string system = argv[p++];
std::string cmd = argv[p++];
Strings args;
while (p < argc)
@@ -262,6 +268,9 @@ void chrootHelper(int argc, char * * argv)
writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
+ if (system != "")
+ setPersonality(system);
+
execvp(cmd.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to exec '%s'", cmd);
diff --git a/src/nix/run.hh b/src/nix/run.hh
index 6180a87dd..fed360158 100644
--- a/src/nix/run.hh
+++ b/src/nix/run.hh
@@ -6,6 +6,7 @@ namespace nix {
void runProgramInStore(ref<Store> store,
const std::string & program,
- const Strings & args);
+ const Strings & args,
+ std::optional<std::string_view> system = std::nullopt);
}
diff --git a/src/nix/search.cc b/src/nix/search.cc
index e96a85ea2..d2a31607d 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -5,29 +5,40 @@
#include "names.hh"
#include "get-drvs.hh"
#include "common-args.hh"
-#include "json.hh"
#include "shared.hh"
#include "eval-cache.hh"
#include "attr-path.hh"
-#include "fmt.hh"
+#include "hilite.hh"
#include <regex>
#include <fstream>
+#include <nlohmann/json.hpp>
using namespace nix;
+using json = nlohmann::json;
std::string wrap(std::string prefix, std::string s)
{
- return prefix + s + ANSI_NORMAL;
+ return concatStrings(prefix, s, ANSI_NORMAL);
}
struct CmdSearch : InstallableCommand, MixJSON
{
std::vector<std::string> res;
+ std::vector<std::string> excludeRes;
CmdSearch()
{
expectArgs("regex", &res);
+ addFlag(Flag {
+ .longName = "exclude",
+ .shortName = 'e',
+ .description = "Hide packages whose attribute path, name or description contain *regex*.",
+ .labels = {"regex"},
+ .handler = {[this](std::string s) {
+ excludeRes.push_back(s);
+ }},
+ });
}
std::string description() override
@@ -62,14 +73,20 @@ struct CmdSearch : InstallableCommand, MixJSON
res.push_back("^");
std::vector<std::regex> regexes;
+ std::vector<std::regex> excludeRegexes;
regexes.reserve(res.size());
+ excludeRegexes.reserve(excludeRes.size());
for (auto & re : res)
regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
+ for (auto & re : excludeRes)
+ excludeRegexes.emplace_back(re, std::regex::extended | std::regex::icase);
+
auto state = getEvalState();
- auto jsonOut = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
+ std::optional<nlohmann::json> jsonOut;
+ if (json) jsonOut = json::object();
uint64_t results = 0;
@@ -77,13 +94,15 @@ struct CmdSearch : InstallableCommand, MixJSON
visit = [&](eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath, bool initialRecurse)
{
+ auto attrPathS = state->symbols.resolve(attrPath);
+
Activity act(*logger, lvlInfo, actUnknown,
- fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
+ fmt("evaluating '%s'", concatStringsSep(".", attrPathS)));
try {
auto recurse = [&]()
{
for (const auto & attr : cursor.getAttrs()) {
- auto cursor2 = cursor.getAttr(attr);
+ auto cursor2 = cursor.getAttr(state->symbols[attr]);
auto attrPath2(attrPath);
attrPath2.push_back(attr);
visit(*cursor2, attrPath2, false);
@@ -91,19 +110,27 @@ struct CmdSearch : InstallableCommand, MixJSON
};
if (cursor.isDerivation()) {
- DrvName name(cursor.getAttr("name")->getString());
+ DrvName name(cursor.getAttr(state->sName)->getString());
- auto aMeta = cursor.maybeGetAttr("meta");
- auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr;
+ auto aMeta = cursor.maybeGetAttr(state->sMeta);
+ auto aDescription = aMeta ? aMeta->maybeGetAttr(state->sDescription) : nullptr;
auto description = aDescription ? aDescription->getString() : "";
std::replace(description.begin(), description.end(), '\n', ' ');
- auto attrPath2 = concatStringsSep(".", attrPath);
+ auto attrPath2 = concatStringsSep(".", attrPathS);
std::vector<std::smatch> attrPathMatches;
std::vector<std::smatch> descriptionMatches;
std::vector<std::smatch> nameMatches;
bool found = false;
+ for (auto & regex : excludeRegexes) {
+ if (
+ std::regex_search(attrPath2, regex)
+ || std::regex_search(name.name, regex)
+ || std::regex_search(description, regex))
+ return;
+ }
+
for (auto & regex : regexes) {
found = false;
auto addAll = [&found](std::sregex_iterator it, std::vector<std::smatch> & vec) {
@@ -126,41 +153,42 @@ struct CmdSearch : InstallableCommand, MixJSON
{
results++;
if (json) {
- auto jsonElem = jsonOut->object(attrPath2);
- jsonElem.attr("pname", name.name);
- jsonElem.attr("version", name.version);
- jsonElem.attr("description", description);
+ (*jsonOut)[attrPath2] = {
+ {"pname", name.name},
+ {"version", name.version},
+ {"description", description},
+ };
} else {
- auto name2 = hiliteMatches(name.name, std::move(nameMatches), ANSI_GREEN, "\e[0;2m");
+ auto name2 = hiliteMatches(name.name, nameMatches, ANSI_GREEN, "\e[0;2m");
if (results > 1) logger->cout("");
logger->cout(
"* %s%s",
- wrap("\e[0;1m", hiliteMatches(attrPath2, std::move(attrPathMatches), ANSI_GREEN, "\e[0;1m")),
+ wrap("\e[0;1m", hiliteMatches(attrPath2, attrPathMatches, ANSI_GREEN, "\e[0;1m")),
name.version != "" ? " (" + name.version + ")" : "");
if (description != "")
logger->cout(
- " %s", hiliteMatches(description, std::move(descriptionMatches), ANSI_GREEN, ANSI_NORMAL));
+ " %s", hiliteMatches(description, descriptionMatches, ANSI_GREEN, ANSI_NORMAL));
}
}
}
else if (
attrPath.size() == 0
- || (attrPath[0] == "legacyPackages" && attrPath.size() <= 2)
- || (attrPath[0] == "packages" && attrPath.size() <= 2))
+ || (attrPathS[0] == "legacyPackages" && attrPath.size() <= 2)
+ || (attrPathS[0] == "packages" && attrPath.size() <= 2))
recurse();
else if (initialRecurse)
recurse();
- else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) {
+ else if (attrPathS[0] == "legacyPackages" && attrPath.size() > 2) {
auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations);
if (attr && attr->getBool())
recurse();
}
} catch (EvalError & e) {
- if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
+ if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages"))
throw;
}
};
@@ -168,6 +196,10 @@ struct CmdSearch : InstallableCommand, MixJSON
for (auto & cursor : installable->getCursors(*state))
visit(*cursor, cursor->getAttrPath(), true);
+ if (json) {
+ std::cout << jsonOut->dump() << std::endl;
+ }
+
if (!json && !results)
throw Error("no results for the given search term(s)!");
}
diff --git a/src/nix/search.md b/src/nix/search.md
index d182788a6..5a5b5ae05 100644
--- a/src/nix/search.md
+++ b/src/nix/search.md
@@ -43,12 +43,23 @@ R""(
# nix search nixpkgs 'firefox|chromium'
```
-* Search for packages containing `git'`and either `frontend` or `gui`:
+* Search for packages containing `git` and either `frontend` or `gui`:
```console
# nix search nixpkgs git 'frontend|gui'
```
+* Search for packages containing `neovim` but hide ones containing either `gui` or `python`:
+
+ ```console
+ # nix search nixpkgs neovim -e 'python|gui'
+ ```
+ or
+
+ ```console
+ # nix search nixpkgs neovim -e 'python' -e 'gui'
+ ```
+
# Description
`nix search` searches *installable* (which must be evaluatable, e.g. a
diff --git a/src/nix/shell.md b/src/nix/shell.md
index 90b81fb2f..9fa1031f5 100644
--- a/src/nix/shell.md
+++ b/src/nix/shell.md
@@ -23,6 +23,12 @@ R""(
Hi everybody!
```
+* Run multiple commands in a shell environment:
+
+ ```console
+ # nix shell nixpkgs#gnumake -c sh -c "cd src && make"
+ ```
+
* Run GNU Hello in a chroot store:
```console
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index 331da003f..9d69c19f2 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -5,10 +5,11 @@
#include "common-args.hh"
#include "store-api.hh"
#include "archive.hh"
-#include "json.hh"
#include "derivations.hh"
+#include <nlohmann/json.hpp>
using namespace nix;
+using json = nlohmann::json;
struct CmdShowDerivation : InstallablesCommand
{
@@ -48,78 +49,64 @@ struct CmdShowDerivation : InstallablesCommand
drvPaths = std::move(closure);
}
- {
-
- JSONObject jsonRoot(std::cout, true);
+ json jsonRoot = json::object();
for (auto & drvPath : drvPaths) {
if (!drvPath.isDerivation()) continue;
- auto drvObj(jsonRoot.object(store->printStorePath(drvPath)));
+ json& drvObj = jsonRoot[store->printStorePath(drvPath)];
auto drv = store->readDerivation(drvPath);
{
- auto outputsObj(drvObj.object("outputs"));
+ json& outputsObj = drvObj["outputs"];
+ outputsObj = json::object();
for (auto & [_outputName, output] : drv.outputs) {
auto & outputName = _outputName; // work around clang bug
- auto outputObj { outputsObj.object(outputName) };
+ auto& outputObj = outputsObj[outputName];
+ outputObj = json::object();
std::visit(overloaded {
[&](const DerivationOutput::InputAddressed & doi) {
- outputObj.attr("path", store->printStorePath(doi.path));
+ outputObj["path"] = store->printStorePath(doi.path);
},
[&](const DerivationOutput::CAFixed & dof) {
- outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName)));
- outputObj.attr("hashAlgo", printMethodAlgo(dof.ca));
- outputObj.attr("hash", getContentAddressHash(dof.ca).to_string(Base16, false));
+ outputObj["path"] = store->printStorePath(dof.path(*store, drv.name, outputName));
+ outputObj["hashAlgo"] = printMethodAlgo(dof.ca);
+ outputObj["hash"] = getContentAddressHash(dof.ca).to_string(Base16, false);
// FIXME print refs?
},
[&](const DerivationOutput::CAFloating & dof) {
- outputObj.attr("hashAlgo", makeContentAddressingPrefix(dof.method) + printHashType(dof.hashType));
+ outputObj["hashAlgo"] = makeContentAddressingPrefix(dof.method) + printHashType(dof.hashType);
},
[&](const DerivationOutput::Deferred &) {},
[&](const DerivationOutput::Impure & doi) {
- outputObj.attr("hashAlgo", makeContentAddressingPrefix(doi.method) + printHashType(doi.hashType));
- outputObj.attr("impure", true);
+ outputObj["hashAlgo"] = makeContentAddressingPrefix(doi.method) + printHashType(doi.hashType);
+ outputObj["impure"] = true;
},
}, output.raw());
}
}
{
- auto inputsList(drvObj.list("inputSrcs"));
+ auto& inputsList = drvObj["inputSrcs"];
+ inputsList = json::array();
for (auto & input : drv.inputSrcs)
- inputsList.elem(store->printStorePath(input));
- }
-
- {
- auto inputDrvsObj(drvObj.object("inputDrvs"));
- for (auto & input : drv.inputDrvs) {
- auto inputList(inputDrvsObj.list(store->printStorePath(input.first)));
- for (auto & outputId : input.second)
- inputList.elem(outputId);
- }
+ inputsList.emplace_back(store->printStorePath(input));
}
- drvObj.attr("system", drv.platform);
- drvObj.attr("builder", drv.builder);
-
{
- auto argsList(drvObj.list("args"));
- for (auto & arg : drv.args)
- argsList.elem(arg);
+ auto& inputDrvsObj = drvObj["inputDrvs"];
+ inputDrvsObj = json::object();
+ for (auto & input : drv.inputDrvs)
+ inputDrvsObj[store->printStorePath(input.first)] = input.second;
}
- {
- auto envObj(drvObj.object("env"));
- for (auto & var : drv.env)
- envObj.attr(var.first, var.second);
- }
+ drvObj["system"] = drv.platform;
+ drvObj["builder"] = drv.builder;
+ drvObj["args"] = drv.args;
+ drvObj["env"] = drv.env;
}
-
- }
-
- std::cout << "\n";
+ std::cout << jsonRoot.dump(2) << std::endl;
}
};
diff --git a/src/nix/show-derivation.md b/src/nix/show-derivation.md
index aa863899c..2cd93aa62 100644
--- a/src/nix/show-derivation.md
+++ b/src/nix/show-derivation.md
@@ -2,9 +2,11 @@ R""(
# Examples
-* Show the store derivation that results from evaluating the Hello
+* Show the [store derivation] that results from evaluating the Hello
package:
+ [store derivation]: ../../glossary.md#gloss-store-derivation
+
```console
# nix show-derivation nixpkgs#hello
{
@@ -37,7 +39,7 @@ R""(
# Description
This command prints on standard output a JSON representation of the
-store derivations to which *installables* evaluate. Store derivations
+[store derivation]s to which *installables* evaluate. Store derivations
are used internally by Nix. They are store paths with extension `.drv`
that represent the build-time dependency graph to which a Nix
expression evaluates.
diff --git a/src/nix/store-copy-log.md b/src/nix/store-copy-log.md
index 19ae57079..0937250f2 100644
--- a/src/nix/store-copy-log.md
+++ b/src/nix/store-copy-log.md
@@ -18,7 +18,9 @@ R""(
(The flag `--substituters ''` avoids querying
`https://cache.nixos.org` for the log.)
-* To copy the log for a specific store derivation via SSH:
+* To copy the log for a specific [store derivation] via SSH:
+
+ [store derivation]: ../../glossary.md#gloss-store-derivation
```console
# nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 17a5a77ee..17796d6b8 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -34,7 +34,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
std::string description() override
{
- return "upgrade Nix to the latest stable version";
+ return "upgrade Nix to the stable version declared in Nixpkgs";
}
std::string doc() override
@@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
- return store->parseStorePath(state->forceString(*v2));
+ return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
}
};
diff --git a/src/nix/upgrade-nix.md b/src/nix/upgrade-nix.md
index 4d27daad9..084c80ba2 100644
--- a/src/nix/upgrade-nix.md
+++ b/src/nix/upgrade-nix.md
@@ -2,7 +2,7 @@ R""(
# Examples
-* Upgrade Nix to the latest stable version:
+* Upgrade Nix to the stable version declared in Nixpkgs:
```console
# nix upgrade-nix
@@ -16,8 +16,11 @@ R""(
# Description
-This command upgrades Nix to the latest version. By default, it
-locates the directory containing the `nix` binary in the `$PATH`
+This command upgrades Nix to the stable version declared in Nixpkgs.
+This stable version is defined in [nix-fallback-paths.nix](https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix)
+and updated manually. It may not always be the latest tagged release.
+
+By default, it locates the directory containing the `nix` binary in the `$PATH`
environment variable. If that directory is a Nix profile, it will
upgrade the `nix` package in that profile to the latest stable binary
release.
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index d132768ec..0b306cc11 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -41,7 +41,7 @@ struct CmdVerify : StorePathsCommand
addFlag({
.longName = "sigs-needed",
.shortName = 'n',
- .description = "Require that each path has at least *n* valid signatures.",
+ .description = "Require that each path is signed by at least *n* different keys.",
.labels = {"n"},
.handler = {&sigsNeeded}
});
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 1d9ab28ba..76125e5e4 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -83,20 +83,37 @@ struct CmdWhyDepends : SourceExprCommand
{
auto package = parseInstallable(store, _package);
auto packagePath = Installable::toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, package);
+
+ /* We don't need to build `dependency`. We try to get the store
+ * path if it's already known, and if not, then it's not a dependency.
+ *
+ * Why? If `package` does depends on `dependency`, then getting the
+ * store path of `package` above necessitated having the store path
+ * of `dependency`. The contrapositive is, if the store path of
+ * `dependency` is not already known at this point (i.e. it's a CA
+ * derivation which hasn't been built), then `package` did not need it
+ * to build.
+ */
auto dependency = parseInstallable(store, _dependency);
- auto dependencyPath = Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency);
- auto dependencyPathHash = dependencyPath.hashPart();
+ auto optDependencyPath = [&]() -> std::optional<StorePath> {
+ try {
+ return {Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency)};
+ } catch (MissingRealisation &) {
+ return std::nullopt;
+ }
+ }();
StorePathSet closure;
store->computeFSClosure({packagePath}, closure, false, false);
- if (!closure.count(dependencyPath)) {
- printError("'%s' does not depend on '%s'",
- store->printStorePath(packagePath),
- store->printStorePath(dependencyPath));
+ if (!optDependencyPath.has_value() || !closure.count(*optDependencyPath)) {
+ printError("'%s' does not depend on '%s'", package->what(), dependency->what());
return;
}
+ auto dependencyPath = *optDependencyPath;
+ auto dependencyPathHash = dependencyPath.hashPart();
+
stopProgressBar(); // FIXME
auto accessor = store->getFSAccessor();