aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/app.cc89
-rw-r--r--src/nix/build.cc16
-rw-r--r--src/nix/bundle.cc5
-rw-r--r--src/nix/copy.cc20
-rw-r--r--src/nix/develop.cc318
-rw-r--r--src/nix/develop.md11
-rw-r--r--src/nix/diff-closures.cc4
-rw-r--r--src/nix/edit.cc5
-rw-r--r--src/nix/flake-check.md7
-rw-r--r--src/nix/flake-show.md3
-rw-r--r--src/nix/flake.cc190
-rw-r--r--src/nix/flake.md6
-rw-r--r--src/nix/get-env.sh119
-rw-r--r--src/nix/local.mk4
-rw-r--r--src/nix/log.cc6
-rw-r--r--src/nix/main.cc59
-rw-r--r--src/nix/make-content-addressable.cc2
-rw-r--r--src/nix/path-info.cc2
-rw-r--r--src/nix/path-info.md2
-rw-r--r--src/nix/prefetch.cc24
-rw-r--r--src/nix/print-dev-env.md37
-rw-r--r--src/nix/profile-history.md4
-rw-r--r--src/nix/profile-remove.md1
-rw-r--r--src/nix/profile-rollback.md26
-rw-r--r--src/nix/profile-upgrade.md2
-rw-r--r--src/nix/profile-wipe-history.md20
-rw-r--r--src/nix/profile.cc90
-rw-r--r--src/nix/realisation.cc15
-rw-r--r--src/nix/registry-add.md7
-rw-r--r--src/nix/registry-pin.md7
-rw-r--r--src/nix/registry-remove.md6
-rw-r--r--src/nix/registry.cc87
-rw-r--r--src/nix/registry.md2
-rw-r--r--src/nix/repl.cc80
-rw-r--r--src/nix/run.cc73
-rw-r--r--src/nix/run.hh11
-rw-r--r--src/nix/search.cc1
-rw-r--r--src/nix/show-config.cc5
-rw-r--r--src/nix/sigs.cc4
-rw-r--r--src/nix/store-delete.cc2
-rw-r--r--src/nix/store-repair.cc2
-rw-r--r--src/nix/verify.cc12
-rw-r--r--src/nix/why-depends.cc4
43 files changed, 1004 insertions, 386 deletions
diff --git a/src/nix/app.cc b/src/nix/app.cc
index cf147c631..9719a65dd 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -3,34 +3,79 @@
#include "eval-inline.hh"
#include "eval-cache.hh"
#include "names.hh"
+#include "command.hh"
namespace nix {
-App Installable::toApp(EvalState & state)
+struct InstallableDerivedPath : Installable
{
- auto [cursor, attrPath] = getCursor(state);
+ ref<Store> store;
+ const DerivedPath derivedPath;
- auto type = cursor->getAttr("type")->getString();
+ InstallableDerivedPath(ref<Store> store, const DerivedPath & derivedPath)
+ : store(store)
+ , derivedPath(derivedPath)
+ {
+ }
+
+
+ std::string what() override { return derivedPath.to_string(*store); }
+
+ DerivedPaths toDerivedPaths() override
+ {
+ return {derivedPath};
+ }
- auto checkProgram = [&](const Path & program)
+ std::optional<StorePath> getStorePath() override
{
- if (!state.store->isInStore(program))
- throw Error("app program '%s' is not in the Nix store", program);
- };
+ return std::nullopt;
+ }
+};
+
+/**
+ * Return the rewrites that are needed to resolve a string whose context is
+ * included in `dependencies`
+ */
+StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies)
+{
+ StringPairs res;
+ for (auto & dep : dependencies)
+ if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep))
+ for (auto & [ outputName, outputPath ] : drvDep->outputs)
+ res.emplace(
+ downstreamPlaceholder(store, drvDep->drvPath, outputName),
+ store.printStorePath(outputPath)
+ );
+ return res;
+}
+
+/**
+ * Resolve the given string assuming the given context
+ */
+std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies)
+{
+ auto rewrites = resolveRewrites(store, dependencies);
+ return rewriteStrings(toResolve, rewrites);
+}
+
+UnresolvedApp Installable::toApp(EvalState & state)
+{
+ auto [cursor, attrPath] = getCursor(state);
+
+ auto type = cursor->getAttr("type")->getString();
if (type == "app") {
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
- checkProgram(program);
std::vector<StorePathWithOutputs> context2;
for (auto & [path, name] : context)
context2.push_back({state.store->parseStorePath(path), {name}});
- return App {
+ return UnresolvedApp{App {
.context = std::move(context2),
.program = program,
- };
+ }};
}
else if (type == "derivation") {
@@ -45,15 +90,33 @@ App Installable::toApp(EvalState & state)
? aMainProgram->getString()
: DrvName(name).name;
auto program = outPath + "/bin/" + mainProgram;
- checkProgram(program);
- return App {
+ return UnresolvedApp { App {
.context = { { drvPath, {outputName} } },
.program = program,
- };
+ }};
}
else
throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
}
+// FIXME: move to libcmd
+App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store)
+{
+ auto res = unresolved;
+
+ std::vector<std::shared_ptr<Installable>> installableContext;
+
+ for (auto & ctxElt : unresolved.context)
+ installableContext.push_back(
+ std::make_shared<InstallableDerivedPath>(store, ctxElt.toDerivedPath()));
+
+ auto builtContext = build(evalStore, store, Realise::Outputs, installableContext);
+ res.program = resolveString(*store, unresolved.program, builtContext);
+ if (!store->isInStore(res.program))
+ throw Error("app program '%s' is not in the Nix store", res.program);
+
+ return res;
+}
+
}
diff --git a/src/nix/build.cc b/src/nix/build.cc
index 03159b6cc..ce6df7df8 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -52,7 +52,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
void run(ref<Store> store) override
{
- auto buildables = build(store, dryRun ? Realise::Nothing : Realise::Outputs, installables, buildMode);
+ auto buildables = build(
+ getEvalStore(), store,
+ dryRun ? Realise::Derivation : Realise::Outputs,
+ installables, buildMode);
+
+ if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump());
if (dryRun) return;
@@ -61,14 +66,13 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
for (const auto & [_i, buildable] : enumerate(buildables)) {
auto i = _i;
std::visit(overloaded {
- [&](DerivedPathWithHints::Opaque bo) {
+ [&](BuiltPath::Opaque bo) {
std::string symlink = outLink;
if (i) symlink += fmt("-%d", i);
store2->addPermRoot(bo.path, absPath(symlink));
},
- [&](DerivedPathWithHints::Built bfd) {
- auto builtOutputs = store->queryDerivationOutputMap(bfd.drvPath);
- for (auto & output : builtOutputs) {
+ [&](BuiltPath::Built bfd) {
+ for (auto & output : bfd.outputs) {
std::string symlink = outLink;
if (i) symlink += fmt("-%d", i);
if (output.first != "out") symlink += fmt("-%s", output.first);
@@ -79,8 +83,6 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
}
updateProfile(buildables);
-
- if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump());
}
};
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 53dccc63a..aca024bca 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -59,7 +59,7 @@ struct CmdBundle : InstallableCommand
Strings getDefaultFlakeAttrPathPrefixes() override
{
- Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
+ Strings res{"apps." + settings.thisSystem.get() + "."};
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
res.push_back(s);
return res;
@@ -69,8 +69,7 @@ struct CmdBundle : InstallableCommand
{
auto evalState = getEvalState();
- auto app = installable->toApp(*evalState);
- store->buildPaths(toDerivedPaths(app.context));
+ auto app = installable->toApp(*evalState).resolve(getEvalStore(), store);
auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false };
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index f59f7c76b..197c85316 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -8,7 +8,7 @@
using namespace nix;
-struct CmdCopy : RealisedPathsCommand
+struct CmdCopy : BuiltPathsCommand
{
std::string srcUri, dstUri;
@@ -16,10 +16,10 @@ struct CmdCopy : RealisedPathsCommand
SubstituteFlag substitute = NoSubstitute;
- using RealisedPathsCommand::run;
+ using BuiltPathsCommand::run;
CmdCopy()
- : RealisedPathsCommand(true)
+ : BuiltPathsCommand(true)
{
addFlag({
.longName = "from",
@@ -75,16 +75,22 @@ struct CmdCopy : RealisedPathsCommand
if (srcUri.empty() && dstUri.empty())
throw UsageError("you must pass '--from' and/or '--to'");
- RealisedPathsCommand::run(store);
+ BuiltPathsCommand::run(store);
}
- void run(ref<Store> srcStore, std::vector<RealisedPath> paths) override
+ void run(ref<Store> srcStore, BuiltPaths && paths) override
{
ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
+ RealisedPath::Set stuffToCopy;
+
+ for (auto & builtPath : paths) {
+ auto theseRealisations = builtPath.toRealisedPaths(*srcStore);
+ stuffToCopy.insert(theseRealisations.begin(), theseRealisations.end());
+ }
+
copyPaths(
- srcStore, dstStore, RealisedPath::Set(paths.begin(), paths.end()),
- NoRepair, checkSigs, substitute);
+ *srcStore, *dstStore, stuffToCopy, NoRepair, checkSigs, substitute);
}
};
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 7cc7b85be..c20b9f272 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -7,8 +7,10 @@
#include "derivations.hh"
#include "affinity.hh"
#include "progress-bar.hh"
+#include "run.hh"
-#include <regex>
+#include <memory>
+#include <nlohmann/json.hpp>
using namespace nix;
@@ -25,94 +27,142 @@ static DevelopSettings developSettings;
static GlobalConfig::Register rDevelopSettings(&developSettings);
-struct Var
-{
- bool exported = true;
- bool associative = false;
- std::string quoted; // quoted string or array
-};
-
struct BuildEnvironment
{
- std::map<std::string, Var> env;
- std::string bashFunctions;
-};
+ struct String
+ {
+ bool exported;
+ std::string value;
-BuildEnvironment readEnvironment(const Path & path)
-{
- BuildEnvironment res;
+ bool operator == (const String & other) const
+ {
+ return exported == other.exported && value == other.value;
+ }
+ };
- std::set<std::string> exported;
+ using Array = std::vector<std::string>;
- debug("reading environment file '%s'", path);
+ using Associative = std::map<std::string, std::string>;
- auto file = readFile(path);
+ using Value = std::variant<String, Array, Associative>;
- auto pos = file.cbegin();
+ std::map<std::string, Value> vars;
+ std::map<std::string, std::string> bashFunctions;
- static std::string varNameRegex =
- R"re((?:[a-zA-Z_][a-zA-Z0-9_]*))re";
+ static BuildEnvironment fromJSON(std::string_view in)
+ {
+ BuildEnvironment res;
- static std::string simpleStringRegex =
- R"re((?:[a-zA-Z0-9_/:\.\-\+=]*))re";
+ std::set<std::string> exported;
- static std::string dquotedStringRegex =
- R"re((?:\$?"(?:[^"\\]|\\[$`"\\\n])*"))re";
+ auto json = nlohmann::json::parse(in);
- static std::string squotedStringRegex =
- R"re((?:\$?(?:'(?:[^'\\]|\\[abeEfnrtv\\'"?])*'|\\')+))re";
+ for (auto & [name, info] : json["variables"].items()) {
+ std::string type = info["type"];
+ if (type == "var" || type == "exported")
+ res.vars.insert({name, BuildEnvironment::String { .exported = type == "exported", .value = info["value"] }});
+ else if (type == "array")
+ res.vars.insert({name, (Array) info["value"]});
+ else if (type == "associative")
+ res.vars.insert({name, (Associative) info["value"]});
+ }
- static std::string indexedArrayRegex =
- R"re((?:\(( *\[[0-9]+\]="(?:[^"\\]|\\.)*")*\)))re";
+ for (auto & [name, def] : json["bashFunctions"].items()) {
+ res.bashFunctions.insert({name, def});
+ }
- static std::regex declareRegex(
- "^declare -a?x (" + varNameRegex + ")(=(" +
- dquotedStringRegex + "|" + indexedArrayRegex + "))?\n");
+ return res;
+ }
- static std::regex varRegex(
- "^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + squotedStringRegex + "|" + indexedArrayRegex + ")\n");
+ std::string toJSON() const
+ {
+ auto res = nlohmann::json::object();
+
+ auto vars2 = nlohmann::json::object();
+ for (auto & [name, value] : vars) {
+ auto info = nlohmann::json::object();
+ if (auto str = std::get_if<String>(&value)) {
+ info["type"] = str->exported ? "exported" : "var";
+ info["value"] = str->value;
+ }
+ else if (auto arr = std::get_if<Array>(&value)) {
+ info["type"] = "array";
+ info["value"] = *arr;
+ }
+ else if (auto arr = std::get_if<Associative>(&value)) {
+ info["type"] = "associative";
+ info["value"] = *arr;
+ }
+ vars2[name] = std::move(info);
+ }
+ res["variables"] = std::move(vars2);
- /* Note: we distinguish between an indexed and associative array
- using the space before the closing parenthesis. Will
- undoubtedly regret this some day. */
- static std::regex assocArrayRegex(
- "^(" + varNameRegex + ")=" + R"re((?:\(( *\[[^\]]+\]="(?:[^"\\]|\\.)*")* *\)))re" + "\n");
+ res["bashFunctions"] = bashFunctions;
- static std::regex functionRegex(
- "^" + varNameRegex + " \\(\\) *\n");
+ auto json = res.dump();
- while (pos != file.end()) {
+ assert(BuildEnvironment::fromJSON(json) == *this);
- std::smatch match;
+ return json;
+ }
- if (std::regex_search(pos, file.cend(), match, declareRegex, std::regex_constants::match_continuous)) {
- pos = match[0].second;
- exported.insert(match[1]);
+ void toBash(std::ostream & out, const std::set<std::string> & ignoreVars) const
+ {
+ for (auto & [name, value] : vars) {
+ if (!ignoreVars.count(name)) {
+ if (auto str = std::get_if<String>(&value)) {
+ out << fmt("%s=%s\n", name, shellEscape(str->value));
+ if (str->exported)
+ out << fmt("export %s\n", name);
+ }
+ else if (auto arr = std::get_if<Array>(&value)) {
+ out << "declare -a " << name << "=(";
+ for (auto & s : *arr)
+ out << shellEscape(s) << " ";
+ out << ")\n";
+ }
+ else if (auto arr = std::get_if<Associative>(&value)) {
+ out << "declare -A " << name << "=(";
+ for (auto & [n, v] : *arr)
+ out << "[" << shellEscape(n) << "]=" << shellEscape(v) << " ";
+ out << ")\n";
+ }
+ }
}
- else if (std::regex_search(pos, file.cend(), match, varRegex, std::regex_constants::match_continuous)) {
- pos = match[0].second;
- res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .quoted = match[2] }});
+ for (auto & [name, def] : bashFunctions) {
+ out << name << " ()\n{\n" << def << "}\n";
}
+ }
- else if (std::regex_search(pos, file.cend(), match, assocArrayRegex, std::regex_constants::match_continuous)) {
- pos = match[0].second;
- res.env.insert({match[1], Var { .associative = true, .quoted = match[2] }});
- }
+ static std::string getString(const Value & value)
+ {
+ if (auto str = std::get_if<String>(&value))
+ return str->value;
+ else
+ throw Error("bash variable is not a string");
+ }
- else if (std::regex_search(pos, file.cend(), match, functionRegex, std::regex_constants::match_continuous)) {
- res.bashFunctions = std::string(pos, file.cend());
- break;
+ static Array getStrings(const Value & value)
+ {
+ if (auto str = std::get_if<String>(&value))
+ return tokenizeString<Array>(str->value);
+ else if (auto arr = std::get_if<Array>(&value)) {
+ return *arr;
+ } else if (auto assoc = std::get_if<Associative>(&value)) {
+ Array assocKeys;
+ std::for_each(assoc->begin(), assoc->end(), [&](auto & n) { assocKeys.push_back(n.first); });
+ return assocKeys;
}
-
- else throw Error("shell environment '%s' has unexpected line '%s'",
- path, file.substr(pos - file.cbegin(), 60));
+ else
+ throw Error("bash variable is not a string or array");
}
- res.env.erase("__output");
-
- return res;
-}
+ bool operator == (const BuildEnvironment & other) const
+ {
+ return vars == other.vars && bashFunctions == other.bashFunctions;
+ }
+};
const static std::string getEnvSh =
#include "get-env.sh.gen.hh"
@@ -123,15 +173,15 @@ const static std::string getEnvSh =
modified derivation with the same dependencies and nearly the same
initial environment variables, that just writes the resulting
environment to a file and exits. */
-StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
+static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore, const StorePath & drvPath)
{
- auto drv = store->derivationFromPath(drvPath);
+ auto drv = evalStore->derivationFromPath(drvPath);
auto builder = baseNameOf(drv.builder);
if (builder != "bash")
throw Error("'nix develop' only works on derivations that use 'bash' as their builder");
- auto getEnvShPath = store->addTextToStore("get-env.sh", getEnvSh, {});
+ auto getEnvShPath = evalStore->addTextToStore("get-env.sh", getEnvSh, {});
drv.args = {store->printStorePath(getEnvShPath)};
@@ -144,26 +194,34 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
/* Rehash and write the derivation. FIXME: would be nice to use
'buildDerivation', but that's privileged. */
drv.name += "-env";
- for (auto & output : drv.outputs) {
- output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
- drv.env[output.first] = "";
- }
drv.inputSrcs.insert(std::move(getEnvShPath));
- Hash h = std::get<0>(hashDerivationModulo(*store, drv, true));
+ if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ for (auto & output : drv.outputs) {
+ output.second = {
+ .output = DerivationOutputDeferred{},
+ };
+ drv.env[output.first] = hashPlaceholder(output.first);
+ }
+ } else {
+ for (auto & output : drv.outputs) {
+ output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
+ drv.env[output.first] = "";
+ }
+ Hash h = std::get<0>(hashDerivationModulo(*evalStore, drv, true));
- for (auto & output : drv.outputs) {
- auto outPath = store->makeOutputPath(output.first, h, drv.name);
- output.second = { .output = DerivationOutputInputAddressed { .path = outPath } };
- drv.env[output.first] = store->printStorePath(outPath);
+ for (auto & output : drv.outputs) {
+ auto outPath = store->makeOutputPath(output.first, h, drv.name);
+ output.second = { .output = DerivationOutputInputAddressed { .path = outPath } };
+ drv.env[output.first] = store->printStorePath(outPath);
+ }
}
- auto shellDrvPath = writeDerivation(*store, drv);
+ auto shellDrvPath = writeDerivation(*evalStore, drv);
/* Build the derivation. */
- store->buildPaths({DerivedPath::Built{shellDrvPath}});
+ store->buildPaths({DerivedPath::Built{shellDrvPath}}, bmNormal, evalStore);
- for (auto & [_0, outputAndOptPath] : drv.outputsAndOptPaths(*store)) {
- auto & [_1, optPath] = outputAndOptPath;
+ for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) {
assert(optPath);
auto & outPath = *optPath;
assert(store->isValidPath(outPath));
@@ -177,18 +235,15 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
struct Common : InstallableCommand, MixProfile
{
- std::set<string> ignoreVars{
+ std::set<std::string> ignoreVars{
"BASHOPTS",
- "EUID",
"HOME", // FIXME: don't ignore in pure mode?
- "HOSTNAME",
"NIX_BUILD_TOP",
"NIX_ENFORCE_PURITY",
"NIX_LOG_FD",
+ "NIX_REMOTE",
"PPID",
- "PWD",
"SHELLOPTS",
- "SHLVL",
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
"TEMP",
"TEMPDIR",
@@ -224,22 +279,10 @@ struct Common : InstallableCommand, MixProfile
out << "nix_saved_PATH=\"$PATH\"\n";
- for (auto & i : buildEnvironment.env) {
- if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) {
- if (i.second.associative)
- out << fmt("declare -A %s=(%s)\n", i.first, i.second.quoted);
- else {
- out << fmt("%s=%s\n", i.first, i.second.quoted);
- if (i.second.exported)
- out << fmt("export %s\n", i.first);
- }
- }
- }
+ buildEnvironment.toBash(out, ignoreVars);
out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
- out << buildEnvironment.bashFunctions << "\n";
-
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
@@ -249,25 +292,25 @@ struct Common : InstallableCommand, MixProfile
auto script = out.str();
/* Substitute occurrences of output paths. */
- auto outputs = buildEnvironment.env.find("outputs");
- assert(outputs != buildEnvironment.env.end());
+ auto outputs = buildEnvironment.vars.find("outputs");
+ assert(outputs != buildEnvironment.vars.end());
// FIXME: properly unquote 'outputs'.
StringMap rewrites;
- for (auto & outputName : tokenizeString<std::vector<std::string>>(replaceStrings(outputs->second.quoted, "'", ""))) {
- auto from = buildEnvironment.env.find(outputName);
- assert(from != buildEnvironment.env.end());
+ for (auto & outputName : BuildEnvironment::getStrings(outputs->second)) {
+ auto from = buildEnvironment.vars.find(outputName);
+ assert(from != buildEnvironment.vars.end());
// FIXME: unquote
- rewrites.insert({from->second.quoted, outputsDir + "/" + outputName});
+ rewrites.insert({BuildEnvironment::getString(from->second), outputsDir + "/" + outputName});
}
/* Substitute redirects. */
for (auto & [installable_, dir_] : redirects) {
auto dir = absPath(dir_);
auto installable = parseInstallable(store, installable_);
- auto buildable = installable->toDerivedPathWithHints();
- auto doRedirect = [&](const StorePath & path)
- {
+ auto builtPaths = toStorePaths(
+ getEvalStore(), store, Realise::Nothing, OperateOn::Output, {installable});
+ for (auto & path: builtPaths) {
auto from = store->printStorePath(path);
if (script.find(from) == std::string::npos)
warn("'%s' (path '%s') is not used by this build environment", installable->what(), from);
@@ -275,16 +318,7 @@ struct Common : InstallableCommand, MixProfile
printInfo("redirecting '%s' to '%s'", from, dir);
rewrites.insert({from, dir});
}
- };
- std::visit(overloaded {
- [&](const DerivedPathWithHints::Opaque & bo) {
- doRedirect(bo.path);
- },
- [&](const DerivedPathWithHints::Built & bfd) {
- for (auto & [outputName, path] : bfd.outputs)
- if (path) doRedirect(*path);
- },
- }, buildable.raw());
+ }
}
return rewriteStrings(script, rewrites);
@@ -294,6 +328,12 @@ struct Common : InstallableCommand, MixProfile
{
return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()};
}
+ Strings getDefaultFlakeAttrPathPrefixes() override
+ {
+ auto res = SourceExprCommand::getDefaultFlakeAttrPathPrefixes();
+ res.emplace_front("devShells." + settings.thisSystem.get() + ".");
+ return res;
+ }
StorePath getShellOutPath(ref<Store> store)
{
@@ -309,7 +349,7 @@ struct Common : InstallableCommand, MixProfile
auto & drvPath = *drvs.begin();
- return getDerivationEnvironment(store, drvPath);
+ return getDerivationEnvironment(store, getEvalStore(), drvPath);
}
}
@@ -321,7 +361,9 @@ struct Common : InstallableCommand, MixProfile
updateProfile(shellOutPath);
- return {readEnvironment(strPath), strPath};
+ debug("reading environment file '%s'", strPath);
+
+ return {BuildEnvironment::fromJSON(readFile(store->toRealPath(shellOutPath))), strPath};
}
};
@@ -404,7 +446,7 @@ struct CmdDevelop : Common, MixEnvironment
if (verbosity >= lvlDebug)
script += "set -x\n";
- script += fmt("rm -f '%s'\n", rcFilePath);
+ script += fmt("command rm -f '%s'\n", rcFilePath);
if (phase) {
if (!command.empty())
@@ -423,7 +465,7 @@ struct CmdDevelop : Common, MixEnvironment
}
else {
- script += "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n";
+ script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script;
if (developSettings.bashPrompt != "")
script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n", shellEscape(developSettings.bashPrompt));
if (developSettings.bashPromptSuffix != "")
@@ -432,8 +474,6 @@ struct CmdDevelop : Common, MixEnvironment
writeFull(rcFileFd.get(), script);
- stopProgressBar();
-
setEnviron();
// prevent garbage collection until shell exits
setenv("NIX_GCROOT", gcroot.data(), 1);
@@ -443,16 +483,20 @@ struct CmdDevelop : Common, MixEnvironment
try {
auto state = getEvalState();
+ auto nixpkgsLockFlags = lockFlags;
+ nixpkgsLockFlags.inputOverrides = {};
+ nixpkgsLockFlags.inputUpdates = {};
+
auto bashInstallable = std::make_shared<InstallableFlake>(
this,
state,
installable->nixpkgsFlakeRef(),
Strings{"bashInteractive"},
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
- lockFlags);
+ nixpkgsLockFlags);
- shell = state->store->printStorePath(
- toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash";
+ shell = store->printStorePath(
+ toStorePath(getEvalStore(), store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash";
} catch (Error &) {
ignoreException();
}
@@ -462,16 +506,25 @@ struct CmdDevelop : Common, MixEnvironment
auto args = phase || !command.empty() ? Strings{std::string(baseNameOf(shell)), rcFilePath}
: Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
- restoreAffinity();
- restoreSignals();
-
- execvp(shell.c_str(), stringsToCharPtrs(args).data());
+ // Need to chdir since phases assume in flake directory
+ if (phase) {
+ // chdir if installable is a flake of type git+file or path
+ auto installableFlake = std::dynamic_pointer_cast<InstallableFlake>(installable);
+ if (installableFlake) {
+ auto sourcePath = installableFlake->getLockedFlake()->flake.resolvedRef.input.getSourcePath();
+ if (sourcePath) {
+ if (chdir(sourcePath->c_str()) == -1) {
+ throw SysError("chdir to '%s' failed", *sourcePath);
+ }
+ }
+ }
+ }
- throw SysError("executing shell '%s'", shell);
+ runProgramInStore(store, shell, args);
}
};
-struct CmdPrintDevEnv : Common
+struct CmdPrintDevEnv : Common, MixJSON
{
std::string description() override
{
@@ -493,7 +546,10 @@ struct CmdPrintDevEnv : Common
stopProgressBar();
- std::cout << makeRcScript(store, buildEnvironment);
+ logger->writeToStdout(
+ json
+ ? buildEnvironment.toJSON()
+ : makeRcScript(store, buildEnvironment));
}
};
diff --git a/src/nix/develop.md b/src/nix/develop.md
index e71d9f8aa..c86c4872b 100644
--- a/src/nix/develop.md
+++ b/src/nix/develop.md
@@ -84,11 +84,20 @@ the flake's `nixConfig` attribute.
# Flake output attributes
-If no flake output attribute is given, `nix run` tries the following
+If no flake output attribute is given, `nix develop` tries the following
flake output attributes:
* `devShell.<system>`
* `defaultPackage.<system>`
+If a flake output *name* is given, `nix develop` tries the following flake
+output attributes:
+
+* `devShells.<system>.<name>`
+
+* `packages.<system>.<name>`
+
+* `legacyPackages.<system>.<name>`
+
)""
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
index 0c7d531c1..734c41e0e 100644
--- a/src/nix/diff-closures.cc
+++ b/src/nix/diff-closures.cc
@@ -131,9 +131,9 @@ struct CmdDiffClosures : SourceExprCommand
void run(ref<Store> store) override
{
auto before = parseInstallable(store, _before);
- auto beforePath = toStorePath(store, Realise::Outputs, operateOn, before);
+ auto beforePath = toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, before);
auto after = parseInstallable(store, _after);
- auto afterPath = toStorePath(store, Realise::Outputs, operateOn, after);
+ auto afterPath = toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, after);
printClosureDiff(store, beforePath, afterPath, "");
}
};
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 6472dd27a..fc48db0d7 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -31,7 +31,7 @@ struct CmdEdit : InstallableCommand
auto [v, pos] = installable->toValue(*state);
try {
- pos = findDerivationFilename(*state, *v, installable->what());
+ pos = findPackageFilename(*state, *v, installable->what());
} catch (NoPositionInfo &) {
}
@@ -42,7 +42,8 @@ struct CmdEdit : InstallableCommand
auto args = editorFor(pos);
- restoreSignals();
+ restoreProcessContext();
+
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
std::string command;
diff --git a/src/nix/flake-check.md b/src/nix/flake-check.md
index dc079ba0c..d995d6274 100644
--- a/src/nix/flake-check.md
+++ b/src/nix/flake-check.md
@@ -22,13 +22,18 @@ This command verifies that the flake specified by flake reference
that the derivations specified by the flake's `checks` output can be
built successfully.
+If the `keep-going` option is set to `true`, Nix will keep evaluating as much
+as it can and report the errors as it encounters them. Otherwise it will stop
+at the first error.
+
# Evaluation checks
-This following flake output attributes must be derivations:
+The following flake output attributes must be derivations:
* `checks.`*system*`.`*name*
* `defaultPackage.`*system*`
* `devShell.`*system*`
+* `devShells.`*system*`.`*name*`
* `nixosConfigurations.`*name*`.config.system.build.toplevel
* `packages.`*system*`.`*name*
diff --git a/src/nix/flake-show.md b/src/nix/flake-show.md
index 1a42c44a0..e484cf47e 100644
--- a/src/nix/flake-show.md
+++ b/src/nix/flake-show.md
@@ -35,4 +35,7 @@ specified by flake reference *flake-url*. These are the top-level
attributes in the `outputs` of the flake, as well as lower-level
attributes for some standard outputs (e.g. `packages` or `checks`).
+With `--json`, the output is in a JSON representation suitable for automatic
+processing by other tools.
+
)""
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 62a413e27..7d7ada707 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -84,6 +84,7 @@ struct CmdFlakeUpdate : FlakeCommand
lockFlags.recreateLockFile = true;
lockFlags.writeLockFile = true;
+ lockFlags.applyNixConfig = true;
lockFlake();
}
@@ -114,6 +115,7 @@ struct CmdFlakeLock : FlakeCommand
settings.tarballTtl = 0;
lockFlags.writeLockFile = true;
+ lockFlags.applyNixConfig = true;
lockFlake();
}
@@ -129,8 +131,18 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
state.forceAttrs(*aOutputs->value);
- for (auto & attr : *aOutputs->value->attrs)
- callback(attr.name, *attr.value, *attr.pos);
+ auto sHydraJobs = state.symbols.create("hydraJobs");
+
+ /* Hack: ensure that hydraJobs is evaluated before anything
+ 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);
+
+ for (auto & attr : *aOutputs->value->attrs) {
+ if (attr.name != sHydraJobs)
+ callback(attr.name, *attr.value, *attr.pos);
+ }
}
struct CmdFlakeMetadata : FlakeCommand, MixJSON
@@ -267,30 +279,50 @@ struct CmdFlakeCheck : FlakeCommand
void run(nix::ref<nix::Store> store) override
{
- settings.readOnlyMode = !build;
+ if (!build) {
+ settings.readOnlyMode = true;
+ evalSettings.enableImportFromDerivation.setDefault(false);
+ }
auto state = getEvalState();
+
+ lockFlags.applyNixConfig = true;
auto flake = lockFlake();
+ bool hasErrors = false;
+ auto reportError = [&](const Error & e) {
+ try {
+ throw e;
+ } catch (Error & e) {
+ if (settings.keepGoing) {
+ ignoreException();
+ hasErrors = true;
+ }
+ else
+ throw;
+ }
+ };
+
// FIXME: rewrite to use EvalCache.
auto checkSystemName = [&](const std::string & system, const Pos & pos) {
// FIXME: what's the format of "system"?
if (system.find('-') == std::string::npos)
- throw Error("'%s' is not a valid system type, at %s", system, pos);
+ reportError(Error("'%s' is not a valid system type, at %s", system, pos));
};
- auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) -> std::optional<StorePath> {
try {
auto drvInfo = getDerivation(*state, v, false);
if (!drvInfo)
throw Error("flake attribute '%s' is not a derivation", attrPath);
// FIXME: check meta attributes
- return store->parseStorePath(drvInfo->queryDrvPath());
+ return std::make_optional(store->parseStorePath(drvInfo->queryDrvPath()));
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the derivation '%s'", attrPath));
- throw;
+ reportError(e);
}
+ return std::nullopt;
};
std::vector<DerivedPath> drvPaths;
@@ -307,7 +339,7 @@ struct CmdFlakeCheck : FlakeCommand
#endif
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the app definition '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -323,7 +355,7 @@ struct CmdFlakeCheck : FlakeCommand
// evaluate the overlay.
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the overlay '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -347,7 +379,7 @@ struct CmdFlakeCheck : FlakeCommand
// check the module.
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the NixOS module '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -362,14 +394,18 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *v.attrs) {
state->forceAttrs(*attr.value, *attr.pos);
- if (!state->isDerivation(*attr.value))
- checkHydraJobs(attrPath + "." + (std::string) attr.name,
- *attr.value, *attr.pos);
+ auto attrPath2 = attrPath + "." + (std::string) attr.name;
+ if (state->isDerivation(*attr.value)) {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("checking Hydra job '%s'", attrPath2));
+ checkDerivation(attrPath2, *attr.value, *attr.pos);
+ } else
+ checkHydraJobs(attrPath2, *attr.value, *attr.pos);
}
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the Hydra jobset '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -384,7 +420,7 @@ struct CmdFlakeCheck : FlakeCommand
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));
- throw;
+ reportError(e);
}
};
@@ -418,7 +454,7 @@ struct CmdFlakeCheck : FlakeCommand
}
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -428,12 +464,12 @@ struct CmdFlakeCheck : FlakeCommand
if (!v.isLambda())
throw Error("bundler must be a function");
if (!v.lambda.fun->formals ||
- v.lambda.fun->formals->argNames.find(state->symbols.create("program")) == v.lambda.fun->formals->argNames.end() ||
- v.lambda.fun->formals->argNames.find(state->symbols.create("system")) == v.lambda.fun->formals->argNames.end())
+ !v.lambda.fun->formals->argNames.count(state->symbols.create("program")) ||
+ !v.lambda.fun->formals->argNames.count(state->symbols.create("system")))
throw Error("bundler must take formal arguments 'program' and 'system'");
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
- throw;
+ reportError(e);
}
};
@@ -450,6 +486,8 @@ struct CmdFlakeCheck : FlakeCommand
fmt("checking flake output '%s'", name));
try {
+ evalSettings.enableImportFromDerivation.setDefault(name != "hydraJobs");
+
state->forceValue(vOutput, pos);
if (name == "checks") {
@@ -461,13 +499,13 @@ struct CmdFlakeCheck : FlakeCommand
auto drvPath = checkDerivation(
fmt("%s.%s.%s", name, attr.name, attr2.name),
*attr2.value, *attr2.pos);
- if ((std::string) attr.name == settings.thisSystem.get())
- drvPaths.push_back(DerivedPath::Built{drvPath});
+ if (drvPath && (std::string) attr.name == settings.thisSystem.get())
+ drvPaths.push_back(DerivedPath::Built{*drvPath});
}
}
}
- else if (name == "packages") {
+ else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
checkSystemName(attr.name, *attr.pos);
@@ -574,7 +612,7 @@ struct CmdFlakeCheck : FlakeCommand
} catch (Error & e) {
e.addTrace(pos, hintfmt("while checking flake output '%s'", name));
- throw;
+ reportError(e);
}
});
}
@@ -583,6 +621,8 @@ struct CmdFlakeCheck : FlakeCommand
Activity act(*logger, lvlInfo, actUnknown, "running flake checks");
store->buildPaths(drvPaths);
}
+ if (hasErrors)
+ throw Error("some errors were encountered during the evaluation");
}
};
@@ -820,12 +860,12 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
if (!dryRun && !dstUri.empty()) {
ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
- copyPaths(store, dstStore, sources);
+ copyPaths(*store, *dstStore, sources);
}
}
};
-struct CmdFlakeShow : FlakeCommand
+struct CmdFlakeShow : FlakeCommand, MixJSON
{
bool showLegacy = false;
@@ -852,51 +892,69 @@ struct CmdFlakeShow : FlakeCommand
void run(nix::ref<nix::Store> store) override
{
+ evalSettings.enableImportFromDerivation.setDefault(false);
+
auto state = getEvalState();
auto flake = std::make_shared<LockedFlake>(lockFlake());
- std::function<void(eval_cache::AttrCursor & visitor, const std::vector<Symbol> & attrPath, const std::string & headerPrefix, const std::string & nextPrefix)> visit;
-
- visit = [&](eval_cache::AttrCursor & visitor, const std::vector<Symbol> & attrPath, const std::string & headerPrefix, const std::string & nextPrefix)
+ std::function<nlohmann::json(
+ eval_cache::AttrCursor & visitor,
+ const std::vector<Symbol> & attrPath,
+ const std::string & headerPrefix,
+ const std::string & nextPrefix)> visit;
+
+ visit = [&](
+ eval_cache::AttrCursor & visitor,
+ const std::vector<Symbol> & attrPath,
+ const std::string & headerPrefix,
+ const std::string & nextPrefix)
+ -> nlohmann::json
{
+ auto j = nlohmann::json::object();
+
Activity act(*logger, lvlInfo, actUnknown,
fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
try {
auto recurse = [&]()
{
- logger->cout("%s", headerPrefix);
+ if (!json)
+ logger->cout("%s", headerPrefix);
auto attrs = visitor.getAttrs();
for (const auto & [i, attr] : enumerate(attrs)) {
bool last = i + 1 == attrs.size();
auto visitor2 = visitor.getAttr(attr);
auto attrPath2(attrPath);
attrPath2.push_back(attr);
- visit(*visitor2, attrPath2,
+ auto j2 = visit(*visitor2, attrPath2,
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr),
nextPrefix + (last ? treeNull : treeLine));
+ if (json) j.emplace(attr, std::move(j2));
}
};
auto showDerivation = [&]()
{
auto name = visitor.getAttr(state->sName)->getString();
-
- /*
- std::string description;
-
- if (auto aMeta = visitor.maybeGetAttr("meta")) {
- if (auto aDescription = aMeta->maybeGetAttr("description"))
- description = aDescription->getString();
+ if (json) {
+ std::optional<std::string> description;
+ if (auto aMeta = visitor.maybeGetAttr("meta")) {
+ if (auto aDescription = aMeta->maybeGetAttr("description"))
+ description = aDescription->getString();
+ }
+ j.emplace("type", "derivation");
+ j.emplace("name", name);
+ if (description)
+ j.emplace("description", *description);
+ } 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" :
+ "package",
+ name);
}
- */
-
- logger->cout("%s: %s '%s'",
- headerPrefix,
- attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" :
- attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" :
- attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" :
- "package",
- name);
};
if (attrPath.size() == 0
@@ -911,6 +969,7 @@ struct CmdFlakeShow : FlakeCommand
|| ((attrPath.size() == 1 || attrPath.size() == 2)
&& (attrPath[0] == "checks"
|| attrPath[0] == "packages"
+ || attrPath[0] == "devShells"
|| attrPath[0] == "apps"))
)
{
@@ -919,7 +978,7 @@ struct CmdFlakeShow : FlakeCommand
else if (
(attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell"))
- || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages"))
+ || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells"))
)
{
if (visitor.isDerivation())
@@ -939,7 +998,7 @@ struct CmdFlakeShow : FlakeCommand
if (attrPath.size() == 1)
recurse();
else if (!showLegacy)
- logger->cout("%s: " ANSI_YELLOW "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix);
+ logger->warn(fmt("%s: " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix));
else {
if (visitor.isDerivation())
showDerivation();
@@ -956,7 +1015,11 @@ struct CmdFlakeShow : FlakeCommand
auto aType = visitor.maybeGetAttr("type");
if (!aType || aType->getString() != "app")
throw EvalError("not an app definition");
- logger->cout("%s: app", headerPrefix);
+ if (json) {
+ j.emplace("type", "app");
+ } else {
+ logger->cout("%s: app", headerPrefix);
+ }
}
else if (
@@ -964,27 +1027,40 @@ struct CmdFlakeShow : FlakeCommand
(attrPath.size() == 2 && attrPath[0] == "templates"))
{
auto description = visitor.getAttr("description")->getString();
- logger->cout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
+ if (json) {
+ j.emplace("type", "template");
+ j.emplace("description", description);
+ } else {
+ logger->cout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
+ }
}
else {
- logger->cout("%s: %s",
- headerPrefix,
+ auto [type, description] =
(attrPath.size() == 1 && attrPath[0] == "overlay")
- || (attrPath.size() == 2 && attrPath[0] == "overlays") ? "Nixpkgs overlay" :
- attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? "NixOS configuration" :
- attrPath.size() == 2 && attrPath[0] == "nixosModules" ? "NixOS module" :
- ANSI_YELLOW "unknown" ANSI_NORMAL);
+ || (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() == 2 && attrPath[0] == "nixosModules" ? std::make_pair("nixos-module", "NixOS module") :
+ std::make_pair("unknown", "unknown");
+ if (json) {
+ j.emplace("type", type);
+ } else {
+ logger->cout("%s: " ANSI_WARNING "%s" ANSI_NORMAL, headerPrefix, description);
+ }
}
} catch (EvalError & e) {
if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
throw;
}
+
+ return j;
};
auto cache = openEvalCache(*state, flake);
- visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
+ auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
+ if (json)
+ logger->cout("%s", j.dump());
}
};
diff --git a/src/nix/flake.md b/src/nix/flake.md
index 0035195e5..3d273100b 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -186,8 +186,8 @@ Currently the `type` attribute can be one of the following:
attribute `url`.
In URL form, the schema must be `http://`, `https://` or `file://`
- URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`
- or `.tar.bz2`.
+ URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`,
+ `.tar.bz2` or `.tar.zst`.
* `github`: A more efficient way to fetch repositories from
GitHub. The following attributes are required:
@@ -395,7 +395,7 @@ the `nixpkgs` input of the top-level flake to be equal to the
`nixpkgs` input of the `dwarffs` input of the top-level flake:
```nix
-inputs.nixops.follows = "dwarffs/nixpkgs";
+inputs.nixpkgs.follows = "dwarffs/nixpkgs";
```
The value of the `follows` attribute is a `/`-separated sequence of
diff --git a/src/nix/get-env.sh b/src/nix/get-env.sh
index 091c0f573..42c806450 100644
--- a/src/nix/get-env.sh
+++ b/src/nix/get-env.sh
@@ -8,12 +8,123 @@ if [[ -n $stdenv ]]; then
source $stdenv/setup
fi
-for __output in $outputs; do
+# Better to use compgen, but stdenv bash doesn't have it.
+__vars="$(declare -p)"
+__functions="$(declare -F)"
+
+__dumpEnv() {
+ printf '{\n'
+
+ printf ' "bashFunctions": {\n'
+ local __first=1
+ while read __line; do
+ if ! [[ $__line =~ ^declare\ -f\ (.*) ]]; then continue; fi
+ __fun_name="${BASH_REMATCH[1]}"
+ __fun_body="$(type $__fun_name)"
+ if [[ $__fun_body =~ \{(.*)\} ]]; then
+ if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
+ __fun_body="${BASH_REMATCH[1]}"
+ printf " "
+ __escapeString "$__fun_name"
+ printf ':'
+ __escapeString "$__fun_body"
+ else
+ printf "Cannot parse definition of function '%s'.\n" "$__fun_name" >&2
+ return 1
+ fi
+ done < <(printf "%s\n" "$__functions")
+ printf '\n },\n'
+
+ printf ' "variables": {\n'
+ local __first=1
+ while read __line; do
+ if ! [[ $__line =~ ^declare\ (-[^ ])\ ([^=]*) ]]; then continue; fi
+ local type="${BASH_REMATCH[1]}"
+ local __var_name="${BASH_REMATCH[2]}"
+
+ if [[ $__var_name =~ ^BASH_ || \
+ $__var_name = _ || \
+ $__var_name = DIRSTACK || \
+ $__var_name = EUID || \
+ $__var_name = FUNCNAME || \
+ $__var_name = HISTCMD || \
+ $__var_name = HOSTNAME || \
+ $__var_name = GROUPS || \
+ $__var_name = PIPESTATUS || \
+ $__var_name = PWD || \
+ $__var_name = RANDOM || \
+ $__var_name = SHLVL || \
+ $__var_name = SECONDS \
+ ]]; then continue; fi
+
+ if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
+
+ printf " "
+ __escapeString "$__var_name"
+ printf ': {'
+
+ # FIXME: handle -i, -r, -n.
+ if [[ $type == -x ]]; then
+ printf '"type": "exported", "value": '
+ __escapeString "${!__var_name}"
+ elif [[ $type == -- ]]; then
+ printf '"type": "var", "value": '
+ __escapeString "${!__var_name}"
+ elif [[ $type == -a ]]; then
+ printf '"type": "array", "value": ['
+ local __first2=1
+ __var_name="$__var_name[@]"
+ for __i in "${!__var_name}"; do
+ if [[ -z $__first2 ]]; then printf ', '; else __first2=; fi
+ __escapeString "$__i"
+ printf ' '
+ done
+ printf ']'
+ elif [[ $type == -A ]]; then
+ printf '"type": "associative", "value": {\n'
+ local __first2=1
+ declare -n __var_name2="$__var_name"
+ for __i in "${!__var_name2[@]}"; do
+ if [[ -z $__first2 ]]; then printf ',\n'; else __first2=; fi
+ printf " "
+ __escapeString "$__i"
+ printf ": "
+ __escapeString "${__var_name2[$__i]}"
+ done
+ printf '\n }'
+ else
+ printf '"type": "unknown"'
+ fi
+
+ printf "}"
+ done < <(printf "%s\n" "$__vars")
+ printf '\n }\n}'
+}
+
+__escapeString() {
+ local __s="$1"
+ __s="${__s//\\/\\\\}"
+ __s="${__s//\"/\\\"}"
+ __s="${__s//$'\n'/\\n}"
+ __s="${__s//$'\r'/\\r}"
+ __s="${__s//$'\t'/\\t}"
+ printf '"%s"' "$__s"
+}
+
+# In case of `__structuredAttrs = true;` the list of outputs is an associative
+# array with a format like `outname => /nix/store/hash-drvname-outname`, so `__olist`
+# must contain the array's keys (hence `${!...[@]}`) in this case.
+if [ -e .attrs.sh ]; then
+ __olist="${!outputs[@]}"
+else
+ __olist=$outputs
+fi
+
+for __output in $__olist; do
if [[ -z $__done ]]; then
- export > ${!__output}
- set >> ${!__output}
+ __dumpEnv > ${!__output}
__done=1
else
- echo -n >> ${!__output}
+ echo -n >> "${!__output}"
fi
done
diff --git a/src/nix/local.mk b/src/nix/local.mk
index 83b6dd08b..e4ec7634d 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -14,7 +14,7 @@ nix_SOURCES := \
$(wildcard src/nix-instantiate/*.cc) \
$(wildcard src/nix-store/*.cc) \
-nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain -I src/libcmd
+nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain -I src/libcmd -I doc/manual
nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd
@@ -30,3 +30,5 @@ src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
src/nix/develop.cc: src/nix/get-env.sh.gen.hh
src/nix-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
+
+src/nix/main.cc: doc/manual/generate-manpage.nix.gen.hh doc/manual/utils.nix.gen.hh
diff --git a/src/nix/log.cc b/src/nix/log.cc
index 638bb5073..962c47525 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -30,15 +30,15 @@ struct CmdLog : InstallableCommand
subs.push_front(store);
- auto b = installable->toDerivedPathWithHints();
+ auto b = installable->toDerivedPath();
RunPager pager;
for (auto & sub : subs) {
auto log = std::visit(overloaded {
- [&](DerivedPathWithHints::Opaque bo) {
+ [&](DerivedPath::Opaque bo) {
return sub->getBuildLog(bo.path);
},
- [&](DerivedPathWithHints::Built bfd) {
+ [&](DerivedPath::Built bfd) {
return sub->getBuildLog(bfd.drvPath);
},
}, b.raw());
diff --git a/src/nix/main.cc b/src/nix/main.cc
index f8701ee56..8aaf08813 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -10,6 +10,7 @@
#include "filetransfer.hh"
#include "finally.hh"
#include "loggers.hh"
+#include "markdown.hh"
#include <sys/types.h>
#include <sys/socket.h>
@@ -17,10 +18,6 @@
#include <netdb.h>
#include <netinet/in.h>
-#if __linux__
-#include <sys/resource.h>
-#endif
-
#include <nlohmann/json.hpp>
extern std::string chrootHelperName;
@@ -167,9 +164,43 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
}
};
-static void showHelp(std::vector<std::string> subcommand)
+/* Render the help for the specified subcommand to stdout using
+ lowdown. */
+static void showHelp(std::vector<std::string> subcommand, MultiCommand & toplevel)
{
- showManPage(subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand)));
+ auto mdName = subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand));
+
+ evalSettings.restrictEval = false;
+ evalSettings.pureEval = false;
+ EvalState state({}, openStore("dummy://"));
+
+ auto vGenerateManpage = state.allocValue();
+ state.eval(state.parseExprFromString(
+ #include "generate-manpage.nix.gen.hh"
+ , "/"), *vGenerateManpage);
+
+ auto vUtils = state.allocValue();
+ state.cacheFile(
+ "/utils.nix", "/utils.nix",
+ state.parseExprFromString(
+ #include "utils.nix.gen.hh"
+ , "/"),
+ *vUtils);
+
+ auto vJson = state.allocValue();
+ mkString(*vJson, toplevel.toJSON().dump());
+
+ auto vRes = state.allocValue();
+ state.callFunction(*vGenerateManpage, *vJson, *vRes, noPos);
+
+ auto attr = vRes->attrs->get(state.symbols.create(mdName + ".md"));
+ if (!attr)
+ throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
+
+ auto markdown = state.forceString(*attr->value);
+
+ RunPager pager;
+ std::cout << renderMarkdownToTerminal(markdown) << "\n";
}
struct CmdHelp : Command
@@ -198,7 +229,10 @@ struct CmdHelp : Command
void run() override
{
- showHelp(subcommand);
+ assert(parent);
+ MultiCommand * toplevel = parent;
+ while (toplevel->parent) toplevel = toplevel->parent;
+ showHelp(subcommand, *toplevel);
}
};
@@ -281,7 +315,7 @@ void mainWrapped(int argc, char * * argv)
} else
break;
}
- showHelp(subcommand);
+ showHelp(subcommand, args);
return;
} catch (UsageError &) {
if (!completions) throw;
@@ -335,14 +369,7 @@ int main(int argc, char * * argv)
{
// Increase the default stack size for the evaluator and for
// libstdc++'s std::regex.
- #if __linux__
- rlim_t stackSize = 64 * 1024 * 1024;
- struct rlimit limit;
- if (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur < stackSize) {
- limit.rlim_cur = stackSize;
- setrlimit(RLIMIT_STACK, &limit);
- }
- #endif
+ nix::setStackSize(64 * 1024 * 1024);
return nix::handleExceptions(argv[0], [&]() {
nix::mainWrapped(argc, argv);
diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc
index 29f855f95..9bbfe4747 100644
--- a/src/nix/make-content-addressable.cc
+++ b/src/nix/make-content-addressable.cc
@@ -25,7 +25,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
;
}
- void run(ref<Store> store, StorePaths storePaths) override
+ void run(ref<Store> store, StorePaths && storePaths) override
{
auto paths = store->topoSortPaths(StorePathSet(storePaths.begin(), storePaths.end()));
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index 518cd5568..3743d7504 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -79,7 +79,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
std::cout << fmt("\t%6.1f%c", res, idents.at(power));
}
- void run(ref<Store> store, StorePaths storePaths) override
+ void run(ref<Store> store, StorePaths && storePaths) override
{
size_t pathLen = 0;
for (auto & storePath : storePaths)
diff --git a/src/nix/path-info.md b/src/nix/path-info.md
index 76a83e39d..7a1714ba4 100644
--- a/src/nix/path-info.md
+++ b/src/nix/path-info.md
@@ -82,7 +82,7 @@ This command shows information about the store paths produced by
By default, this command only prints the store paths. You can get
additional information by passing flags such as `--closure-size`,
---size`, `--sigs` or `--json`.
+`--size`, `--sigs` or `--json`.
> **Warning**
>
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
index 07c41587e..c13603d42 100644
--- a/src/nix/prefetch.cc
+++ b/src/nix/prefetch.cc
@@ -205,26 +205,24 @@ static int main_nix_prefetch_url(int argc, char * * argv)
state->forceAttrs(v);
/* Extract the URL. */
- auto attr = v.attrs->find(state->symbols.create("urls"));
- if (attr == v.attrs->end())
- throw Error("attribute set does not contain a 'urls' attribute");
- state->forceList(*attr->value);
- if (attr->value->listSize() < 1)
+ auto & attr = v.attrs->need(state->symbols.create("urls"));
+ state->forceList(*attr.value);
+ 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]);
/* Extract the hash mode. */
- attr = v.attrs->find(state->symbols.create("outputHashMode"));
- if (attr == v.attrs->end())
+ 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(*attr->value) == "recursive";
+ unpack = state->forceString(*attr2->value) == "recursive";
/* Extract the name. */
if (!name) {
- attr = v.attrs->find(state->symbols.create("name"));
- if (attr != v.attrs->end())
- name = state->forceString(*attr->value);
+ auto attr3 = v.attrs->get(state->symbols.create("name"));
+ if (!attr3)
+ name = state->forceString(*attr3->value);
}
}
@@ -289,8 +287,6 @@ struct CmdStorePrefetchFile : StoreCommand, MixJSON
expectArg("url", &url);
}
- Category category() override { return catUtility; }
-
std::string description() override
{
return "download a file into the Nix store";
diff --git a/src/nix/print-dev-env.md b/src/nix/print-dev-env.md
index b80252acf..2aad491de 100644
--- a/src/nix/print-dev-env.md
+++ b/src/nix/print-dev-env.md
@@ -8,12 +8,43 @@ R""(
# . <(nix print-dev-env nixpkgs#hello)
```
+* Get the build environment in JSON format:
+
+ ```console
+ # nix print-dev-env nixpkgs#hello --json
+ ```
+
+ The output will look like this:
+
+ ```json
+ {
+ "bashFunctions": {
+ "buildPhase": " \n runHook preBuild;\n...",
+ ...
+ },
+ "variables": {
+ "src": {
+ "type": "exported",
+ "value": "/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz"
+ },
+ "postUnpackHooks": {
+ "type": "array",
+ "value": ["_updateSourceDateEpochFromSourceRoot"]
+ },
+ ...
+ }
+ }
+ ```
+
# Description
-This command prints a shell script that can be sourced by `b`ash and
-that sets the environment variables and shell functions defined by the
-build process of *installable*. This allows you to get a similar build
+This command prints a shell script that can be sourced by `bash` and
+that sets the variables and shell functions defined by the build
+process of *installable*. This allows you to get a similar build
environment in your current shell rather than in a subshell (as with
`nix develop`).
+With `--json`, the output is a JSON serialisation of the variables and
+functions defined by the build process.
+
)""
diff --git a/src/nix/profile-history.md b/src/nix/profile-history.md
index d0fe40c82..f0bfe5037 100644
--- a/src/nix/profile-history.md
+++ b/src/nix/profile-history.md
@@ -6,10 +6,10 @@ R""(
```console
# nix profile history
- Version 508 -> 509:
+ Version 508 (2020-04-10):
flake:nixpkgs#legacyPackages.x86_64-linux.awscli: ∅ -> 1.17.13
- Version 509 -> 510:
+ Version 509 (2020-05-16) <- 508:
flake:nixpkgs#legacyPackages.x86_64-linux.awscli: 1.17.13 -> 1.18.211
```
diff --git a/src/nix/profile-remove.md b/src/nix/profile-remove.md
index dcf825da9..ba85441d8 100644
--- a/src/nix/profile-remove.md
+++ b/src/nix/profile-remove.md
@@ -15,6 +15,7 @@ R""(
```
* Remove all packages:
+
```console
# nix profile remove '.*'
```
diff --git a/src/nix/profile-rollback.md b/src/nix/profile-rollback.md
new file mode 100644
index 000000000..6bb75aa5e
--- /dev/null
+++ b/src/nix/profile-rollback.md
@@ -0,0 +1,26 @@
+R""(
+
+# Examples
+
+* Roll back your default profile to the previous version:
+
+ ```console
+ # nix profile rollback
+ switching profile from version 519 to 518
+ ```
+
+* Switch your default profile to version 510:
+
+ ```console
+ # nix profile rollback --to 510
+ switching profile from version 518 to 510
+ ```
+
+# Description
+
+This command switches a profile to the most recent version older
+than the currently active version, or if `--to` *N* is given, to
+version *N* of the profile. To see the available versions of a
+profile, use `nix profile history`.
+
+)""
diff --git a/src/nix/profile-upgrade.md b/src/nix/profile-upgrade.md
index 2bd5d256d..e06e74abe 100644
--- a/src/nix/profile-upgrade.md
+++ b/src/nix/profile-upgrade.md
@@ -18,7 +18,7 @@ R""(
* Upgrade a specific profile element by number:
```console
- # nix profile info
+ # nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify …
# nix profile upgrade 0
diff --git a/src/nix/profile-wipe-history.md b/src/nix/profile-wipe-history.md
new file mode 100644
index 000000000..b4b262864
--- /dev/null
+++ b/src/nix/profile-wipe-history.md
@@ -0,0 +1,20 @@
+R""(
+
+# Examples
+
+* Delete all versions of the default profile older than 100 days:
+
+ ```console
+ # nix profile wipe-history --profile /tmp/profile --older-than 100d
+ removing profile version 515
+ removing profile version 514
+ ```
+
+# Description
+
+This command deletes non-current versions of a profile, making it
+impossible to roll back to these versions. By default, all non-current
+versions are deleted. With `--older-than` *N*`d`, all non-current
+versions older than *N* days are deleted.
+
+)""
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index f254c620c..bd5042d8f 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -12,6 +12,7 @@
#include <nlohmann/json.hpp>
#include <regex>
+#include <iomanip>
using namespace nix;
@@ -262,17 +263,17 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
manifest.elements.emplace_back(std::move(element));
} else {
- auto buildables = build(store, Realise::Outputs, {installable}, bmNormal);
+ auto buildables = build(getEvalStore(), store, Realise::Outputs, {installable}, bmNormal);
for (auto & buildable : buildables) {
ProfileElement element;
std::visit(overloaded {
- [&](DerivedPathWithHints::Opaque bo) {
+ [&](BuiltPath::Opaque bo) {
pathsToBuild.push_back(bo);
element.storePaths.insert(bo.path);
},
- [&](DerivedPathWithHints::Built bfd) {
+ [&](BuiltPath::Built bfd) {
// TODO: Why are we querying if we know the output
// names already? Is it just to figure out what the
// default one is?
@@ -435,7 +436,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
attrPath,
};
- pathsToBuild.push_back(DerivedPath::Built{drv.drvPath, {"out"}}); // FIXME
+ pathsToBuild.push_back(DerivedPath::Built{drv.drvPath, {drv.outputName}});
}
}
@@ -537,10 +538,11 @@ struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
if (!first) std::cout << "\n";
first = false;
- if (prevGen)
- std::cout << fmt("Version %d -> %d:\n", prevGen->first.number, gen.number);
- else
- std::cout << fmt("Version %d:\n", gen.number);
+ std::cout << fmt("Version %s%d" ANSI_NORMAL " (%s)%s:\n",
+ gen.number == curGen ? ANSI_GREEN : ANSI_BOLD,
+ gen.number,
+ std::put_time(std::gmtime(&gen.creationTime), "%Y-%m-%d"),
+ prevGen ? fmt(" <- %d", prevGen->first.number) : "");
ProfileManifest::printDiff(
prevGen ? prevGen->second : ProfileManifest(),
@@ -552,6 +554,76 @@ struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
}
};
+struct CmdProfileRollback : virtual StoreCommand, MixDefaultProfile, MixDryRun
+{
+ std::optional<GenerationNumber> version;
+
+ CmdProfileRollback()
+ {
+ addFlag({
+ .longName = "to",
+ .description = "The profile version to roll back to.",
+ .labels = {"version"},
+ .handler = {&version},
+ });
+ }
+
+ std::string description() override
+ {
+ return "roll back to the previous version or a specified version of a profile";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "profile-rollback.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ switchGeneration(*profile, version, dryRun);
+ }
+};
+
+struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRun
+{
+ std::optional<std::string> minAge;
+
+ CmdProfileWipeHistory()
+ {
+ addFlag({
+ .longName = "older-than",
+ .description =
+ "Delete versions older than the specified age. *age* "
+ "must be in the format *N*`d`, where *N* denotes a number "
+ "of days.",
+ .labels = {"age"},
+ .handler = {&minAge},
+ });
+ }
+
+ std::string description() override
+ {
+ return "delete non-current versions of a profile";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "profile-wipe-history.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ if (minAge)
+ deleteGenerationsOlderThan(*profile, *minAge, dryRun);
+ else
+ deleteOldGenerations(*profile, dryRun);
+ }
+};
+
struct CmdProfile : NixMultiCommand
{
CmdProfile()
@@ -562,6 +634,8 @@ struct CmdProfile : NixMultiCommand
{"list", []() { return make_ref<CmdProfileList>(); }},
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
{"history", []() { return make_ref<CmdProfileHistory>(); }},
+ {"rollback", []() { return make_ref<CmdProfileRollback>(); }},
+ {"wipe-history", []() { return make_ref<CmdProfileWipeHistory>(); }},
})
{ }
diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc
index 9ee9ccb91..dfa8ff449 100644
--- a/src/nix/realisation.cc
+++ b/src/nix/realisation.cc
@@ -28,7 +28,7 @@ struct CmdRealisation : virtual NixMultiCommand
static auto rCmdRealisation = registerCommand<CmdRealisation>("realisation");
-struct CmdRealisationInfo : RealisedPathsCommand, MixJSON
+struct CmdRealisationInfo : BuiltPathsCommand, MixJSON
{
std::string description() override
{
@@ -44,12 +44,19 @@ struct CmdRealisationInfo : RealisedPathsCommand, MixJSON
Category category() override { return catSecondary; }
- void run(ref<Store> store, std::vector<RealisedPath> paths) override
+ void run(ref<Store> store, BuiltPaths && paths) override
{
settings.requireExperimentalFeature("ca-derivations");
+ RealisedPath::Set realisations;
+
+ for (auto & builtPath : paths) {
+ auto theseRealisations = builtPath.toRealisedPaths(*store);
+ realisations.insert(theseRealisations.begin(), theseRealisations.end());
+ }
+
if (json) {
nlohmann::json res = nlohmann::json::array();
- for (auto & path : paths) {
+ for (auto & path : realisations) {
nlohmann::json currentPath;
if (auto realisation = std::get_if<Realisation>(&path.raw))
currentPath = realisation->toJSON();
@@ -61,7 +68,7 @@ struct CmdRealisationInfo : RealisedPathsCommand, MixJSON
std::cout << res.dump();
}
else {
- for (auto & path : paths) {
+ for (auto & path : realisations) {
if (auto realisation = std::get_if<Realisation>(&path.raw)) {
std::cout <<
realisation->id.to_string() << " " <<
diff --git a/src/nix/registry-add.md b/src/nix/registry-add.md
index 80a31996a..a947fa0b3 100644
--- a/src/nix/registry-add.md
+++ b/src/nix/registry-add.md
@@ -21,6 +21,13 @@ R""(
# nix registry add nixpkgs/nixos-20.03 ~/Dev/nixpkgs
```
+* Add `nixpkgs` pointing to `github:nixos/nixpkgs` to your custom flake
+ registry:
+
+ ```console
+ nix registry add --registry ./custom-flake-registry.json nixpkgs github:nixos/nixpkgs
+ ```
+
# Description
This command adds an entry to the user registry that maps flake
diff --git a/src/nix/registry-pin.md b/src/nix/registry-pin.md
index 6e97e003e..ebc0e3eff 100644
--- a/src/nix/registry-pin.md
+++ b/src/nix/registry-pin.md
@@ -24,6 +24,13 @@ R""(
```
+* Pin `nixpkgs` in a custom registry to its most recent Git revision:
+
+ ```console
+ # nix registry pin --registry ./custom-flake-registry.json nixpkgs
+ ```
+
+
# Description
This command adds an entry to the user registry that maps flake
diff --git a/src/nix/registry-remove.md b/src/nix/registry-remove.md
index 4c0eb4947..eecd4c6e7 100644
--- a/src/nix/registry-remove.md
+++ b/src/nix/registry-remove.md
@@ -8,6 +8,12 @@ R""(
# nix registry remove nixpkgs
```
+* Remove the entry `nixpkgs` from a custom registry:
+
+ ```console
+ # nix registry remove --registry ./custom-flake-registry.json nixpkgs
+ ```
+
# Description
This command removes from the user registry any entry for flake
diff --git a/src/nix/registry.cc b/src/nix/registry.cc
index f9719600f..6a92576c7 100644
--- a/src/nix/registry.cc
+++ b/src/nix/registry.cc
@@ -10,6 +10,46 @@
using namespace nix;
using namespace nix::flake;
+
+class RegistryCommand : virtual Args
+{
+ std::string registry_path;
+
+ std::shared_ptr<fetchers::Registry> registry;
+
+public:
+
+ RegistryCommand()
+ {
+ addFlag({
+ .longName = "registry",
+ .description = "The registry to operate on.",
+ .labels = {"registry"},
+ .handler = {&registry_path},
+ });
+ }
+
+ std::shared_ptr<fetchers::Registry> getRegistry()
+ {
+ if (registry) return registry;
+ if (registry_path.empty()) {
+ registry = fetchers::getUserRegistry();
+ } else {
+ registry = fetchers::getCustomRegistry(registry_path);
+ }
+ return registry;
+ }
+
+ Path getRegistryPath()
+ {
+ if (registry_path.empty()) {
+ return fetchers::getUserRegistryPath();
+ } else {
+ return registry_path;
+ }
+ }
+};
+
struct CmdRegistryList : StoreCommand
{
std::string description() override
@@ -45,7 +85,7 @@ struct CmdRegistryList : StoreCommand
}
};
-struct CmdRegistryAdd : MixEvalArgs, Command
+struct CmdRegistryAdd : MixEvalArgs, Command, RegistryCommand
{
std::string fromUrl, toUrl;
@@ -71,16 +111,16 @@ struct CmdRegistryAdd : MixEvalArgs, Command
{
auto fromRef = parseFlakeRef(fromUrl);
auto toRef = parseFlakeRef(toUrl);
+ auto registry = getRegistry();
fetchers::Attrs extraAttrs;
if (toRef.subdir != "") extraAttrs["dir"] = toRef.subdir;
- auto userRegistry = fetchers::getUserRegistry();
- userRegistry->remove(fromRef.input);
- userRegistry->add(fromRef.input, toRef.input, extraAttrs);
- userRegistry->write(fetchers::getUserRegistryPath());
+ registry->remove(fromRef.input);
+ registry->add(fromRef.input, toRef.input, extraAttrs);
+ registry->write(getRegistryPath());
}
};
-struct CmdRegistryRemove : virtual Args, MixEvalArgs, Command
+struct CmdRegistryRemove : RegistryCommand, Command
{
std::string url;
@@ -103,19 +143,21 @@ struct CmdRegistryRemove : virtual Args, MixEvalArgs, Command
void run() override
{
- auto userRegistry = fetchers::getUserRegistry();
- userRegistry->remove(parseFlakeRef(url).input);
- userRegistry->write(fetchers::getUserRegistryPath());
+ auto registry = getRegistry();
+ registry->remove(parseFlakeRef(url).input);
+ registry->write(getRegistryPath());
}
};
-struct CmdRegistryPin : virtual Args, EvalCommand
+struct CmdRegistryPin : RegistryCommand, EvalCommand
{
std::string url;
+ std::string locked;
+
std::string description() override
{
- return "pin a flake to its current version in user flake registry";
+ return "pin a flake to its current version or to the current version of a flake URL";
}
std::string doc() override
@@ -128,18 +170,31 @@ struct CmdRegistryPin : virtual Args, EvalCommand
CmdRegistryPin()
{
expectArg("url", &url);
+
+ expectArgs({
+ .label = "locked",
+ .optional = true,
+ .handler = {&locked},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getStore(), prefix);
+ }}
+ });
}
void run(nix::ref<nix::Store> store) override
{
+ if (locked.empty()) {
+ locked = url;
+ }
+ auto registry = getRegistry();
auto ref = parseFlakeRef(url);
- auto userRegistry = fetchers::getUserRegistry();
- userRegistry->remove(ref.input);
- auto [tree, resolved] = ref.resolve(store).input.fetch(store);
+ auto locked_ref = parseFlakeRef(locked);
+ registry->remove(ref.input);
+ auto [tree, resolved] = locked_ref.resolve(store).input.fetch(store);
fetchers::Attrs extraAttrs;
if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
- userRegistry->add(ref.input, resolved, extraAttrs);
- userRegistry->write(fetchers::getUserRegistryPath());
+ registry->add(ref.input, resolved, extraAttrs);
+ registry->write(getRegistryPath());
}
};
diff --git a/src/nix/registry.md b/src/nix/registry.md
index 557e5795b..a1674bd2e 100644
--- a/src/nix/registry.md
+++ b/src/nix/registry.md
@@ -41,7 +41,7 @@ A registry is a JSON file with the following format:
```json
{
"version": 2,
- [
+ "flakes": [
{
"from": {
"type": "indirect",
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index bce8d31dc..c1233ab46 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -68,6 +68,7 @@ struct NixRepl
StorePath getDerivationPath(Value & v);
bool processLine(string line);
void loadFile(const Path & path);
+ void loadFlake(const std::string & flakeRef);
void initEnv();
void reloadFiles();
void addAttrsToScope(Value & attrs);
@@ -104,6 +105,25 @@ NixRepl::~NixRepl()
write_history(historyFile.c_str());
}
+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) {
@@ -343,24 +363,6 @@ StringSet NixRepl::completePrefix(string prefix)
}
-static int runProgram(const string & program, const Strings & args)
-{
- Strings args2(args);
- args2.push_front(program);
-
- Pid pid;
- pid = fork();
- if (pid == -1) throw SysError("forking");
- if (pid == 0) {
- restoreAffinity();
- execvp(program.c_str(), stringsToCharPtrs(args2).data());
- _exit(1);
- }
-
- return pid.wait();
-}
-
-
bool isVarName(const string & s)
{
if (s.size() == 0) return false;
@@ -413,9 +415,10 @@ bool NixRepl::processLine(string line)
<< " <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 the derivation in $EDITOR\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"
@@ -436,6 +439,10 @@ bool NixRepl::processLine(string line)
loadFile(arg);
}
+ else if (command == ":lf" || command == ":load-flake") {
+ loadFlake(arg);
+ }
+
else if (command == ":r" || command == ":reload") {
state->resetFileCache();
reloadFiles();
@@ -455,14 +462,14 @@ bool NixRepl::processLine(string line)
pos = v.lambda.fun->pos;
} else {
// assume it's a derivation
- pos = findDerivationFilename(*state, v, arg);
+ pos = findPackageFilename(*state, v, arg);
}
// Open in EDITOR
auto args = editorFor(pos);
auto editor = args.front();
args.pop_front();
- runProgram(editor, args);
+ runProgram(editor, true, args);
// Reload right after exiting the editor
state->resetFileCache();
@@ -481,7 +488,7 @@ bool NixRepl::processLine(string line)
state->callFunction(f, v, result, Pos());
StorePath drvPath = getDerivationPath(result);
- runProgram(settings.nixBinDir + "/nix-shell", Strings{state->store->printStorePath(drvPath)});
+ runNix("nix-shell", {state->store->printStorePath(drvPath)});
}
else if (command == ":b" || command == ":i" || command == ":s") {
@@ -494,16 +501,18 @@ bool NixRepl::processLine(string line)
/* We could do the build in this process using buildPaths(),
but doing it in a child makes it easier to recover from
problems / SIGINT. */
- if (runProgram(settings.nixBinDir + "/nix", Strings{"build", "--no-link", drvPathRaw}) == 0) {
+ try {
+ runNix("nix", {"build", "--no-link", drvPathRaw});
auto drv = state->store->readDerivation(drvPath);
std::cout << std::endl << "this derivation produced the following outputs:" << std::endl;
for (auto & i : drv.outputsAndOptPaths(*state->store))
std::cout << fmt(" %s -> %s\n", i.first, state->store->printStorePath(*i.second.second));
+ } catch (ExecError &) {
}
} else if (command == ":i") {
- runProgram(settings.nixBinDir + "/nix-env", Strings{"-i", drvPathRaw});
+ runNix("nix-env", {"-i", drvPathRaw});
} else {
- runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPathRaw});
+ runNix("nix-shell", {drvPathRaw});
}
}
@@ -575,6 +584,25 @@ void NixRepl::loadFile(const Path & path)
addAttrsToScope(v2);
}
+void NixRepl::loadFlake(const std::string & flakeRefS)
+{
+ auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
+ if (evalSettings.pureEval && !flakeRef.input.isImmutable())
+ throw Error("cannot use ':load-flake' on mutable 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()
{
@@ -679,7 +707,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break;
case nString:
- str << ANSI_YELLOW;
+ str << ANSI_WARNING;
printStringValue(str, v.string.s);
str << ANSI_NORMAL;
break;
diff --git a/src/nix/run.cc b/src/nix/run.cc
index ba60e57d8..b01fdebaa 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -1,3 +1,4 @@
+#include "run.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
@@ -20,47 +21,43 @@ using namespace nix;
std::string chrootHelperName = "__run_in_chroot";
-struct RunCommon : virtual Command
-{
-
- using Command::run;
+namespace nix {
- void runProgram(ref<Store> store,
- const std::string & program,
- const Strings & args)
- {
- stopProgressBar();
+void runProgramInStore(ref<Store> store,
+ const std::string & program,
+ const Strings & args)
+{
+ stopProgressBar();
- restoreSignals();
+ restoreProcessContext();
- restoreAffinity();
+ /* If this is a diverted store (i.e. its "logical" location
+ (typically /nix/store) differs from its "physical" location
+ (e.g. /home/eelco/nix/store), then run the command in a
+ chroot. For non-root users, this requires running it in new
+ mount and user namespaces. Unfortunately,
+ unshare(CLONE_NEWUSER) doesn't work in a multithreaded program
+ (which "nix" is), so we exec() a single-threaded helper program
+ (chrootHelper() below) to do the work. */
+ auto store2 = store.dynamic_pointer_cast<LocalStore>();
- /* If this is a diverted store (i.e. its "logical" location
- (typically /nix/store) differs from its "physical" location
- (e.g. /home/eelco/nix/store), then run the command in a
- chroot. For non-root users, this requires running it in new
- mount and user namespaces. Unfortunately,
- unshare(CLONE_NEWUSER) doesn't work in a multithreaded
- program (which "nix" is), so we exec() a single-threaded
- helper program (chrootHelper() below) to do the work. */
- auto store2 = store.dynamic_pointer_cast<LocalStore>();
+ if (store2 && store->storeDir != store2->getRealStoreDir()) {
+ Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
+ for (auto & arg : args) helperArgs.push_back(arg);
- if (store2 && store->storeDir != store2->realStoreDir) {
- Strings helperArgs = { chrootHelperName, store->storeDir, store2->realStoreDir, program };
- for (auto & arg : args) helperArgs.push_back(arg);
+ execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
- execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+ throw SysError("could not execute chroot helper");
+ }
- throw SysError("could not execute chroot helper");
- }
+ execvp(program.c_str(), stringsToCharPtrs(args).data());
- execvp(program.c_str(), stringsToCharPtrs(args).data());
+ throw SysError("unable to execute '%s'", program);
+}
- throw SysError("unable to execute '%s'", program);
- }
-};
+}
-struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
+struct CmdShell : InstallablesCommand, MixEnvironment
{
using InstallablesCommand::run;
@@ -95,7 +92,7 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
void run(ref<Store> store) override
{
- auto outPaths = toStorePaths(store, Realise::Outputs, OperateOn::Output, installables);
+ auto outPaths = toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, installables);
auto accessor = store->getFSAccessor();
@@ -127,13 +124,13 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
Strings args;
for (auto & arg : command) args.push_back(arg);
- runProgram(store, *command.begin(), args);
+ runProgramInStore(store, *command.begin(), args);
}
};
static auto rCmdShell = registerCommand<CmdShell>("shell");
-struct CmdRun : InstallableCommand, RunCommon
+struct CmdRun : InstallableCommand
{
using InstallableCommand::run;
@@ -170,7 +167,7 @@ struct CmdRun : InstallableCommand, RunCommon
Strings getDefaultFlakeAttrPathPrefixes() override
{
- Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
+ Strings res{"apps." + settings.thisSystem.get() + "."};
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
res.push_back(s);
return res;
@@ -180,14 +177,12 @@ struct CmdRun : InstallableCommand, RunCommon
{
auto state = getEvalState();
- auto app = installable->toApp(*state);
-
- state->store->buildPaths(toDerivedPaths(app.context));
+ auto app = installable->toApp(*state).resolve(getEvalStore(), store);
Strings allArgs{app.program};
for (auto & i : args) allArgs.push_back(i);
- runProgram(store, app.program, allArgs);
+ runProgramInStore(store, app.program, allArgs);
}
};
diff --git a/src/nix/run.hh b/src/nix/run.hh
new file mode 100644
index 000000000..6180a87dd
--- /dev/null
+++ b/src/nix/run.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "store-api.hh"
+
+namespace nix {
+
+void runProgramInStore(ref<Store> store,
+ const std::string & program,
+ const Strings & args);
+
+}
diff --git a/src/nix/search.cc b/src/nix/search.cc
index c52a48d4e..0d8fdd5c2 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -62,6 +62,7 @@ struct CmdSearch : InstallableCommand, MixJSON
void run(ref<Store> store) override
{
settings.readOnlyMode = true;
+ evalSettings.enableImportFromDerivation.setDefault(false);
// Empty search string should match all packages
// Use "^" here instead of ".*" due to differences in resulting highlighting
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index 91721219b..29944e748 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -22,10 +22,7 @@ struct CmdShowConfig : Command, MixJSON
// FIXME: use appropriate JSON types (bool, ints, etc).
logger->cout("%s", globalConfig.toJSON().dump());
} else {
- std::map<std::string, Config::SettingInfo> settings;
- globalConfig.getSettings(settings);
- for (auto & s : settings)
- logger->cout("%s = %s", s.first, s.second.value);
+ logger->cout("%s", globalConfig.toKeyValue());
}
}
};
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 10d0ae985..e89ad2650 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -27,7 +27,7 @@ struct CmdCopySigs : StorePathsCommand
return "copy store path signatures from substituters";
}
- void run(ref<Store> store, StorePaths storePaths) override
+ void run(ref<Store> store, StorePaths && storePaths) override
{
if (substituterUris.empty())
throw UsageError("you must specify at least one substituter using '-s'");
@@ -114,7 +114,7 @@ struct CmdSign : StorePathsCommand
return "sign store paths";
}
- void run(ref<Store> store, StorePaths storePaths) override
+ void run(ref<Store> store, StorePaths && storePaths) override
{
if (secretKeyFile.empty())
throw UsageError("you must specify a secret key file using '-k'");
diff --git a/src/nix/store-delete.cc b/src/nix/store-delete.cc
index 10245978e..e4a3cb554 100644
--- a/src/nix/store-delete.cc
+++ b/src/nix/store-delete.cc
@@ -30,7 +30,7 @@ struct CmdStoreDelete : StorePathsCommand
;
}
- void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ void run(ref<Store> store, std::vector<StorePath> && storePaths) override
{
for (auto & path : storePaths)
options.pathsToDelete.insert(path);
diff --git a/src/nix/store-repair.cc b/src/nix/store-repair.cc
index 1c7a4392e..8fcb3639a 100644
--- a/src/nix/store-repair.cc
+++ b/src/nix/store-repair.cc
@@ -17,7 +17,7 @@ struct CmdStoreRepair : StorePathsCommand
;
}
- void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ void run(ref<Store> store, std::vector<StorePath> && storePaths) override
{
for (auto & path : storePaths)
store->repairPath(path);
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index e01014440..d132768ec 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -59,7 +59,7 @@ struct CmdVerify : StorePathsCommand
;
}
- void run(ref<Store> store, StorePaths storePaths) override
+ void run(ref<Store> store, StorePaths && storePaths) override
{
std::vector<ref<Store>> substituters;
for (auto & s : substituterUris)
@@ -97,15 +97,11 @@ struct CmdVerify : StorePathsCommand
if (!noContents) {
- std::unique_ptr<AbstractHashSink> hashSink;
- if (!info->ca)
- hashSink = std::make_unique<HashSink>(info->narHash.type);
- else
- hashSink = std::make_unique<HashModuloSink>(info->narHash.type, std::string(info->path.hashPart()));
+ auto hashSink = HashSink(info->narHash.type);
- store->narFromPath(info->path, *hashSink);
+ store->narFromPath(info->path, hashSink);
- auto hash = hashSink->finish();
+ auto hash = hashSink.finish();
if (hash.first != info->narHash) {
corrupted++;
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 7a4ca5172..2f6b361bb 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -62,9 +62,9 @@ struct CmdWhyDepends : SourceExprCommand
void run(ref<Store> store) override
{
auto package = parseInstallable(store, _package);
- auto packagePath = toStorePath(store, Realise::Outputs, operateOn, package);
+ auto packagePath = toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, package);
auto dependency = parseInstallable(store, _dependency);
- auto dependencyPath = toStorePath(store, Realise::Derivation, operateOn, dependency);
+ auto dependencyPath = toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency);
auto dependencyPathHash = dependencyPath.hashPart();
StorePathSet closure;