aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-to-store.cc2
-rw-r--r--src/nix/build.cc3
-rw-r--r--src/nix/command.hh51
-rw-r--r--src/nix/eval.cc7
-rw-r--r--src/nix/flake-template.nix9
-rw-r--r--src/nix/flake.cc709
-rw-r--r--src/nix/hash.cc5
-rw-r--r--src/nix/installables.cc424
-rw-r--r--src/nix/installables.hh51
-rw-r--r--src/nix/local.mk2
-rw-r--r--src/nix/ls.cc10
-rw-r--r--src/nix/main.cc1
-rw-r--r--src/nix/profile.cc427
-rw-r--r--src/nix/progress-bar.cc13
-rw-r--r--src/nix/repl.cc1
-rw-r--r--src/nix/run.cc57
-rw-r--r--src/nix/search.cc4
-rw-r--r--src/nix/shell.cc15
-rw-r--r--src/nix/show-config.cc2
-rw-r--r--src/nix/why-depends.cc6
20 files changed, 1635 insertions, 164 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index 139db3657..ed02227db 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -50,7 +50,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
if (!dryRun)
store->addToStore(info, sink.s);
- std::cout << fmt("%s\n", store->printStorePath(info.path));
+ logger->stdout("%s", store->printStorePath(info.path));
}
};
diff --git a/src/nix/build.cc b/src/nix/build.cc
index 0b0762836..613bd9efb 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -1,3 +1,4 @@
+#include "eval.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
@@ -42,7 +43,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
},
Example{
"To make a profile point at GNU Hello:",
- "nix build --profile /tmp/profile nixpkgs.hello"
+ "nix build --profile /tmp/profile nixpkgs#hello"
},
};
}
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 2c2303208..8b910ba78 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -4,12 +4,18 @@
#include "args.hh"
#include "common-eval-args.hh"
#include "path.hh"
-#include "eval.hh"
+#include "flake/lockfile.hh"
+
+#include <optional>
namespace nix {
extern std::string programPath;
+class EvalState;
+struct Pos;
+class Store;
+
/* A command that requires a Nix store. */
struct StoreCommand : virtual Command
{
@@ -23,25 +29,36 @@ private:
std::shared_ptr<Store> _store;
};
-struct SourceExprCommand : virtual StoreCommand, MixEvalArgs
+struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
- Path file;
+ ref<EvalState> getEvalState();
- SourceExprCommand();
+ std::shared_ptr<EvalState> evalState;
+};
- /* Return a value representing the Nix expression from which we
- are installing. This is either the file specified by ‘--file’,
- or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs
- = import ...; bla = import ...; }’. */
- Value * getSourceExpr(EvalState & state);
+struct MixFlakeOptions : virtual Args
+{
+ flake::LockFlags lockFlags;
- ref<EvalState> getEvalState();
+ MixFlakeOptions();
+};
-private:
+struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
+{
+ std::optional<Path> file;
+ std::optional<std::string> expr;
- std::shared_ptr<EvalState> evalState;
+ SourceExprCommand();
- std::shared_ptr<Value> vSourceExpr;
+ std::vector<std::shared_ptr<Installable>> parseInstallables(
+ ref<Store> store, std::vector<std::string> ss);
+
+ std::shared_ptr<Installable> parseInstallable(
+ ref<Store> store, const std::string & installable);
+
+ virtual Strings getDefaultFlakeAttrPaths();
+
+ virtual Strings getDefaultFlakeAttrPathPrefixes();
};
enum RealiseMode { Build, NoBuild, DryRun };
@@ -73,14 +90,14 @@ struct InstallableCommand : virtual Args, SourceExprCommand
InstallableCommand()
{
- expectArg("installable", &_installable);
+ expectArg("installable", &_installable, true);
}
void prepare() override;
private:
- std::string _installable;
+ std::string _installable{"."};
};
/* A command that operates on zero or more store paths. */
@@ -137,10 +154,6 @@ static RegisterCommand registerCommand(const std::string & name)
return RegisterCommand(name, [](){ return make_ref<T>(); });
}
-std::shared_ptr<Installable> parseInstallable(
- SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
- bool useDefaultInstallables);
-
Buildables build(ref<Store> store, RealiseMode mode,
std::vector<std::shared_ptr<Installable>> installables);
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 6398fc58e..96ca83325 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -28,7 +28,7 @@ struct CmdEval : MixJSON, InstallableCommand
return {
Example{
"To evaluate a Nix expression given on the command line:",
- "nix eval '(1 + 2)'"
+ "nix eval --expr '1 + 2'"
},
Example{
"To evaluate a Nix expression from a file or URI:",
@@ -55,16 +55,15 @@ struct CmdEval : MixJSON, InstallableCommand
auto v = installable->toValue(*state).first;
PathSet context;
- stopProgressBar();
-
if (raw) {
+ stopProgressBar();
std::cout << state->coerceToString(noPos, *v, context);
} else if (json) {
JSONPlaceholder jsonOut(std::cout);
printValueAsJSON(*state, true, *v, jsonOut, context);
} else {
state->forceValueDeep(*v);
- std::cout << *v << "\n";
+ logger->stdout("%s", *v);
}
}
};
diff --git a/src/nix/flake-template.nix b/src/nix/flake-template.nix
new file mode 100644
index 000000000..60d7171f3
--- /dev/null
+++ b/src/nix/flake-template.nix
@@ -0,0 +1,9 @@
+{
+ description = "A flake for building Hello World";
+
+ outputs = { self, nixpkgs }: {
+
+ packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
+
+ };
+}
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
new file mode 100644
index 000000000..a8518a543
--- /dev/null
+++ b/src/nix/flake.cc
@@ -0,0 +1,709 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "flake/flake.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+#include "attr-path.hh"
+#include "fetchers.hh"
+#include "registry.hh"
+#include "json.hh"
+
+#include <nlohmann/json.hpp>
+#include <queue>
+#include <iomanip>
+
+using namespace nix;
+using namespace nix::flake;
+
+class FlakeCommand : virtual Args, public EvalCommand, public MixFlakeOptions
+{
+ std::string flakeUrl = ".";
+
+public:
+
+ FlakeCommand()
+ {
+ expectArg("flake-url", &flakeUrl, true);
+ }
+
+ FlakeRef getFlakeRef()
+ {
+ return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
+ }
+
+ Flake getFlake()
+ {
+ auto evalState = getEvalState();
+ return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
+ }
+
+ LockedFlake lockFlake()
+ {
+ return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
+ }
+};
+
+struct CmdFlakeList : EvalCommand
+{
+ std::string description() override
+ {
+ return "list available Nix flakes";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ using namespace fetchers;
+
+ auto registries = getRegistries(store);
+
+ for (auto & registry : registries) {
+ for (auto & entry : registry->entries) {
+ // FIXME: format nicely
+ logger->stdout("%s %s %s",
+ registry->type == Registry::Flag ? "flags " :
+ registry->type == Registry::User ? "user " :
+ registry->type == Registry::System ? "system" :
+ "global",
+ entry.from->to_string(),
+ entry.to->to_string());
+ }
+ }
+ }
+};
+
+static void printFlakeInfo(const Store & store, const Flake & flake)
+{
+ logger->stdout("Resolved URL: %s", flake.resolvedRef.to_string());
+ logger->stdout("Locked URL: %s", flake.lockedRef.to_string());
+ if (flake.description)
+ logger->stdout("Description: %s", *flake.description);
+ logger->stdout("Path: %s", store.printStorePath(flake.sourceInfo->storePath));
+ if (auto rev = flake.lockedRef.input->getRev())
+ logger->stdout("Revision: %s", rev->to_string(Base16, false));
+ if (flake.sourceInfo->info.revCount)
+ logger->stdout("Revisions: %s", *flake.sourceInfo->info.revCount);
+ if (flake.sourceInfo->info.lastModified)
+ logger->stdout("Last modified: %s",
+ std::put_time(std::localtime(&*flake.sourceInfo->info.lastModified), "%F %T"));
+}
+
+static nlohmann::json flakeToJson(const Store & store, const Flake & flake)
+{
+ nlohmann::json j;
+ if (flake.description)
+ j["description"] = *flake.description;
+ j["originalUrl"] = flake.originalRef.to_string();
+ j["original"] = attrsToJson(flake.originalRef.toAttrs());
+ j["resolvedUrl"] = flake.resolvedRef.to_string();
+ j["resolved"] = attrsToJson(flake.resolvedRef.toAttrs());
+ j["url"] = flake.lockedRef.to_string(); // FIXME: rename to lockedUrl
+ j["locked"] = attrsToJson(flake.lockedRef.toAttrs());
+ j["info"] = flake.sourceInfo->info.toJson();
+ if (auto rev = flake.lockedRef.input->getRev())
+ j["revision"] = rev->to_string(Base16, false);
+ if (flake.sourceInfo->info.revCount)
+ j["revCount"] = *flake.sourceInfo->info.revCount;
+ if (flake.sourceInfo->info.lastModified)
+ j["lastModified"] = *flake.sourceInfo->info.lastModified;
+ j["path"] = store.printStorePath(flake.sourceInfo->storePath);
+ return j;
+}
+
+struct CmdFlakeUpdate : FlakeCommand
+{
+ std::string description() override
+ {
+ return "update flake lock file";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ /* Use --refresh by default for 'nix flake update'. */
+ settings.tarballTtl = 0;
+
+ lockFlake();
+ }
+};
+
+static void enumerateOutputs(EvalState & state, Value & vFlake,
+ std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback)
+{
+ state.forceAttrs(vFlake);
+
+ auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ state.forceAttrs(*aOutputs->value);
+
+ for (auto & attr : *aOutputs->value->attrs)
+ callback(attr.name, *attr.value, *attr.pos);
+}
+
+struct CmdFlakeInfo : FlakeCommand, MixJSON
+{
+ std::string description() override
+ {
+ return "list info about a given flake";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto flake = getFlake();
+
+ if (json) {
+ auto json = flakeToJson(*store, flake);
+ logger->stdout(json.dump());
+ } else
+ printFlakeInfo(*store, flake);
+ }
+};
+
+struct CmdFlakeListInputs : FlakeCommand, MixJSON
+{
+ std::string description() override
+ {
+ return "list flake inputs";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto flake = lockFlake();
+
+ if (json)
+ logger->stdout(flake.lockFile.toJson());
+ else {
+ logger->stdout("%s", flake.flake.lockedRef);
+
+ std::function<void(const Node & node, const std::string & prefix)> recurse;
+
+ recurse = [&](const Node & node, const std::string & prefix)
+ {
+ for (const auto & [i, input] : enumerate(node.inputs)) {
+ //auto tree2 = tree.child(i + 1 == inputs.inputs.size());
+ bool last = i + 1 == node.inputs.size();
+ logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
+ prefix + (last ? treeLast : treeConn), input.first,
+ std::dynamic_pointer_cast<const LockedNode>(input.second)->lockedRef);
+ recurse(*input.second, prefix + (last ? treeNull : treeLine));
+ }
+ };
+
+ recurse(*flake.lockFile.root, "");
+ }
+ }
+};
+
+struct CmdFlakeCheck : FlakeCommand
+{
+ bool build = true;
+
+ CmdFlakeCheck()
+ {
+ mkFlag()
+ .longName("no-build")
+ .description("do not build checks")
+ .set(&build, false);
+ }
+
+ std::string description() override
+ {
+ return "check whether the flake evaluates and run its tests";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ settings.readOnlyMode = !build;
+
+ auto state = getEvalState();
+ auto flake = lockFlake();
+
+ 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);
+ };
+
+ auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ 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());
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the derivation '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ std::vector<StorePathWithOutputs> drvPaths;
+
+ auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ auto app = App(*state, v);
+ for (auto & i : app.context) {
+ auto [drvPathS, outputName] = decodeContext(i);
+ auto drvPath = store->parseStorePath(drvPathS);
+ if (!outputName.empty() && drvPath.isDerivation())
+ drvPaths.emplace_back(drvPath);
+ }
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the app definition '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ state->forceValue(v, pos);
+ if (v.type != tLambda || v.lambda.fun->matchAttrs || std::string(v.lambda.fun->arg) != "final")
+ throw Error("overlay does not take an argument named 'final'");
+ auto body = dynamic_cast<ExprLambda *>(v.lambda.fun->body);
+ if (!body || body->matchAttrs || std::string(body->arg) != "prev")
+ throw Error("overlay does not take an argument named 'prev'");
+ // FIXME: if we have a 'nixpkgs' input, use it to
+ // evaluate the overlay.
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the overlay '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ state->forceValue(v, pos);
+ if (v.type == tLambda) {
+ if (!v.lambda.fun->matchAttrs || !v.lambda.fun->formals->ellipsis)
+ throw Error("module must match an open attribute set ('{ config, ... }')");
+ } else if (v.type == tAttrs) {
+ for (auto & attr : *v.attrs)
+ try {
+ state->forceValue(*attr.value, *attr.pos);
+ } catch (Error & e) {
+ e.addPrefix(fmt("while evaluating the option '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attr.name, *attr.pos));
+ 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.addPrefix(fmt("while checking the NixOS module '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ std::function<void(const std::string & attrPath, Value & v, const Pos & pos)> checkHydraJobs;
+
+ checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ 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);
+ if (!state->isDerivation(*attr.value))
+ checkHydraJobs(attrPath + "." + (std::string) attr.name,
+ *attr.value, *attr.pos);
+ }
+
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the Hydra jobset '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const Pos & 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);
+ if (!state->isDerivation(*vToplevel))
+ throw Error("attribute 'config.system.build.toplevel' is not a derivation");
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the NixOS configuration '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ {
+ Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
+
+ auto vFlake = state->allocValue();
+ flake::callFlake(*state, flake, *vFlake);
+
+ enumerateOutputs(*state,
+ *vFlake,
+ [&](const std::string & name, Value & vOutput, const Pos & pos) {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("checking flake output '%s'", name));
+
+ try {
+ state->forceValue(vOutput, pos);
+
+ if (name == "checks") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ 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 ((std::string) attr.name == settings.thisSystem.get())
+ drvPaths.emplace_back(drvPath);
+ }
+ }
+ }
+
+ else if (name == "packages") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ 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);
+ }
+ }
+
+ else if (name == "apps") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ 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);
+ }
+ }
+
+ else if (name == "defaultPackage" || name == "devShell") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ checkSystemName(attr.name, *attr.pos);
+ checkDerivation(
+ fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+ }
+
+ else if (name == "defaultApp") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ checkSystemName(attr.name, *attr.pos);
+ checkApp(
+ fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+ }
+
+ else if (name == "legacyPackages") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ checkSystemName(attr.name, *attr.pos);
+ // FIXME: do getDerivations?
+ }
+ }
+
+ else if (name == "overlay")
+ checkOverlay(name, vOutput, pos);
+
+ else if (name == "overlays") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkOverlay(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
+ else if (name == "nixosModule")
+ checkModule(name, vOutput, pos);
+
+ else if (name == "nixosModules") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkModule(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
+ else if (name == "nixosConfigurations") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkNixOSConfiguration(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
+ else if (name == "hydraJobs")
+ checkHydraJobs(name, vOutput, pos);
+
+ else
+ warn("unknown flake output '%s'", name);
+
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking flake output '" ANSI_BOLD "%s" ANSI_NORMAL "':\n", name));
+ throw;
+ }
+ });
+ }
+
+ if (build && !drvPaths.empty()) {
+ Activity act(*logger, lvlInfo, actUnknown, "running flake checks");
+ store->buildPaths(drvPaths);
+ }
+ }
+};
+
+struct CmdFlakeAdd : MixEvalArgs, Command
+{
+ std::string fromUrl, toUrl;
+
+ std::string description() override
+ {
+ return "upsert flake in user flake registry";
+ }
+
+ CmdFlakeAdd()
+ {
+ expectArg("from-url", &fromUrl);
+ expectArg("to-url", &toUrl);
+ }
+
+ void run() override
+ {
+ auto fromRef = parseFlakeRef(fromUrl);
+ auto toRef = parseFlakeRef(toUrl);
+ 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());
+ }
+};
+
+struct CmdFlakeRemove : virtual Args, MixEvalArgs, Command
+{
+ std::string url;
+
+ std::string description() override
+ {
+ return "remove flake from user flake registry";
+ }
+
+ CmdFlakeRemove()
+ {
+ expectArg("url", &url);
+ }
+
+ void run() override
+ {
+ auto userRegistry = fetchers::getUserRegistry();
+ userRegistry->remove(parseFlakeRef(url).input);
+ userRegistry->write(fetchers::getUserRegistryPath());
+ }
+};
+
+struct CmdFlakePin : virtual Args, EvalCommand
+{
+ std::string url;
+
+ std::string description() override
+ {
+ return "pin a flake to its current version in user flake registry";
+ }
+
+ CmdFlakePin()
+ {
+ expectArg("url", &url);
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto ref = parseFlakeRef(url);
+ auto userRegistry = fetchers::getUserRegistry();
+ userRegistry->remove(ref.input);
+ auto [tree, resolved] = ref.resolve(store).input->fetchTree(store);
+ fetchers::Attrs extraAttrs;
+ if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
+ userRegistry->add(ref.input, resolved, extraAttrs);
+ }
+};
+
+struct CmdFlakeInit : virtual Args, Command
+{
+ std::string description() override
+ {
+ return "create a skeleton 'flake.nix' file in the current directory";
+ }
+
+ void run() override
+ {
+ Path flakeDir = absPath(".");
+
+ if (!pathExists(flakeDir + "/.git"))
+ throw Error("the directory '%s' is not a Git repository", flakeDir);
+
+ Path flakePath = flakeDir + "/flake.nix";
+
+ if (pathExists(flakePath))
+ throw Error("file '%s' already exists", flakePath);
+
+ writeFile(flakePath,
+#include "flake-template.nix.gen.hh"
+ );
+ }
+};
+
+struct CmdFlakeClone : FlakeCommand
+{
+ Path destDir;
+
+ std::string description() override
+ {
+ return "clone flake repository";
+ }
+
+ CmdFlakeClone()
+ {
+ mkFlag()
+ .shortName('f')
+ .longName("dest")
+ .label("path")
+ .description("destination path")
+ .dest(&destDir);
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ if (destDir.empty())
+ throw Error("missing flag '--dest'");
+
+ getFlakeRef().resolve(store).input->clone(destDir);
+ }
+};
+
+struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
+{
+ std::string dstUri;
+
+ CmdFlakeArchive()
+ {
+ mkFlag()
+ .longName("to")
+ .labels({"store-uri"})
+ .description("URI of the destination Nix store")
+ .dest(&dstUri);
+ }
+
+ std::string description() override
+ {
+ return "copy a flake and all its inputs to a store";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To copy the dwarffs flake and its dependencies to a binary cache:",
+ "nix flake archive --to file:///tmp/my-cache dwarffs"
+ },
+ Example{
+ "To fetch the dwarffs flake and its dependencies to the local Nix store:",
+ "nix flake archive dwarffs"
+ },
+ Example{
+ "To print the store paths of the flake sources of NixOps without fetching them:",
+ "nix flake archive --json --dry-run nixops"
+ },
+ };
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto flake = lockFlake();
+
+ auto jsonRoot = json ? std::optional<JSONObject>(std::cout) : std::nullopt;
+
+ StorePathSet sources;
+
+ sources.insert(flake.flake.sourceInfo->storePath.clone());
+ 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)
+ {
+ auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional<JSONObject>();
+ for (auto & input : node.inputs) {
+ auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(input.second);
+ assert(lockedInput);
+ auto jsonObj3 = jsonObj2 ? jsonObj2->object(input.first) : std::optional<JSONObject>();
+ if (!dryRun)
+ lockedInput->lockedRef.input->fetchTree(store);
+ auto storePath = lockedInput->computeStorePath(*store);
+ if (jsonObj3)
+ jsonObj3->attr("path", store->printStorePath(storePath));
+ sources.insert(std::move(storePath));
+ traverse(*lockedInput, jsonObj3);
+ }
+ };
+
+ traverse(*flake.lockFile.root, jsonRoot);
+
+ if (!dryRun && !dstUri.empty()) {
+ ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
+ copyPaths(store, dstStore, sources);
+ }
+ }
+};
+
+struct CmdFlake : virtual MultiCommand, virtual Command
+{
+ CmdFlake()
+ : MultiCommand({
+ {"list", []() { return make_ref<CmdFlakeList>(); }},
+ {"update", []() { return make_ref<CmdFlakeUpdate>(); }},
+ {"info", []() { return make_ref<CmdFlakeInfo>(); }},
+ {"list-inputs", []() { return make_ref<CmdFlakeListInputs>(); }},
+ {"check", []() { return make_ref<CmdFlakeCheck>(); }},
+ {"add", []() { return make_ref<CmdFlakeAdd>(); }},
+ {"remove", []() { return make_ref<CmdFlakeRemove>(); }},
+ {"pin", []() { return make_ref<CmdFlakePin>(); }},
+ {"init", []() { return make_ref<CmdFlakeInit>(); }},
+ {"clone", []() { return make_ref<CmdFlakeClone>(); }},
+ {"archive", []() { return make_ref<CmdFlakeArchive>(); }},
+ })
+ {
+ }
+
+ std::string description() override
+ {
+ return "manage Nix flakes";
+ }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix flake' requires a sub-command.");
+ command->prepare();
+ command->run();
+ }
+
+ void printHelp(const string & programName, std::ostream & out) override
+ {
+ MultiCommand::printHelp(programName, out);
+ }
+};
+
+static auto r1 = registerCommand<CmdFlake>("flake");
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 0cc523f50..01628cf6c 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -60,8 +60,7 @@ struct CmdHash : Command
Hash h = hashSink->finish().first;
if (truncate && h.hashSize > 20) h = compressHash(h, 20);
- std::cout << format("%1%\n") %
- h.to_string(base, base == SRI);
+ logger->stdout(h.to_string(base, base == SRI));
}
}
};
@@ -95,7 +94,7 @@ struct CmdToBase : Command
void run() override
{
for (auto s : args)
- std::cout << fmt("%s\n", Hash(s, ht).to_string(base, base == SRI));
+ logger->stdout(Hash(s, ht).to_string(base, base == SRI));
}
};
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 902383bff..fece68b17 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -1,3 +1,4 @@
+#include "installables.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
@@ -7,78 +8,98 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
+#include "flake/flake.hh"
+#include "flake/eval-cache.hh"
+#include "url.hh"
#include <gc/gc.h>
#include <regex>
+#include <queue>
namespace nix {
-
-SourceExprCommand::SourceExprCommand()
+MixFlakeOptions::MixFlakeOptions()
{
mkFlag()
- .shortName('f')
- .longName("file")
- .label("file")
- .description("evaluate FILE rather than the default")
- .dest(&file);
-}
+ .longName("recreate-lock-file")
+ .description("recreate lock file from scratch")
+ .set(&lockFlags.recreateLockFile, true);
-Value * SourceExprCommand::getSourceExpr(EvalState & state)
-{
- if (vSourceExpr) return vSourceExpr.get();
-
- auto sToplevel = state.symbols.create("_toplevel");
-
- // Allocate the vSourceExpr Value as uncollectable. Boehm GC doesn't
- // consider the member variable "alive" during execution causing it to be
- // GC'ed in the middle of evaluation.
- vSourceExpr = std::allocate_shared<Value>(traceable_allocator<Value>());
-
- if (file != "")
- state.evalFile(lookupFileArg(state, file), *vSourceExpr);
-
- else {
+ mkFlag()
+ .longName("no-update-lock-file")
+ .description("do not allow any updates to the lock file")
+ .set(&lockFlags.updateLockFile, false);
- /* Construct the installation source from $NIX_PATH. */
+ mkFlag()
+ .longName("no-write-lock-file")
+ .description("do not write the newly generated lock file")
+ .set(&lockFlags.writeLockFile, false);
- auto searchPath = state.getSearchPath();
+ mkFlag()
+ .longName("no-registries")
+ .description("don't use flake registries")
+ .set(&lockFlags.useRegistries, false);
- state.mkAttrs(*vSourceExpr, 1024);
+ mkFlag()
+ .longName("commit-lock-file")
+ .description("commit changes to the lock file")
+ .set(&lockFlags.commitLockFile, true);
- mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
+ mkFlag()
+ .longName("update-input")
+ .description("update a specific flake input")
+ .label("input-path")
+ .handler([&](std::vector<std::string> ss) {
+ lockFlags.inputUpdates.insert(flake::parseInputPath(ss[0]));
+ });
- std::unordered_set<std::string> seen;
+ mkFlag()
+ .longName("override-input")
+ .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')")
+ .arity(2)
+ .labels({"input-path", "flake-url"})
+ .handler([&](std::vector<std::string> ss) {
+ lockFlags.inputOverrides.insert_or_assign(
+ flake::parseInputPath(ss[0]),
+ parseFlakeRef(ss[1], absPath(".")));
+ });
+}
- auto addEntry = [&](const std::string & name) {
- if (name == "") return;
- if (!seen.insert(name).second) return;
- Value * v1 = state.allocValue();
- mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
- Value * v2 = state.allocValue();
- mkApp(*v2, *v1, mkString(*state.allocValue(), name));
- mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)),
- state.getBuiltin("import"), *v2);
- };
+SourceExprCommand::SourceExprCommand()
+{
+ mkFlag()
+ .shortName('f')
+ .longName("file")
+ .label("file")
+ .description("evaluate attributes from FILE")
+ .dest(&file);
- for (auto & i : searchPath)
- /* Hack to handle channels. */
- if (i.first.empty() && pathExists(i.second + "/manifest.nix")) {
- for (auto & j : readDirectory(i.second))
- if (j.name != "manifest.nix"
- && pathExists(fmt("%s/%s/default.nix", i.second, j.name)))
- addEntry(j.name);
- } else
- addEntry(i.first);
+ mkFlag()
+ .longName("expr")
+ .label("expr")
+ .description("evaluate attributes from EXPR")
+ .dest(&expr);
+}
- vSourceExpr->attrs->sort();
- }
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
+{
+ return {"defaultPackage." + settings.thisSystem.get()};
+}
- return vSourceExpr.get();
+Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
+{
+ return {
+ // As a convenience, look for the attribute in
+ // 'outputs.packages'.
+ "packages." + settings.thisSystem.get() + ".",
+ // As a temporary hack until Nixpkgs is properly converted
+ // to provide a clean 'packages' set, look in 'legacyPackages'.
+ "legacyPackages." + settings.thisSystem.get() + "."
+ };
}
-ref<EvalState> SourceExprCommand::getEvalState()
+ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState)
evalState = std::make_shared<EvalState>(searchPath, getStore());
@@ -93,6 +114,27 @@ Buildable Installable::toBuildable()
return std::move(buildables[0]);
}
+App::App(EvalState & state, Value & vApp)
+{
+ state.forceAttrs(vApp);
+
+ auto aType = vApp.attrs->need(state.sType);
+ if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app")
+ throw Error("value does not have type 'app', at %s", *aType.pos);
+
+ auto aProgram = vApp.attrs->need(state.symbols.create("program"));
+ program = state.forceString(*aProgram.value, context, *aProgram.pos);
+
+ // FIXME: check that 'program' is in the closure of 'context'.
+ if (!state.store->isInStore(program))
+ throw Error("app program '%s' is not in the Nix store", program);
+}
+
+App Installable::toApp(EvalState & state)
+{
+ return App(state, *toValue(state).first);
+}
+
struct InstallableStorePath : Installable
{
ref<Store> store;
@@ -122,54 +164,63 @@ struct InstallableStorePath : Installable
}
};
-struct InstallableValue : Installable
+std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations()
{
- SourceExprCommand & cmd;
+ auto state = cmd.getEvalState();
- InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+ auto v = toValue(*state).first;
- Buildables toBuildables() override
- {
- auto state = cmd.getEvalState();
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- auto v = toValue(*state).first;
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+ std::vector<flake::EvalCache::Derivation> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ state->store->parseStorePath(drvInfo.queryDrvPath()),
+ state->store->parseStorePath(drvInfo.queryOutPath()),
+ drvInfo.queryOutputName()
+ });
+ }
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ return res;
+}
- Buildables res;
+Buildables InstallableValue::toBuildables()
+{
+ auto state = cmd.getEvalState();
- StorePathSet drvPaths;
+ Buildables res;
- for (auto & drv : drvs) {
- Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())};
- drvPaths.insert(b.drvPath->clone());
+ StorePathSet drvPaths;
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
+ for (auto & drv : toDerivations()) {
+ Buildable b{.drvPath = drv.drvPath.clone()};
+ drvPaths.insert(drv.drvPath.clone());
- b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath()));
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
- res.push_back(std::move(b));
- }
+ b.outputs.emplace(outputName, drv.outPath.clone());
- // Hack to recognize .all: if all drvs have the same drvPath,
- // merge the buildables.
- if (drvPaths.size() == 1) {
- Buildable b{.drvPath = drvPaths.begin()->clone()};
- for (auto & b2 : res)
- for (auto & output : b2.outputs)
- b.outputs.insert_or_assign(output.first, output.second.clone());
- Buildables bs;
- bs.push_back(std::move(b));
- return bs;
- } else
- return res;
+ res.push_back(std::move(b));
}
-};
+
+ // Hack to recognize .all: if all drvs have the same drvPath,
+ // merge the buildables.
+ if (drvPaths.size() == 1) {
+ Buildable b{.drvPath = drvPaths.begin()->clone()};
+ for (auto & b2 : res)
+ for (auto & output : b2.outputs)
+ b.outputs.insert_or_assign(output.first, output.second.clone());
+ Buildables bs;
+ bs.push_back(std::move(b));
+ return bs;
+ } else
+ return res;
+}
struct InstallableExpr : InstallableValue
{
@@ -190,70 +241,201 @@ struct InstallableExpr : InstallableValue
struct InstallableAttrPath : InstallableValue
{
+ Value * v;
std::string attrPath;
- InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath)
- : InstallableValue(cmd), attrPath(attrPath)
+ InstallableAttrPath(SourceExprCommand & cmd, Value * v, const std::string & attrPath)
+ : InstallableValue(cmd), v(v), attrPath(attrPath)
{ }
std::string what() override { return attrPath; }
std::pair<Value *, Pos> toValue(EvalState & state) override
{
- auto source = cmd.getSourceExpr(state);
+ auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v);
+ state.forceValue(*vRes);
+ return {vRes, pos};
+ }
+};
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
- auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first;
- state.forceValue(*v);
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
- return {v, noPos};
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, lockedFlake, *vFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ state.forceValue(*aOutputs->value);
+
+ return aOutputs->value;
+}
+
+std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake::toDerivation()
+{
+ auto state = cmd.getEvalState();
+
+ auto lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags);
+
+ Value * vOutputs = nullptr;
+
+ auto emptyArgs = state->allocBindings(0);
+
+ auto & evalCache = flake::EvalCache::singleton();
+
+ auto fingerprint = lockedFlake.getFingerprint();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto drv = evalCache.getDerivation(fingerprint, attrPath);
+ if (drv) {
+ if (state->store->isValidPath(drv->drvPath))
+ return {attrPath, lockedFlake.flake.lockedRef, std::move(*drv)};
+ }
+
+ if (!vOutputs)
+ vOutputs = getFlakeOutputs(*state, lockedFlake);
+
+ try {
+ auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs).first;
+ state->forceValue(*v);
+
+ auto drvInfo = getDerivation(*state, *v, false);
+ if (!drvInfo)
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
+
+ auto drv = flake::EvalCache::Derivation{
+ state->store->parseStorePath(drvInfo->queryDrvPath()),
+ state->store->parseStorePath(drvInfo->queryOutPath()),
+ drvInfo->queryOutputName()
+ };
+
+ evalCache.addDerivation(fingerprint, attrPath, drv);
+
+ return {attrPath, lockedFlake.flake.lockedRef, std::move(drv)};
+ } catch (AttrPathNotFound & e) {
+ }
}
-};
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
+{
+ std::vector<flake::EvalCache::Derivation> res;
+ res.push_back(std::get<2>(toDerivation()));
+ return res;
+}
+
+std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
+{
+ auto lockedFlake = lockFlake(state, flakeRef, cmd.lockFlags);
+
+ auto vOutputs = getFlakeOutputs(state, lockedFlake);
+
+ auto emptyArgs = state.allocBindings(0);
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return {v, pos};
+ } catch (AttrPathNotFound & e) {
+ }
+ }
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
// FIXME: extend
std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)";
static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex));
-static std::vector<std::shared_ptr<Installable>> parseInstallables(
- SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables)
+std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
+ ref<Store> store, std::vector<std::string> ss)
{
std::vector<std::shared_ptr<Installable>> result;
- if (ss.empty() && useDefaultInstallables) {
- if (cmd.file == "")
- cmd.file = ".";
- ss = {""};
- }
+ if (file || expr) {
+ if (file && expr)
+ throw UsageError("'--file' and '--expr' are exclusive");
- for (auto & s : ss) {
+ // FIXME: backward compatibility hack
+ if (file) evalSettings.pureEval = false;
- if (s.compare(0, 1, "(") == 0)
- result.push_back(std::make_shared<InstallableExpr>(cmd, s));
+ auto state = getEvalState();
+ auto vFile = state->allocValue();
- else if (s.find("/") != std::string::npos) {
+ if (file)
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
+ else {
+ auto e = state->parseExprFromString(*expr, absPath("."));
+ state->eval(e, *vFile);
+ }
- auto path = store->toStorePath(store->followLinksToStore(s));
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s == "." ? "" : s));
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(store, path));
- }
+ } else {
- else if (s == "" || std::regex_match(s, attrPathRegex))
- result.push_back(std::make_shared<InstallableAttrPath>(cmd, s));
+ auto follow = [&](const std::string & s) -> std::optional<StorePath> {
+ try {
+ return store->followLinksToStorePath(s);
+ } catch (NotInStore &) {
+ return {};
+ }
+ };
- else
- throw UsageError("don't know what to do with argument '%s'", s);
+ for (auto & s : ss) {
+ if (hasPrefix(s, "nixpkgs.")) {
+ bool static warned;
+ warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs#<attr>' instead");
+ result.push_back(std::make_shared<InstallableFlake>(*this,
+ FlakeRef::fromAttrs({{"type", "indirect"}, {"id", "nixpkgs"}}),
+ Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}, Strings{}));
+ }
+
+ else {
+ auto res = maybeParseFlakeRefWithFragment(s, absPath("."));
+ if (res) {
+ auto &[flakeRef, fragment] = *res;
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this, std::move(flakeRef),
+ fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
+ getDefaultFlakeAttrPathPrefixes()));
+ } else {
+ std::optional<StorePath> storePath;
+ if (s.find('/') != std::string::npos && (storePath = follow(s)))
+ result.push_back(std::make_shared<InstallableStorePath>(store, store->printStorePath(*storePath)));
+ else
+ throw Error("unrecognized argument '%s'", s);
+ }
+ }
+ }
}
return result;
}
-std::shared_ptr<Installable> parseInstallable(
- SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
- bool useDefaultInstallables)
+std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
+ ref<Store> store, const std::string & installable)
{
- auto installables = parseInstallables(cmd, store, {installable}, false);
+ auto installables = parseInstallables(store, {installable});
assert(installables.size() == 1);
return installables.front();
}
@@ -308,7 +490,7 @@ StorePath toStorePath(ref<Store> store, RealiseMode mode,
auto paths = toStorePaths(store, mode, {installable});
if (paths.size() != 1)
- throw Error("argument '%s' should evaluate to one store path", installable->what());
+ throw Error("argument '%s' should evaluate to one store path", installable->what());
return paths.begin()->clone();
}
@@ -339,12 +521,16 @@ StorePathSet toDerivations(ref<Store> store,
void InstallablesCommand::prepare()
{
- installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
+ if (_installables.empty() && useDefaultInstallables())
+ // FIXME: commands like "nix install" should not have a
+ // default, probably.
+ _installables.push_back(".");
+ installables = parseInstallables(getStore(), _installables);
}
void InstallableCommand::prepare()
{
- installable = parseInstallable(*this, getStore(), _installable, false);
+ installable = parseInstallable(getStore(), _installable);
}
}
diff --git a/src/nix/installables.hh b/src/nix/installables.hh
index 503984220..8f2d50077 100644
--- a/src/nix/installables.hh
+++ b/src/nix/installables.hh
@@ -3,11 +3,15 @@
#include "util.hh"
#include "path.hh"
#include "eval.hh"
+#include "flake/eval-cache.hh"
#include <optional>
namespace nix {
+struct DrvInfo;
+struct SourceExprCommand;
+
struct Buildable
{
std::optional<StorePath> drvPath;
@@ -16,6 +20,15 @@ struct Buildable
typedef std::vector<Buildable> Buildables;
+struct App
+{
+ PathSet context;
+ Path program;
+ // FIXME: add args, sandbox settings, metadata, ...
+
+ App(EvalState & state, Value & vApp);
+};
+
struct Installable
{
virtual ~Installable() { }
@@ -29,6 +42,8 @@ struct Installable
Buildable toBuildable();
+ App toApp(EvalState & state);
+
virtual std::pair<Value *, Pos> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
@@ -42,4 +57,40 @@ struct Installable
}
};
+struct InstallableValue : Installable
+{
+ SourceExprCommand & cmd;
+
+ InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+
+ virtual std::vector<flake::EvalCache::Derivation> toDerivations();
+
+ Buildables toBuildables() override;
+};
+
+struct InstallableFlake : InstallableValue
+{
+ FlakeRef flakeRef;
+ Strings attrPaths;
+ Strings prefixes;
+
+ InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef,
+ Strings && attrPaths, Strings && prefixes)
+ : InstallableValue(cmd), flakeRef(flakeRef), attrPaths(attrPaths),
+ prefixes(prefixes)
+ { }
+
+ std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
+
+ std::vector<std::string> getActualAttrPaths();
+
+ Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
+
+ std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> toDerivation();
+
+ std::vector<flake::EvalCache::Derivation> toDerivations() override;
+
+ std::pair<Value *, Pos> toValue(EvalState & state) override;
+};
+
}
diff --git a/src/nix/local.mk b/src/nix/local.mk
index 033675e89..3fcd15dc6 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -27,3 +27,5 @@ $(foreach name, \
$(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote))
src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
+
+$(d)/flake.cc: $(d)/flake-template.nix.gen.hh
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index 3ef1f2750..8590199d7 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -34,16 +34,14 @@ struct MixLs : virtual Args, MixJSON
(st.isExecutable ? "-r-xr-xr-x" : "-r--r--r--") :
st.type == FSAccessor::Type::tSymlink ? "lrwxrwxrwx" :
"dr-xr-xr-x";
- std::cout <<
- (format("%s %20d %s") % tp % st.fileSize % relPath);
+ auto line = fmt("%s %20d %s", tp, st.fileSize, relPath);
if (st.type == FSAccessor::Type::tSymlink)
- std::cout << " -> " << accessor->readLink(curPath)
- ;
- std::cout << "\n";
+ line += " -> " + accessor->readLink(curPath);
+ logger->stdout(line);
if (recursive && st.type == FSAccessor::Type::tDirectory)
doPath(st, curPath, relPath, false);
} else {
- std::cout << relPath << "\n";
+ logger->stdout(relPath);
if (recursive) {
auto st = accessor->stat(curPath);
if (st.type == FSAccessor::Type::tDirectory)
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 2c64c7476..15f412dae 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -150,6 +150,7 @@ void mainWrapped(int argc, char * * argv)
verbosity = lvlWarn;
settings.verboseBuild = false;
+ evalSettings.pureEval = true;
NixArgs args;
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
new file mode 100644
index 000000000..e473be47f
--- /dev/null
+++ b/src/nix/profile.cc
@@ -0,0 +1,427 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+#include "archive.hh"
+#include "builtins/buildenv.hh"
+#include "flake/flakeref.hh"
+#include "../nix-env/user-env.hh"
+
+#include <nlohmann/json.hpp>
+#include <regex>
+
+using namespace nix;
+
+struct ProfileElementSource
+{
+ FlakeRef originalRef;
+ // FIXME: record original attrpath.
+ FlakeRef resolvedRef;
+ std::string attrPath;
+ // FIXME: output names
+};
+
+struct ProfileElement
+{
+ StorePathSet storePaths;
+ std::optional<ProfileElementSource> source;
+ bool active = true;
+ // FIXME: priority
+};
+
+struct ProfileManifest
+{
+ std::vector<ProfileElement> elements;
+
+ ProfileManifest() { }
+
+ ProfileManifest(EvalState & state, const Path & profile)
+ {
+ auto manifestPath = profile + "/manifest.json";
+
+ if (pathExists(manifestPath)) {
+ auto json = nlohmann::json::parse(readFile(manifestPath));
+
+ auto version = json.value("version", 0);
+ if (version != 1)
+ throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
+
+ for (auto & e : json["elements"]) {
+ ProfileElement element;
+ for (auto & p : e["storePaths"])
+ element.storePaths.insert(state.store->parseStorePath((std::string) p));
+ element.active = e["active"];
+ if (e.value("uri", "") != "") {
+ element.source = ProfileElementSource{
+ parseFlakeRef(e["originalUri"]),
+ parseFlakeRef(e["uri"]),
+ e["attrPath"]
+ };
+ }
+ elements.emplace_back(std::move(element));
+ }
+ }
+
+ else if (pathExists(profile + "/manifest.nix")) {
+ // FIXME: needed because of pure mode; ugly.
+ if (state.allowedPaths) {
+ state.allowedPaths->insert(state.store->followLinksToStore(profile));
+ state.allowedPaths->insert(state.store->followLinksToStore(profile + "/manifest.nix"));
+ }
+
+ auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile));
+
+ for (auto & drvInfo : drvInfos) {
+ ProfileElement element;
+ element.storePaths = singleton(state.store->parseStorePath(drvInfo.queryOutPath()));
+ elements.emplace_back(std::move(element));
+ }
+ }
+ }
+
+ std::string toJSON(Store & store) const
+ {
+ auto array = nlohmann::json::array();
+ for (auto & element : elements) {
+ auto paths = nlohmann::json::array();
+ for (auto & path : element.storePaths)
+ paths.push_back(store.printStorePath(path));
+ nlohmann::json obj;
+ obj["storePaths"] = paths;
+ obj["active"] = element.active;
+ if (element.source) {
+ obj["originalUri"] = element.source->originalRef.to_string();
+ obj["uri"] = element.source->resolvedRef.to_string();
+ obj["attrPath"] = element.source->attrPath;
+ }
+ array.push_back(obj);
+ }
+ nlohmann::json json;
+ json["version"] = 1;
+ json["elements"] = array;
+ return json.dump();
+ }
+
+ StorePath build(ref<Store> store)
+ {
+ auto tempDir = createTempDir();
+
+ StorePathSet references;
+
+ Packages pkgs;
+ for (auto & element : elements) {
+ for (auto & path : element.storePaths) {
+ if (element.active)
+ pkgs.emplace_back(store->printStorePath(path), true, 5);
+ references.insert(path.clone());
+ }
+ }
+
+ buildProfile(tempDir, std::move(pkgs));
+
+ writeFile(tempDir + "/manifest.json", toJSON(*store));
+
+ /* Add the symlink tree to the store. */
+ StringSink sink;
+ dumpPath(tempDir, sink);
+
+ auto narHash = hashString(htSHA256, *sink.s);
+
+ ValidPathInfo info(store->makeFixedOutputPath(true, narHash, "profile", references));
+ info.references = std::move(references);
+ info.narHash = narHash;
+ info.narSize = sink.s->size();
+ info.ca = makeFixedOutputCA(true, info.narHash);
+
+ store->addToStore(info, sink.s);
+
+ return std::move(info.path);
+ }
+};
+
+struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
+{
+ std::string description() override
+ {
+ return "install a package into a profile";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To install a package from Nixpkgs:",
+ "nix profile install nixpkgs#hello"
+ },
+ Example{
+ "To install a package from a specific branch of Nixpkgs:",
+ "nix profile install nixpkgs/release-19.09#hello"
+ },
+ Example{
+ "To install a package from a specific revision of Nixpkgs:",
+ "nix profile install nixpkgs/1028bb33859f8dfad7f98e1c8d185f3d1aaa7340#hello"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ ProfileManifest manifest(*getEvalState(), *profile);
+
+ std::vector<StorePathWithOutputs> pathsToBuild;
+
+ for (auto & installable : installables) {
+ if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) {
+ auto [attrPath, resolvedRef, drv] = installable2->toDerivation();
+
+ ProfileElement element;
+ element.storePaths = singleton(drv.outPath.clone()); // FIXME
+ element.source = ProfileElementSource{
+ installable2->flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.emplace_back(drv.drvPath.clone(), StringSet{"out"}); // FIXME
+
+ manifest.elements.emplace_back(std::move(element));
+ } else
+ throw Error("'nix profile install' does not support argument '%s'", installable->what());
+ }
+
+ store->buildPaths(pathsToBuild);
+
+ updateProfile(manifest.build(store));
+ }
+};
+
+class MixProfileElementMatchers : virtual Args
+{
+ std::vector<std::string> _matchers;
+
+public:
+
+ MixProfileElementMatchers()
+ {
+ expectArgs("elements", &_matchers);
+ }
+
+ typedef std::variant<size_t, Path, std::regex> Matcher;
+
+ std::vector<Matcher> getMatchers(ref<Store> store)
+ {
+ std::vector<Matcher> res;
+
+ for (auto & s : _matchers) {
+ size_t n;
+ if (string2Int(s, n))
+ res.push_back(n);
+ else if (store->isStorePath(s))
+ res.push_back(s);
+ else
+ res.push_back(std::regex(s, std::regex::extended | std::regex::icase));
+ }
+
+ return res;
+ }
+
+ bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
+ {
+ for (auto & matcher : matchers) {
+ if (auto n = std::get_if<size_t>(&matcher)) {
+ if (*n == pos) return true;
+ } else if (auto path = std::get_if<Path>(&matcher)) {
+ if (element.storePaths.count(store.parseStorePath(*path))) return true;
+ } else if (auto regex = std::get_if<std::regex>(&matcher)) {
+ if (element.source
+ && std::regex_match(element.source->attrPath, *regex))
+ return true;
+ }
+ }
+
+ return false;
+ }
+};
+
+struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElementMatchers
+{
+ std::string description() override
+ {
+ return "remove packages from a profile";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To remove a package by attribute path:",
+ "nix profile remove packages.x86_64-linux.hello"
+ },
+ Example{
+ "To remove all packages:",
+ "nix profile remove '.*'"
+ },
+ Example{
+ "To remove a package by store path:",
+ "nix profile remove /nix/store/rr3y0c6zyk7kjjl8y19s4lsrhn4aiq1z-hello-2.10"
+ },
+ Example{
+ "To remove a package by position:",
+ "nix profile remove 3"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ ProfileManifest oldManifest(*getEvalState(), *profile);
+
+ auto matchers = getMatchers(store);
+
+ ProfileManifest newManifest;
+
+ for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
+ auto & element(oldManifest.elements[i]);
+ if (!matches(*store, element, i, matchers))
+ newManifest.elements.push_back(std::move(element));
+ }
+
+ // FIXME: warn about unused matchers?
+
+ printInfo("removed %d packages, kept %d packages",
+ oldManifest.elements.size() - newManifest.elements.size(),
+ newManifest.elements.size());
+
+ updateProfile(newManifest.build(store));
+ }
+};
+
+struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProfileElementMatchers
+{
+ std::string description() override
+ {
+ return "upgrade packages using their most recent flake";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To upgrade all packages that were installed using a mutable flake reference:",
+ "nix profile upgrade '.*'"
+ },
+ Example{
+ "To upgrade a specific package:",
+ "nix profile upgrade packages.x86_64-linux.hello"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ ProfileManifest manifest(*getEvalState(), *profile);
+
+ auto matchers = getMatchers(store);
+
+ // FIXME: code duplication
+ std::vector<StorePathWithOutputs> pathsToBuild;
+
+ for (size_t i = 0; i < manifest.elements.size(); ++i) {
+ auto & element(manifest.elements[i]);
+ if (element.source
+ && !element.source->originalRef.input->isImmutable()
+ && matches(*store, element, i, matchers))
+ {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("checking '%s' for updates", element.source->attrPath));
+
+ InstallableFlake installable(*this, FlakeRef(element.source->originalRef), {element.source->attrPath}, {});
+
+ auto [attrPath, resolvedRef, drv] = installable.toDerivation();
+
+ if (element.source->resolvedRef == resolvedRef) continue;
+
+ printInfo("upgrading '%s' from flake '%s' to '%s'",
+ element.source->attrPath, element.source->resolvedRef, resolvedRef);
+
+ element.storePaths = singleton(drv.outPath.clone()); // FIXME
+ element.source = ProfileElementSource{
+ installable.flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.emplace_back(drv.drvPath, StringSet{"out"}); // FIXME
+ }
+ }
+
+ store->buildPaths(pathsToBuild);
+
+ updateProfile(manifest.build(store));
+ }
+};
+
+struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
+{
+ std::string description() override
+ {
+ return "list installed packages";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To show what packages are installed in the default profile:",
+ "nix profile info"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ ProfileManifest manifest(*getEvalState(), *profile);
+
+ for (size_t i = 0; i < manifest.elements.size(); ++i) {
+ auto & element(manifest.elements[i]);
+ logger->stdout("%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 : "-",
+ concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
+ }
+ }
+};
+
+struct CmdProfile : virtual MultiCommand, virtual Command
+{
+ CmdProfile()
+ : MultiCommand({
+ {"install", []() { return make_ref<CmdProfileInstall>(); }},
+ {"remove", []() { return make_ref<CmdProfileRemove>(); }},
+ {"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
+ {"info", []() { return make_ref<CmdProfileInfo>(); }},
+ })
+ { }
+
+ std::string description() override
+ {
+ return "manage Nix profiles";
+ }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix profile' requires a sub-command.");
+ command->prepare();
+ command->run();
+ }
+
+ void printHelp(const string & programName, std::ostream & out) override
+ {
+ MultiCommand::printHelp(programName, out);
+ }
+};
+
+static auto r1 = registerCommand<CmdProfile>("profile");
+
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index adc9b9a5d..8e7ba95a3 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -7,6 +7,7 @@
#include <atomic>
#include <map>
#include <thread>
+#include <iostream>
namespace nix {
@@ -442,6 +443,18 @@ public:
return res;
}
+
+ void writeToStdout(std::string_view s) override
+ {
+ auto state(state_.lock());
+ if (state->active) {
+ std::cerr << "\r\e[K";
+ Logger::writeToStdout(s);
+ draw(*state);
+ } else {
+ Logger::writeToStdout(s);
+ }
+ }
};
void startProgressBar(bool printBuildLogs)
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 27727bd25..a2632cff8 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -811,6 +811,7 @@ struct CmdRepl : StoreCommand, MixEvalArgs
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);
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 5334531fd..901b87fbb 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -89,15 +89,15 @@ struct CmdRun : InstallablesCommand, RunCommon, MixEnvironment
},
Example{
"To start a shell providing youtube-dl from your 'nixpkgs' channel:",
- "nix run nixpkgs.youtube-dl"
+ "nix run nixpkgs#youtube-dl"
},
Example{
"To run GNU Hello:",
- "nix run nixpkgs.hello -c hello --greeting 'Hi everybody!'"
+ "nix run nixpkgs#hello -c hello --greeting 'Hi everybody!'"
},
Example{
"To run GNU Hello in a chroot store:",
- "nix run --store ~/my-nix nixpkgs.hello -c hello"
+ "nix run --store ~/my-nix nixpkgs#hello -c hello"
},
};
}
@@ -143,6 +143,57 @@ struct CmdRun : InstallablesCommand, RunCommon, MixEnvironment
static auto r1 = registerCommand<CmdRun>("run");
+struct CmdApp : InstallableCommand, RunCommon
+{
+ std::vector<std::string> args;
+
+ CmdApp()
+ {
+ expectArgs("args", &args);
+ }
+
+ std::string description() override
+ {
+ return "run a Nix application";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To run Blender:",
+ "nix app blender-bin"
+ },
+ };
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"defaultApp." + settings.thisSystem.get()};
+ }
+
+ Strings getDefaultFlakeAttrPathPrefixes() override
+ {
+ return {"apps." + settings.thisSystem.get() + "."};
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+
+ auto app = installable->toApp(*state);
+
+ state->realiseContext(app.context);
+
+ Strings allArgs{app.program};
+ for (auto & i : args) allArgs.push_back(i);
+
+ runProgram(store, app.program, allArgs);
+ }
+};
+
+static auto r2 = registerCommand<CmdApp>("app");
+
void chrootHelper(int argc, char * * argv)
{
int p = 1;
diff --git a/src/nix/search.cc b/src/nix/search.cc
index 769274543..caea25cdc 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -248,7 +248,9 @@ struct CmdSearch : SourceExprCommand, MixJSON
auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr;
- doExpr(getSourceExpr(*state), "", true, cache.get());
+ // FIXME
+ throw Error("NOT IMPLEMENTED");
+ //doExpr(getSourceExpr(*state), "", true, cache.get());
} catch (std::exception &) {
/* Fun fact: catching std::ios::failure does not work
diff --git a/src/nix/shell.cc b/src/nix/shell.cc
index 71e640667..439ef02ed 100644
--- a/src/nix/shell.cc
+++ b/src/nix/shell.cc
@@ -182,6 +182,11 @@ struct Common : InstallableCommand, MixProfile
out << "eval \"$shellHook\"\n";
}
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()};
+ }
+
StorePath getShellOutPath(ref<Store> store)
{
auto path = installable->getStorePath();
@@ -238,11 +243,15 @@ struct CmdDevShell : Common, MixEnvironment
return {
Example{
"To get the build environment of GNU hello:",
- "nix dev-shell nixpkgs.hello"
+ "nix dev-shell nixpkgs#hello"
+ },
+ Example{
+ "To get the build environment of the default package of flake in the current directory:",
+ "nix dev-shell"
},
Example{
"To store the build environment in a profile:",
- "nix dev-shell --profile /tmp/my-shell nixpkgs.hello"
+ "nix dev-shell --profile /tmp/my-shell nixpkgs#hello"
},
Example{
"To use a build environment previously recorded in a profile:",
@@ -300,7 +309,7 @@ struct CmdPrintDevEnv : Common
return {
Example{
"To apply the build environment of GNU hello to the current shell:",
- ". <(nix print-dev-env nixpkgs.hello)"
+ ". <(nix print-dev-env nixpkgs#hello)"
},
};
}
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index 87544f937..55a3255d2 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -23,7 +23,7 @@ struct CmdShowConfig : Command, MixJSON
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (auto & s : settings)
- std::cout << s.first + " = " + s.second.value + "\n";
+ logger->stdout(s.first + " = " + s.second.value);
}
}
};
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index d3b7a674a..6037f37fe 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -69,9 +69,9 @@ struct CmdWhyDepends : SourceExprCommand
void run(ref<Store> store) override
{
- auto package = parseInstallable(*this, store, _package, false);
+ auto package = parseInstallable(store, _package);
auto packagePath = toStorePath(store, Build, package);
- auto dependency = parseInstallable(*this, store, _dependency, false);
+ auto dependency = parseInstallable(store, _dependency);
auto dependencyPath = toStorePath(store, NoBuild, dependency);
auto dependencyPathHash = storePathToHash(store->printStorePath(dependencyPath));
@@ -149,7 +149,7 @@ struct CmdWhyDepends : SourceExprCommand
auto pathS = store->printStorePath(node.path);
assert(node.dist != inf);
- std::cout << fmt("%s%s%s%s" ANSI_NORMAL "\n",
+ logger->stdout("%s%s%s%s" ANSI_NORMAL,
firstPad,
node.visited ? "\e[38;5;244m" : "",
firstPad != "" ? "→ " : "",