aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-to-store.cc7
-rw-r--r--src/nix/build.cc25
-rw-r--r--src/nix/cat.cc14
-rw-r--r--src/nix/command.cc121
-rw-r--r--src/nix/command.hh136
-rw-r--r--src/nix/copy.cc7
-rw-r--r--src/nix/doctor.cc7
-rw-r--r--src/nix/dump-path.cc7
-rw-r--r--src/nix/edit.cc7
-rw-r--r--src/nix/eval.cc7
-rw-r--r--src/nix/flake-template.nix11
-rw-r--r--src/nix/flake.cc666
-rw-r--r--src/nix/hash.cc26
-rw-r--r--src/nix/installables.cc452
-rw-r--r--src/nix/installables.hh100
-rw-r--r--src/nix/local.mk2
-rw-r--r--src/nix/log.cc11
-rw-r--r--src/nix/ls.cc14
-rw-r--r--src/nix/main.cc13
-rw-r--r--src/nix/make-content-addressable.cc7
-rw-r--r--src/nix/optimise-store.cc11
-rw-r--r--src/nix/path-info.cc7
-rw-r--r--src/nix/ping-store.cc7
-rw-r--r--src/nix/profile.cc424
-rw-r--r--src/nix/repl.cc5
-rw-r--r--src/nix/run.cc112
-rw-r--r--src/nix/search.cc11
-rw-r--r--src/nix/shell.cc317
-rw-r--r--src/nix/show-config.cc11
-rw-r--r--src/nix/show-derivation.cc7
-rw-r--r--src/nix/sigs.cc14
-rw-r--r--src/nix/upgrade-nix.cc7
-rw-r--r--src/nix/verify.cc7
-rw-r--r--src/nix/why-depends.cc11
34 files changed, 2106 insertions, 485 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index e86b96e3f..296b2c7e4 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -22,11 +22,6 @@ struct CmdAddToStore : MixDryRun, StoreCommand
.dest(&namePart);
}
- std::string name() override
- {
- return "add-to-store";
- }
-
std::string description() override
{
return "add a path to the Nix store";
@@ -58,4 +53,4 @@ struct CmdAddToStore : MixDryRun, StoreCommand
}
};
-static RegisterCommand r1(make_ref<CmdAddToStore>());
+static auto r1 = registerCommand<CmdAddToStore>("add-to-store");
diff --git a/src/nix/build.cc b/src/nix/build.cc
index b329ac38a..4fd1de026 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"
@@ -5,7 +6,7 @@
using namespace nix;
-struct CmdBuild : MixDryRun, InstallablesCommand
+struct CmdBuild : MixDryRun, MixProfile, InstallablesCommand
{
Path outLink = "result";
@@ -24,11 +25,6 @@ struct CmdBuild : MixDryRun, InstallablesCommand
.set(&outLink, Path(""));
}
- std::string name() override
- {
- return "build";
- }
-
std::string description() override
{
return "build a derivation or fetch a store path";
@@ -45,6 +41,10 @@ struct CmdBuild : MixDryRun, InstallablesCommand
"To build the build.x86_64-linux attribute from release.nix:",
"nix build -f release.nix build.x86_64-linux"
},
+ Example{
+ "To make a profile point at GNU Hello:",
+ "nix build --profile /tmp/profile nixpkgs:hello"
+ },
};
}
@@ -54,19 +54,20 @@ struct CmdBuild : MixDryRun, InstallablesCommand
if (dryRun) return;
- for (size_t i = 0; i < buildables.size(); ++i) {
- auto & b(buildables[i]);
-
- if (outLink != "")
- for (auto & output : b.outputs)
+ if (outLink != "") {
+ for (size_t i = 0; i < buildables.size(); ++i) {
+ for (auto & output : buildables[i].outputs)
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) {
std::string symlink = outLink;
if (i) symlink += fmt("-%d", i);
if (output.first != "out") symlink += fmt("-%s", output.first);
store2->addPermRoot(output.second, absPath(symlink), true);
}
+ }
}
+
+ updateProfile(buildables);
}
};
-static RegisterCommand r1(make_ref<CmdBuild>());
+static auto r1 = registerCommand<CmdBuild>("build");
diff --git a/src/nix/cat.cc b/src/nix/cat.cc
index a35f640d8..851f90abd 100644
--- a/src/nix/cat.cc
+++ b/src/nix/cat.cc
@@ -28,11 +28,6 @@ struct CmdCatStore : StoreCommand, MixCat
expectArg("path", &path);
}
- std::string name() override
- {
- return "cat-store";
- }
-
std::string description() override
{
return "print the contents of a store file on stdout";
@@ -54,11 +49,6 @@ struct CmdCatNar : StoreCommand, MixCat
expectArg("path", &path);
}
- std::string name() override
- {
- return "cat-nar";
- }
-
std::string description() override
{
return "print the contents of a file inside a NAR file";
@@ -70,5 +60,5 @@ struct CmdCatNar : StoreCommand, MixCat
}
};
-static RegisterCommand r1(make_ref<CmdCatStore>());
-static RegisterCommand r2(make_ref<CmdCatNar>());
+static auto r1 = registerCommand<CmdCatStore>("cat-store");
+static auto r2 = registerCommand<CmdCatNar>("cat-nar");
diff --git a/src/nix/command.cc b/src/nix/command.cc
index 532f331a7..1cb4cc92a 100644
--- a/src/nix/command.cc
+++ b/src/nix/command.cc
@@ -1,82 +1,11 @@
#include "command.hh"
#include "store-api.hh"
#include "derivations.hh"
+#include "profiles.hh"
namespace nix {
-Commands * RegisterCommand::commands = 0;
-
-void Command::printHelp(const string & programName, std::ostream & out)
-{
- Args::printHelp(programName, out);
-
- auto exs = examples();
- if (!exs.empty()) {
- out << "\n";
- out << "Examples:\n";
- for (auto & ex : exs)
- out << "\n"
- << " " << ex.description << "\n" // FIXME: wrap
- << " $ " << ex.command << "\n";
- }
-}
-
-MultiCommand::MultiCommand(const Commands & _commands)
- : commands(_commands)
-{
- expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) {
- assert(!command);
- auto i = commands.find(ss[0]);
- if (i == commands.end())
- throw UsageError("'%s' is not a recognised command", ss[0]);
- command = i->second;
- }});
-}
-
-void MultiCommand::printHelp(const string & programName, std::ostream & out)
-{
- if (command) {
- command->printHelp(programName + " " + command->name(), out);
- return;
- }
-
- out << "Usage: " << programName << " <COMMAND> <FLAGS>... <ARGS>...\n";
-
- out << "\n";
- out << "Common flags:\n";
- printFlags(out);
-
- out << "\n";
- out << "Available commands:\n";
-
- Table2 table;
- for (auto & command : commands) {
- auto descr = command.second->description();
- if (!descr.empty())
- table.push_back(std::make_pair(command.second->name(), descr));
- }
- printTable(out, table);
-
-#if 0
- out << "\n";
- out << "For full documentation, run 'man " << programName << "' or 'man " << programName << "-<COMMAND>'.\n";
-#endif
-}
-
-bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end)
-{
- if (Args::processFlag(pos, end)) return true;
- if (command && command->processFlag(pos, end)) return true;
- return false;
-}
-
-bool MultiCommand::processArgs(const Strings & args, bool finish)
-{
- if (command)
- return command->processArgs(args, finish);
- else
- return Args::processArgs(args, finish);
-}
+Commands * RegisterCommand::commands = nullptr;
StoreCommand::StoreCommand()
{
@@ -153,4 +82,50 @@ void StorePathCommand::run(ref<Store> store)
run(store, *storePaths.begin());
}
+MixProfile::MixProfile()
+{
+ mkFlag()
+ .longName("profile")
+ .description("profile to update")
+ .labels({"path"})
+ .dest(&profile);
+}
+
+void MixProfile::updateProfile(const Path & storePath)
+{
+ if (!profile) return;
+ auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
+ if (!store) throw Error("'--profile' is not supported for this Nix store");
+ auto profile2 = absPath(*profile);
+ switchLink(profile2,
+ createGeneration(
+ ref<LocalFSStore>(store),
+ profile2, storePath));
+}
+
+void MixProfile::updateProfile(const Buildables & buildables)
+{
+ if (!profile) return;
+
+ std::optional<Path> result;
+
+ for (auto & buildable : buildables) {
+ for (auto & output : buildable.outputs) {
+ if (result)
+ throw Error("'--profile' requires that the arguments produce a single store path, but there are multiple");
+ result = output.second;
+ }
+ }
+
+ if (!result)
+ throw Error("'--profile' requires that the arguments produce a single store path, but there are none");
+
+ updateProfile(*result);
+}
+
+MixDefaultProfile::MixDefaultProfile()
+{
+ profile = getDefaultProfile();
+}
+
}
diff --git a/src/nix/command.hh b/src/nix/command.hh
index fad404c73..13f3a0dc9 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -1,39 +1,22 @@
#pragma once
+#include "installables.hh"
#include "args.hh"
#include "common-eval-args.hh"
+#include <optional>
+
namespace nix {
extern std::string programPath;
-struct Value;
-class Bindings;
class EvalState;
-
-/* A command is an argument parser that can be executed by calling its
- run() method. */
-struct Command : virtual Args
-{
- virtual std::string name() = 0;
- virtual void prepare() { };
- virtual void run() = 0;
-
- struct Example
- {
- std::string description;
- std::string command;
- };
-
- typedef std::list<Example> Examples;
-
- virtual Examples examples() { return Examples(); }
-
- void printHelp(const string & programName, std::ostream & out) override;
-};
-
class Store;
+namespace flake {
+enum HandleLockFile : unsigned int;
+}
+
/* A command that requires a Nix store. */
struct StoreCommand : virtual Command
{
@@ -47,50 +30,43 @@ private:
std::shared_ptr<Store> _store;
};
-struct Buildable
+struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
- Path drvPath; // may be empty
- std::map<std::string, Path> outputs;
-};
+ ref<EvalState> getEvalState();
+
+private:
-typedef std::vector<Buildable> Buildables;
+ std::shared_ptr<EvalState> evalState;
+};
-struct Installable
+struct MixFlakeOptions : virtual Args
{
- virtual std::string what() = 0;
+ bool recreateLockFile = false;
- virtual Buildables toBuildables()
- {
- throw Error("argument '%s' cannot be built", what());
- }
+ bool saveLockFile = true;
- Buildable toBuildable();
+ bool useRegistries = true;
- virtual Value * toValue(EvalState & state)
- {
- throw Error("argument '%s' cannot be evaluated", what());
- }
+ MixFlakeOptions();
+
+ flake::HandleLockFile getLockFileMode();
};
-struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
+struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
{
- Path file;
+ std::optional<Path> file;
SourceExprCommand();
- /* 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);
-
- ref<EvalState> getEvalState();
+ std::vector<std::shared_ptr<Installable>> parseInstallables(
+ ref<Store> store, std::vector<std::string> ss);
-private:
+ std::shared_ptr<Installable> parseInstallable(
+ ref<Store> store, const std::string & installable);
- std::shared_ptr<EvalState> evalState;
+ virtual Strings getDefaultFlakeAttrPaths();
- Value * vSourceExpr = 0;
+ virtual Strings getDefaultFlakeAttrPathPrefixes();
};
enum RealiseMode { Build, NoBuild, DryRun };
@@ -122,14 +98,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. */
@@ -167,41 +143,24 @@ struct StorePathCommand : public InstallablesCommand
void run(ref<Store> store) override;
};
-typedef std::map<std::string, ref<Command>> Commands;
-
-/* An argument parser that supports multiple subcommands,
- i.e. ‘<command> <subcommand>’. */
-class MultiCommand : virtual Args
-{
-public:
- Commands commands;
-
- std::shared_ptr<Command> command;
-
- MultiCommand(const Commands & commands);
-
- void printHelp(const string & programName, std::ostream & out) override;
-
- bool processFlag(Strings::iterator & pos, Strings::iterator end) override;
-
- bool processArgs(const Strings & args, bool finish) override;
-};
-
/* A helper class for registering commands globally. */
struct RegisterCommand
{
static Commands * commands;
- RegisterCommand(ref<Command> command)
+ RegisterCommand(const std::string & name,
+ std::function<ref<Command>()> command)
{
if (!commands) commands = new Commands;
- commands->emplace(command->name(), command);
+ commands->emplace(name, command);
}
};
-std::shared_ptr<Installable> parseInstallable(
- SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
- bool useDefaultInstallables);
+template<class T>
+static RegisterCommand registerCommand(const std::string & name)
+{
+ return RegisterCommand(name, [](){ return make_ref<T>(); });
+}
Buildables build(ref<Store> store, RealiseMode mode,
std::vector<std::shared_ptr<Installable>> installables);
@@ -216,4 +175,23 @@ PathSet toDerivations(ref<Store> store,
std::vector<std::shared_ptr<Installable>> installables,
bool useDeriver = false);
+struct MixProfile : virtual Args, virtual StoreCommand
+{
+ std::optional<Path> profile;
+
+ MixProfile();
+
+ /* If 'profile' is set, make it point at 'storePath'. */
+ void updateProfile(const Path & storePath);
+
+ /* If 'profile' is set, make it point at the store path produced
+ by 'buildables'. */
+ void updateProfile(const Buildables & buildables);
+};
+
+struct MixDefaultProfile : MixProfile
+{
+ MixDefaultProfile();
+};
+
}
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 12a9f9cd3..b1aceb15c 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -42,11 +42,6 @@ struct CmdCopy : StorePathsCommand
.set(&substitute, Substitute);
}
- std::string name() override
- {
- return "copy";
- }
-
std::string description() override
{
return "copy paths between Nix stores";
@@ -97,4 +92,4 @@ struct CmdCopy : StorePathsCommand
}
};
-static RegisterCommand r1(make_ref<CmdCopy>());
+static auto r1 = registerCommand<CmdCopy>("copy");
diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc
index 7b5444619..98260127b 100644
--- a/src/nix/doctor.cc
+++ b/src/nix/doctor.cc
@@ -20,11 +20,6 @@ struct CmdDoctor : StoreCommand
{
bool success = true;
- std::string name() override
- {
- return "doctor";
- }
-
std::string description() override
{
return "check your system for potential problems";
@@ -121,4 +116,4 @@ struct CmdDoctor : StoreCommand
}
};
-static RegisterCommand r1(make_ref<CmdDoctor>());
+static auto r1 = registerCommand<CmdDoctor>("doctor");
diff --git a/src/nix/dump-path.cc b/src/nix/dump-path.cc
index f411c0cb7..90f1552d9 100644
--- a/src/nix/dump-path.cc
+++ b/src/nix/dump-path.cc
@@ -5,11 +5,6 @@ using namespace nix;
struct CmdDumpPath : StorePathCommand
{
- std::string name() override
- {
- return "dump-path";
- }
-
std::string description() override
{
return "dump a store path to stdout (in NAR format)";
@@ -33,4 +28,4 @@ struct CmdDumpPath : StorePathCommand
}
};
-static RegisterCommand r1(make_ref<CmdDumpPath>());
+static auto r1 = registerCommand<CmdDumpPath>("dump-path");
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index a6169f118..632a99d11 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -10,11 +10,6 @@ using namespace nix;
struct CmdEdit : InstallableCommand
{
- std::string name() override
- {
- return "edit";
- }
-
std::string description() override
{
return "open the Nix expression of a Nix package in $EDITOR";
@@ -78,4 +73,4 @@ struct CmdEdit : InstallableCommand
}
};
-static RegisterCommand r1(make_ref<CmdEdit>());
+static auto r1 = registerCommand<CmdEdit>("edit");
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index daefea757..276fdf003 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -18,11 +18,6 @@ struct CmdEval : MixJSON, InstallableCommand
mkFlag(0, "raw", "print strings unquoted", &raw);
}
- std::string name() override
- {
- return "eval";
- }
-
std::string description() override
{
return "evaluate a Nix expression";
@@ -74,4 +69,4 @@ struct CmdEval : MixJSON, InstallableCommand
}
};
-static RegisterCommand r1(make_ref<CmdEval>());
+static auto r1 = registerCommand<CmdEval>("eval");
diff --git a/src/nix/flake-template.nix b/src/nix/flake-template.nix
new file mode 100644
index 000000000..eb8eb14fc
--- /dev/null
+++ b/src/nix/flake-template.nix
@@ -0,0 +1,11 @@
+{
+ description = "A flake for building Hello World";
+
+ edition = 201909;
+
+ outputs = { self, nixpkgs }: {
+
+ packages.x86_64-linux.hello = nixpkgs.packages.x86_64-linux.hello;
+
+ };
+}
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
new file mode 100644
index 000000000..6e7c5e2eb
--- /dev/null
+++ b/src/nix/flake.cc
@@ -0,0 +1,666 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "progress-bar.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 <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()
+ {
+ if (flakeUrl.find('/') != std::string::npos || flakeUrl == ".")
+ return FlakeRef(flakeUrl, true);
+ else
+ return FlakeRef(flakeUrl);
+ }
+
+ Flake getFlake()
+ {
+ auto evalState = getEvalState();
+ return flake::getFlake(*evalState, getFlakeRef(), useRegistries);
+ }
+
+ ResolvedFlake resolveFlake()
+ {
+ return flake::resolveFlake(*getEvalState(), getFlakeRef(), getLockFileMode());
+ }
+};
+
+struct CmdFlakeList : EvalCommand
+{
+ std::string description() override
+ {
+ return "list available Nix flakes";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto registries = getEvalState()->getFlakeRegistries();
+
+ stopProgressBar();
+
+ for (auto & entry : registries[FLAG_REGISTRY]->entries)
+ std::cout << entry.first.to_string() << " flags " << entry.second.to_string() << "\n";
+
+ for (auto & entry : registries[USER_REGISTRY]->entries)
+ std::cout << entry.first.to_string() << " user " << entry.second.to_string() << "\n";
+
+ for (auto & entry : registries[GLOBAL_REGISTRY]->entries)
+ std::cout << entry.first.to_string() << " global " << entry.second.to_string() << "\n";
+ }
+};
+
+static void printSourceInfo(const SourceInfo & sourceInfo)
+{
+ std::cout << fmt("URL: %s\n", sourceInfo.resolvedRef.to_string());
+ if (sourceInfo.resolvedRef.ref)
+ std::cout << fmt("Branch: %s\n",*sourceInfo.resolvedRef.ref);
+ if (sourceInfo.resolvedRef.rev)
+ std::cout << fmt("Revision: %s\n", sourceInfo.resolvedRef.rev->to_string(Base16, false));
+ if (sourceInfo.revCount)
+ std::cout << fmt("Revisions: %s\n", *sourceInfo.revCount);
+ if (sourceInfo.lastModified)
+ std::cout << fmt("Last modified: %s\n",
+ std::put_time(std::localtime(&*sourceInfo.lastModified), "%F %T"));
+ std::cout << fmt("Path: %s\n", sourceInfo.storePath);
+}
+
+static void sourceInfoToJson(const SourceInfo & sourceInfo, nlohmann::json & j)
+{
+ j["url"] = sourceInfo.resolvedRef.to_string();
+ if (sourceInfo.resolvedRef.ref)
+ j["branch"] = *sourceInfo.resolvedRef.ref;
+ if (sourceInfo.resolvedRef.rev)
+ j["revision"] = sourceInfo.resolvedRef.rev->to_string(Base16, false);
+ if (sourceInfo.revCount)
+ j["revCount"] = *sourceInfo.revCount;
+ if (sourceInfo.lastModified)
+ j["lastModified"] = *sourceInfo.lastModified;
+ j["path"] = sourceInfo.storePath;
+}
+
+static void printFlakeInfo(const Flake & flake)
+{
+ std::cout << fmt("Description: %s\n", flake.description);
+ std::cout << fmt("Edition: %s\n", flake.edition);
+ printSourceInfo(flake.sourceInfo);
+}
+
+static nlohmann::json flakeToJson(const Flake & flake)
+{
+ nlohmann::json j;
+ j["description"] = flake.description;
+ j["edition"] = flake.edition;
+ sourceInfoToJson(flake.sourceInfo, j);
+ return j;
+}
+
+#if 0
+// FIXME: merge info CmdFlakeInfo?
+struct CmdFlakeDeps : FlakeCommand
+{
+ std::string description() override
+ {
+ return "list informaton about dependencies";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ std::queue<ResolvedFlake> todo;
+ todo.push(resolveFlake());
+
+ stopProgressBar();
+
+ while (!todo.empty()) {
+ auto resFlake = std::move(todo.front());
+ todo.pop();
+
+ for (auto & info : resFlake.flakeDeps) {
+ printFlakeInfo(info.second.flake);
+ todo.push(info.second);
+ }
+ }
+ }
+};
+#endif
+
+struct CmdFlakeUpdate : FlakeCommand
+{
+ std::string description() override
+ {
+ return "update flake lock file";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ auto flakeRef = getFlakeRef();
+
+ if (std::get_if<FlakeRef::IsPath>(&flakeRef.data))
+ updateLockFile(*evalState, flakeRef, true);
+ else
+ throw Error("cannot update lockfile of flake '%s'", flakeRef);
+ }
+};
+
+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
+ {
+ if (json) {
+ auto state = getEvalState();
+ auto flake = resolveFlake();
+
+ auto json = flakeToJson(flake.flake);
+
+ auto vFlake = state->allocValue();
+ flake::callFlake(*state, flake, *vFlake);
+
+ auto outputs = nlohmann::json::object();
+
+ enumerateOutputs(*state,
+ *vFlake,
+ [&](const std::string & name, Value & vProvide, const Pos & pos) {
+ auto provide = nlohmann::json::object();
+
+ if (name == "checks" || name == "packages") {
+ state->forceAttrs(vProvide, pos);
+ for (auto & aCheck : *vProvide.attrs)
+ provide[aCheck.name] = nlohmann::json::object();
+ }
+
+ outputs[name] = provide;
+ });
+
+ json["outputs"] = std::move(outputs);
+
+ std::cout << json.dump() << std::endl;
+ } else {
+ auto flake = getFlake();
+ stopProgressBar();
+ printFlakeInfo(flake);
+ }
+ }
+};
+
+struct CmdFlakeCheck : FlakeCommand, MixJSON
+{
+ 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 = resolveFlake();
+
+ 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 drvInfo->queryDrvPath();
+ } catch (Error & e) {
+ e.addPrefix(fmt("while checking the derivation '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
+ throw;
+ }
+ };
+
+ PathSet drvPaths;
+
+ auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ auto app = App(*state, v);
+ for (auto & i : app.context) {
+ auto [drvPath, outputName] = decodeContext(i);
+ if (!outputName.empty() && nix::isDerivation(drvPath))
+ drvPaths.insert(drvPath + "!" + outputName);
+ }
+ } 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);
+ 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.insert(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
+{
+ FlakeUri alias;
+ FlakeUri url;
+
+ std::string description() override
+ {
+ return "upsert flake in user flake registry";
+ }
+
+ CmdFlakeAdd()
+ {
+ expectArg("alias", &alias);
+ expectArg("flake-url", &url);
+ }
+
+ void run() override
+ {
+ FlakeRef aliasRef(alias);
+ Path userRegistryPath = getUserRegistryPath();
+ auto userRegistry = readRegistry(userRegistryPath);
+ userRegistry->entries.erase(aliasRef);
+ userRegistry->entries.insert_or_assign(aliasRef, FlakeRef(url));
+ writeRegistry(*userRegistry, userRegistryPath);
+ }
+};
+
+struct CmdFlakeRemove : virtual Args, MixEvalArgs, Command
+{
+ FlakeUri alias;
+
+ std::string description() override
+ {
+ return "remove flake from user flake registry";
+ }
+
+ CmdFlakeRemove()
+ {
+ expectArg("alias", &alias);
+ }
+
+ void run() override
+ {
+ Path userRegistryPath = getUserRegistryPath();
+ auto userRegistry = readRegistry(userRegistryPath);
+ userRegistry->entries.erase(FlakeRef(alias));
+ writeRegistry(*userRegistry, userRegistryPath);
+ }
+};
+
+struct CmdFlakePin : virtual Args, EvalCommand
+{
+ FlakeUri alias;
+
+ std::string description() override
+ {
+ return "pin flake require in user flake registry";
+ }
+
+ CmdFlakePin()
+ {
+ expectArg("alias", &alias);
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ Path userRegistryPath = getUserRegistryPath();
+ FlakeRegistry userRegistry = *readRegistry(userRegistryPath);
+ auto it = userRegistry.entries.find(FlakeRef(alias));
+ if (it != userRegistry.entries.end()) {
+ it->second = getFlake(*evalState, it->second, true).sourceInfo.resolvedRef;
+ writeRegistry(userRegistry, userRegistryPath);
+ } else {
+ std::shared_ptr<FlakeRegistry> globalReg = evalState->getGlobalFlakeRegistry();
+ it = globalReg->entries.find(FlakeRef(alias));
+ if (it != globalReg->entries.end()) {
+ auto newRef = getFlake(*evalState, it->second, true).sourceInfo.resolvedRef;
+ userRegistry.entries.insert_or_assign(alias, newRef);
+ writeRegistry(userRegistry, userRegistryPath);
+ } else
+ throw Error("the flake alias '%s' does not exist in the user or global registry", alias);
+ }
+ }
+};
+
+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()
+ {
+ expectArg("dest-dir", &destDir, true);
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ Registries registries = evalState->getFlakeRegistries();
+ gitCloneFlake(getFlakeRef().to_string(), *evalState, registries, destDir);
+ }
+};
+
+struct CmdFlake : virtual MultiCommand, virtual Command
+{
+ CmdFlake()
+ : MultiCommand({
+ {"list", []() { return make_ref<CmdFlakeList>(); }},
+ {"update", []() { return make_ref<CmdFlakeUpdate>(); }},
+ {"info", []() { return make_ref<CmdFlakeInfo>(); }},
+ {"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>(); }},
+ })
+ {
+ }
+
+ 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 d7451376c..0cc523f50 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -36,11 +36,6 @@ struct CmdHash : Command
expectArgs("paths", &paths);
}
- std::string name() override
- {
- return mode == mFile ? "hash-file" : "hash-path";
- }
-
std::string description() override
{
return mode == mFile
@@ -71,8 +66,8 @@ struct CmdHash : Command
}
};
-static RegisterCommand r1(make_ref<CmdHash>(CmdHash::mFile));
-static RegisterCommand r2(make_ref<CmdHash>(CmdHash::mPath));
+static RegisterCommand r1("hash-file", [](){ return make_ref<CmdHash>(CmdHash::mFile); });
+static RegisterCommand r2("hash-path", [](){ return make_ref<CmdHash>(CmdHash::mPath); });
struct CmdToBase : Command
{
@@ -88,15 +83,6 @@ struct CmdToBase : Command
expectArgs("strings", &args);
}
- std::string name() override
- {
- return
- base == Base16 ? "to-base16" :
- base == Base32 ? "to-base32" :
- base == Base64 ? "to-base64" :
- "to-sri";
- }
-
std::string description() override
{
return fmt("convert a hash to %s representation",
@@ -113,10 +99,10 @@ struct CmdToBase : Command
}
};
-static RegisterCommand r3(make_ref<CmdToBase>(Base16));
-static RegisterCommand r4(make_ref<CmdToBase>(Base32));
-static RegisterCommand r5(make_ref<CmdToBase>(Base64));
-static RegisterCommand r6(make_ref<CmdToBase>(SRI));
+static RegisterCommand r3("to-base16", [](){ return make_ref<CmdToBase>(Base16); });
+static RegisterCommand r4("to-base32", [](){ return make_ref<CmdToBase>(Base32); });
+static RegisterCommand r5("to-base64", [](){ return make_ref<CmdToBase>(Base64); });
+static RegisterCommand r6("to-sri", [](){ return make_ref<CmdToBase>(SRI); });
/* Legacy nix-hash command. */
static int compatNixHash(int argc, char * * argv)
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 0e8bba39d..671cf513a 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,75 +8,76 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
+#include "flake/flake.hh"
+#include "flake/eval-cache.hh"
#include <regex>
+#include <queue>
namespace nix {
+MixFlakeOptions::MixFlakeOptions()
+{
+ mkFlag()
+ .longName("recreate-lock-file")
+ .description("recreate lock file from scratch")
+ .set(&recreateLockFile, true);
+
+ mkFlag()
+ .longName("no-save-lock-file")
+ .description("do not save the newly generated lock file")
+ .set(&saveLockFile, false);
+
+ mkFlag()
+ .longName("no-registries")
+ .description("don't use flake registries")
+ .set(&useRegistries, false);
+}
+
+flake::HandleLockFile MixFlakeOptions::getLockFileMode()
+{
+ using namespace flake;
+ return
+ useRegistries
+ ? recreateLockFile
+ ? (saveLockFile ? RecreateLockFile : UseNewLockFile)
+ : (saveLockFile ? UpdateLockFile : UseUpdatedLockFile)
+ : AllPure;
+}
+
SourceExprCommand::SourceExprCommand()
{
mkFlag()
.shortName('f')
.longName("file")
.label("file")
- .description("evaluate FILE rather than the default")
+ .description("evaluate a set of attributes from FILE (deprecated)")
.dest(&file);
}
-Value * SourceExprCommand::getSourceExpr(EvalState & state)
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
{
- if (vSourceExpr) return vSourceExpr;
-
- auto sToplevel = state.symbols.create("_toplevel");
-
- vSourceExpr = state.allocValue();
-
- if (file != "")
- state.evalFile(lookupFileArg(state, file), *vSourceExpr);
-
- else {
-
- /* Construct the installation source from $NIX_PATH. */
-
- auto searchPath = state.getSearchPath();
-
- state.mkAttrs(*vSourceExpr, 1024);
-
- mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
-
- std::unordered_set<std::string> seen;
-
- 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);
- };
-
- 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);
-
- vSourceExpr->attrs->sort();
- }
+ return {"defaultPackage." + settings.thisSystem.get()};
+}
- return vSourceExpr;
+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)
+ if (!evalState) {
evalState = std::make_shared<EvalState>(searchPath, getStore());
+ evalState->addRegistryOverrides(registryOverrides);
+ }
return ref<EvalState>(evalState);
}
@@ -87,6 +89,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));
+}
+
struct InstallableStorePath : Installable
{
Path storePath;
@@ -99,53 +122,65 @@ struct InstallableStorePath : Installable
{
return {{isDerivation(storePath) ? storePath : "", {{"out", storePath}}}};
}
+
+ std::optional<Path> getStorePath() override
+ {
+ return storePath;
+ }
};
-struct InstallableValue : Installable
+std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations()
{
- SourceExprCommand & cmd;
-
- InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+ auto state = cmd.getEvalState();
- Buildables toBuildables() override
- {
- auto state = cmd.getEvalState();
+ auto v = toValue(*state);
- auto v = toValue(*state);
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ std::vector<flake::EvalCache::Derivation> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ drvInfo.queryDrvPath(),
+ drvInfo.queryOutPath(),
+ drvInfo.queryOutputName()
+ });
+ }
- Buildables res;
+ return res;
+}
- PathSet drvPaths;
+Buildables InstallableValue::toBuildables()
+{
+ Buildables res;
- for (auto & drv : drvs) {
- Buildable b{drv.queryDrvPath()};
- drvPaths.insert(b.drvPath);
+ PathSet drvPaths;
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath);
+ for (auto & drv : toDerivations()) {
+ Buildable b{drv.drvPath};
+ drvPaths.insert(b.drvPath);
- b.outputs.emplace(outputName, drv.queryOutPath());
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath);
- res.push_back(std::move(b));
- }
+ b.outputs.emplace(outputName, drv.outPath);
- // Hack to recognize .all: if all drvs have the same drvPath,
- // merge the buildables.
- if (drvPaths.size() == 1) {
- Buildable b{*drvPaths.begin()};
- for (auto & b2 : res)
- b.outputs.insert(b2.outputs.begin(), b2.outputs.end());
- return {b};
- } 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{*drvPaths.begin()};
+ for (auto & b2 : res)
+ b.outputs.insert(b2.outputs.begin(), b2.outputs.end());
+ return {b};
+ } else
+ return res;
+}
struct InstallableExpr : InstallableValue
{
@@ -166,70 +201,251 @@ 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; }
Value * toValue(EvalState & state) override
{
- auto source = cmd.getSourceExpr(state);
+ auto vRes = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v);
+ state.forceValue(*vRes);
+ return vRes;
+ }
+};
+
+void makeFlakeClosureGCRoot(Store & store,
+ const FlakeRef & origFlakeRef,
+ const flake::ResolvedFlake & resFlake)
+{
+ if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return;
+
+ /* Get the store paths of all non-local flakes. */
+ PathSet closure;
+
+ assert(store.isValidPath(resFlake.flake.sourceInfo.storePath));
+ closure.insert(resFlake.flake.sourceInfo.storePath);
+
+ std::queue<std::reference_wrapper<const flake::LockedInputs>> queue;
+ queue.push(resFlake.lockFile);
+
+ while (!queue.empty()) {
+ const flake::LockedInputs & flake = queue.front();
+ queue.pop();
+ /* Note: due to lazy fetching, these paths might not exist
+ yet. */
+ for (auto & dep : flake.inputs) {
+ auto path = dep.second.computeStorePath(store);
+ if (store.isValidPath(path))
+ closure.insert(path);
+ queue.push(dep.second);
+ }
+ }
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+ if (closure.empty()) return;
- Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
- state.forceValue(*v);
+ /* Write the closure to a file in the store. */
+ auto closurePath = store.addTextToStore("flake-closure", concatStringsSep(" ", closure), closure);
- return v;
+ Path cacheDir = getCacheDir() + "/nix/flake-closures";
+ createDirs(cacheDir);
+
+ auto s = origFlakeRef.to_string();
+ assert(s[0] != '.');
+ s = replaceStrings(s, "%", "%25");
+ s = replaceStrings(s, "/", "%2f");
+ s = replaceStrings(s, ":", "%3a");
+ Path symlink = cacheDir + "/" + s;
+ debug("writing GC root '%s' for flake closure of '%s'", symlink, origFlakeRef);
+ replaceSymlink(closurePath, symlink);
+ store.addIndirectRoot(symlink);
+}
+
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
+
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
+
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::ResolvedFlake & resFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, resFlake, *vFlake);
+
+ makeFlakeClosureGCRoot(*state.store, flakeRef, resFlake);
+
+ 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 resFlake = resolveFlake(*state, flakeRef, cmd.getLockFileMode());
+
+ Value * vOutputs = nullptr;
+
+ auto emptyArgs = state->allocBindings(0);
+
+ auto & evalCache = flake::EvalCache::singleton();
+
+ auto fingerprint = resFlake.getFingerprint();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto drv = evalCache.getDerivation(fingerprint, attrPath);
+ if (drv) {
+ if (state->store->isValidPath(drv->drvPath))
+ return {attrPath, resFlake.flake.sourceInfo.resolvedRef, *drv};
+ }
+
+ if (!vOutputs)
+ vOutputs = getFlakeOutputs(*state, resFlake);
+
+ try {
+ auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs);
+ 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{
+ drvInfo->queryDrvPath(),
+ drvInfo->queryOutPath(),
+ drvInfo->queryOutputName()
+ };
+
+ evalCache.addDerivation(fingerprint, attrPath, drv);
+
+ return {attrPath, resFlake.flake.sourceInfo.resolvedRef, drv};
+ } catch (AttrPathNotFound & e) {
+ }
}
-};
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
+{
+ return {std::get<2>(toDerivation())};
+}
+
+Value * InstallableFlake::toValue(EvalState & state)
+{
+ auto resFlake = resolveFlake(state, flakeRef, cmd.getLockFileMode());
+
+ auto vOutputs = getFlakeOutputs(state, resFlake);
+
+ auto emptyArgs = state.allocBindings(0);
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return v;
+ } 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 = {""};
- }
-
- for (auto & s : ss) {
+ if (file) {
+ // FIXME: backward compatibility hack
+ 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();
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
- else if (s.find("/") != std::string::npos) {
+ if (ss.empty())
+ ss = {""};
- auto path = store->toStorePath(store->followLinksToStore(s));
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s));
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(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<Path> {
+ 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) {
+
+ size_t hash;
+ std::optional<Path> storePath;
+
+ if (s.compare(0, 1, "(") == 0)
+ result.push_back(std::make_shared<InstallableExpr>(*this, s));
+
+ else 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("nixpkgs"),
+ Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}));
+ }
+
+ else if ((hash = s.rfind('#')) != std::string::npos)
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this,
+ FlakeRef(std::string(s, 0, hash), true),
+ std::string(s, hash + 1),
+ getDefaultFlakeAttrPathPrefixes()));
+
+ else {
+ try {
+ auto flakeRef = FlakeRef(s, true);
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this, std::move(flakeRef), getDefaultFlakeAttrPaths()));
+ } catch (...) {
+ if (s.find('/') != std::string::npos && (storePath = follow(s)))
+ result.push_back(std::make_shared<InstallableStorePath>(*storePath));
+ else
+ throw;
+ }
+ }
+ }
}
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();
}
@@ -285,7 +501,7 @@ Path 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();
}
@@ -316,12 +532,16 @@ PathSet toDerivations(ref<Store> store,
void InstallablesCommand::prepare()
{
- installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
+ if (_installables.empty() && !file && 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
new file mode 100644
index 000000000..9388c673e
--- /dev/null
+++ b/src/nix/installables.hh
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "util.hh"
+#include "flake/eval-cache.hh"
+
+#include <optional>
+
+namespace nix {
+
+struct Value;
+struct DrvInfo;
+class EvalState;
+class SourceExprCommand;
+
+struct Buildable
+{
+ Path drvPath; // may be empty
+ std::map<std::string, Path> outputs;
+};
+
+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() { }
+
+ virtual std::string what() = 0;
+
+ virtual Buildables toBuildables()
+ {
+ throw Error("argument '%s' cannot be built", what());
+ }
+
+ Buildable toBuildable();
+
+ App toApp(EvalState & state);
+
+ virtual Value * toValue(EvalState & state)
+ {
+ throw Error("argument '%s' cannot be evaluated", what());
+ }
+
+ /* Return a value only if this installable is a store path or a
+ symlink to it. */
+ virtual std::optional<Path> getStorePath()
+ {
+ return {};
+ }
+};
+
+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)
+ : InstallableValue(cmd), flakeRef(flakeRef), attrPaths(std::move(attrPaths))
+ { }
+
+ InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef,
+ std::string attrPath, Strings && prefixes)
+ : InstallableValue(cmd), flakeRef(flakeRef), attrPaths{attrPath},
+ prefixes(prefixes)
+ { }
+
+ std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
+
+ std::vector<std::string> getActualAttrPaths();
+
+ Value * getFlakeOutputs(EvalState & state, const flake::ResolvedFlake & resFlake);
+
+ std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> toDerivation();
+
+ std::vector<flake::EvalCache::Derivation> toDerivations() override;
+
+ Value * toValue(EvalState & state) override;
+};
+
+}
diff --git a/src/nix/local.mk b/src/nix/local.mk
index c09efd1fc..44a95f910 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -23,3 +23,5 @@ $(foreach name, \
nix-build nix-channel nix-collect-garbage nix-copy-closure nix-daemon nix-env nix-hash nix-instantiate nix-prefetch-url nix-shell nix-store, \
$(eval $(call install-symlink, nix, $(bindir)/$(name))))
$(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote))
+
+$(d)/flake.cc: $(d)/flake-template.nix.gen.hh
diff --git a/src/nix/log.cc b/src/nix/log.cc
index f07ec4e93..122a3d690 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -8,15 +8,6 @@ using namespace nix;
struct CmdLog : InstallableCommand
{
- CmdLog()
- {
- }
-
- std::string name() override
- {
- return "log";
- }
-
std::string description() override
{
return "show the build log of the specified packages or paths, if available";
@@ -68,4 +59,4 @@ struct CmdLog : InstallableCommand
}
};
-static RegisterCommand r1(make_ref<CmdLog>());
+static auto r1 = registerCommand<CmdLog>("log");
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index d089be42f..9408cc9da 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -100,11 +100,6 @@ struct CmdLsStore : StoreCommand, MixLs
};
}
- std::string name() override
- {
- return "ls-store";
- }
-
std::string description() override
{
return "show information about a store path";
@@ -136,11 +131,6 @@ struct CmdLsNar : Command, MixLs
};
}
- std::string name() override
- {
- return "ls-nar";
- }
-
std::string description() override
{
return "show information about the contents of a NAR file";
@@ -152,5 +142,5 @@ struct CmdLsNar : Command, MixLs
}
};
-static RegisterCommand r1(make_ref<CmdLsStore>());
-static RegisterCommand r2(make_ref<CmdLsNar>());
+static auto r1 = registerCommand<CmdLsStore>("ls-store");
+static auto r2 = registerCommand<CmdLsNar>("ls-nar");
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 1c9d909d8..5c0faf879 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -104,10 +104,20 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
"--help-config' for a list of configuration settings.\n";
}
+ void printHelp(const string & programName, std::ostream & out) override
+ {
+ MultiCommand::printHelp(programName, out);
+
+#if 0
+ out << "\nFor full documentation, run 'man " << programName << "' or 'man " << programName << "-<COMMAND>'.\n";
+#endif
+
+ std::cout << "\nNote: this program is EXPERIMENTAL and subject to change.\n";
+ }
+
void showHelpAndExit()
{
printHelp(programName, std::cout);
- std::cout << "\nNote: this program is EXPERIMENTAL and subject to change.\n";
throw Exit();
}
};
@@ -134,6 +144,7 @@ void mainWrapped(int argc, char * * argv)
verbosity = lvlWarn;
settings.verboseBuild = false;
+ evalSettings.pureEval = true;
NixArgs args;
diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc
index 16344ee14..5b99b5084 100644
--- a/src/nix/make-content-addressable.cc
+++ b/src/nix/make-content-addressable.cc
@@ -11,11 +11,6 @@ struct CmdMakeContentAddressable : StorePathsCommand
realiseMode = Build;
}
- std::string name() override
- {
- return "make-content-addressable";
- }
-
std::string description() override
{
return "rewrite a path or closure to content-addressable form";
@@ -92,4 +87,4 @@ struct CmdMakeContentAddressable : StorePathsCommand
}
};
-static RegisterCommand r1(make_ref<CmdMakeContentAddressable>());
+static auto r1 = registerCommand<CmdMakeContentAddressable>("make-content-addressable");
diff --git a/src/nix/optimise-store.cc b/src/nix/optimise-store.cc
index 725fb75a1..fed012b04 100644
--- a/src/nix/optimise-store.cc
+++ b/src/nix/optimise-store.cc
@@ -8,15 +8,6 @@ using namespace nix;
struct CmdOptimiseStore : StoreCommand
{
- CmdOptimiseStore()
- {
- }
-
- std::string name() override
- {
- return "optimise-store";
- }
-
std::string description() override
{
return "replace identical files in the store by hard links";
@@ -38,4 +29,4 @@ struct CmdOptimiseStore : StoreCommand
}
};
-static RegisterCommand r1(make_ref<CmdOptimiseStore>());
+static auto r1 = registerCommand<CmdOptimiseStore>("optimise-store");
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index dea5f0557..2cb718f12 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -24,11 +24,6 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
mkFlag(0, "sigs", "show signatures", &showSigs);
}
- std::string name() override
- {
- return "path-info";
- }
-
std::string description() override
{
return "query information about store paths";
@@ -130,4 +125,4 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
}
};
-static RegisterCommand r1(make_ref<CmdPathInfo>());
+static auto r1 = registerCommand<CmdPathInfo>("path-info");
diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc
index 310942574..3a2e542a3 100644
--- a/src/nix/ping-store.cc
+++ b/src/nix/ping-store.cc
@@ -6,11 +6,6 @@ using namespace nix;
struct CmdPingStore : StoreCommand
{
- std::string name() override
- {
- return "ping-store";
- }
-
std::string description() override
{
return "test whether a store can be opened";
@@ -32,4 +27,4 @@ struct CmdPingStore : StoreCommand
}
};
-static RegisterCommand r1(make_ref<CmdPingStore>());
+static auto r1 = registerCommand<CmdPingStore>("ping-store");
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
new file mode 100644
index 000000000..786ebddef
--- /dev/null
+++ b/src/nix/profile.cc
@@ -0,0 +1,424 @@
+#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
+{
+ PathSet 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((std::string) p);
+ element.active = e["active"];
+ if (e.value("uri", "") != "") {
+ element.source = ProfileElementSource{
+ FlakeRef(e["originalUri"]),
+ FlakeRef(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 = {drvInfo.queryOutPath()};
+ elements.emplace_back(std::move(element));
+ }
+ }
+ }
+
+ std::string toJSON() const
+ {
+ auto array = nlohmann::json::array();
+ for (auto & element : elements) {
+ auto paths = nlohmann::json::array();
+ for (auto & path : element.storePaths)
+ paths.push_back(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();
+ }
+
+ Path build(ref<Store> store)
+ {
+ auto tempDir = createTempDir();
+
+ ValidPathInfo info;
+
+ Packages pkgs;
+ for (auto & element : elements) {
+ for (auto & path : element.storePaths) {
+ if (element.active)
+ pkgs.emplace_back(path, true, 5);
+ info.references.insert(path);
+ }
+ }
+
+ buildProfile(tempDir, std::move(pkgs));
+
+ writeFile(tempDir + "/manifest.json", toJSON());
+
+ /* Add the symlink tree to the store. */
+ StringSink sink;
+ dumpPath(tempDir, sink);
+
+ info.narHash = hashString(htSHA256, *sink.s);
+ info.narSize = sink.s->size();
+ info.path = store->makeFixedOutputPath(true, info.narHash, "profile", info.references);
+ info.ca = makeFixedOutputCA(true, info.narHash);
+
+ store->addToStore(info, sink.s);
+
+ return 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);
+
+ PathSet pathsToBuild;
+
+ for (auto & installable : installables) {
+ if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) {
+ auto [attrPath, resolvedRef, drv] = installable2->toDerivation();
+
+ ProfileElement element;
+ element.storePaths = {drv.outPath}; // FIXME
+ element.source = ProfileElementSource{
+ installable2->flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.insert(makeDrvPathWithOutputs(drv.drvPath, {"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 ProfileElement & element, size_t pos, 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(*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(element, i, matchers))
+ newManifest.elements.push_back(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
+ PathSet pathsToBuild;
+
+ for (size_t i = 0; i < manifest.elements.size(); ++i) {
+ auto & element(manifest.elements[i]);
+ if (element.source
+ && !element.source->originalRef.isImmutable()
+ && matches(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 = {drv.outPath}; // FIXME
+ element.source = ProfileElementSource{
+ installable.flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.insert(makeDrvPathWithOutputs(drv.drvPath, {"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]);
+ std::cout << fmt("%d %s %s %s\n", i,
+ element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-",
+ element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
+ concatStringsSep(" ", 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/repl.cc b/src/nix/repl.cc
index f857b2e89..0fa1594cc 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -771,8 +771,6 @@ struct CmdRepl : StoreCommand, MixEvalArgs
expectArgs("files", &files);
}
- std::string name() override { return "repl"; }
-
std::string description() override
{
return "start an interactive environment for evaluating Nix expressions";
@@ -780,12 +778,13 @@ 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);
}
};
-static RegisterCommand r1(make_ref<CmdRepl>());
+static auto r1 = registerCommand<CmdRepl>("repl");
}
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 90b76d666..d444fd2eb 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -8,6 +8,7 @@
#include "fs-accessor.hh"
#include "progress-bar.hh"
#include "affinity.hh"
+#include "eval.hh"
#if __linux__
#include <sys/mount.h>
@@ -19,7 +20,44 @@ using namespace nix;
std::string chrootHelperName = "__run_in_chroot";
-struct CmdRun : InstallablesCommand
+struct RunCommon : virtual Command
+{
+ void runProgram(ref<Store> store,
+ const std::string & program,
+ const Strings & args)
+ {
+ stopProgressBar();
+
+ restoreSignals();
+
+ 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 (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());
+
+ throw SysError("could not execute chroot helper");
+ }
+
+ execvp(program.c_str(), stringsToCharPtrs(args).data());
+
+ throw SysError("unable to execute '%s'", program);
+ }
+};
+
+struct CmdRun : InstallablesCommand, RunCommon
{
std::vector<std::string> command = { "bash" };
StringSet keep, unset;
@@ -61,11 +99,6 @@ struct CmdRun : InstallablesCommand
.handler([&](std::vector<std::string> ss) { unset.insert(ss.front()); });
}
- std::string name() override
- {
- return "run";
- }
-
std::string description() override
{
return "run a shell in which the specified packages are available";
@@ -147,42 +180,65 @@ struct CmdRun : InstallablesCommand
setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1);
- std::string cmd = *command.begin();
Strings args;
for (auto & arg : command) args.push_back(arg);
- stopProgressBar();
+ runProgram(store, *command.begin(), args);
+ }
+};
- restoreSignals();
+static auto r1 = registerCommand<CmdRun>("run");
- restoreAffinity();
+struct CmdApp : InstallableCommand, RunCommon
+{
+ std::vector<std::string> args;
- /* 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>();
+ CmdApp()
+ {
+ expectArgs("args", &args);
+ }
- if (store2 && store->storeDir != store2->realStoreDir) {
- Strings helperArgs = { chrootHelperName, store->storeDir, store2->realStoreDir, cmd };
- for (auto & arg : args) helperArgs.push_back(arg);
+ std::string description() override
+ {
+ return "run a Nix application";
+ }
- execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To run Blender:",
+ "nix app blender-bin"
+ },
+ };
+ }
- throw SysError("could not execute chroot helper");
- }
+ 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);
- execvp(cmd.c_str(), stringsToCharPtrs(args).data());
+ Strings allArgs{app.program};
+ for (auto & i : args) allArgs.push_back(i);
- throw SysError("unable to exec '%s'", cmd);
+ runProgram(store, app.program, allArgs);
}
};
-static RegisterCommand r1(make_ref<CmdRun>());
+static auto r2 = registerCommand<CmdApp>("app");
void chrootHelper(int argc, char * * argv)
{
diff --git a/src/nix/search.cc b/src/nix/search.cc
index eb75493e4..caea25cdc 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -52,11 +52,6 @@ struct CmdSearch : SourceExprCommand, MixJSON
.handler([&]() { writeCache = false; useCache = false; });
}
- std::string name() override
- {
- return "search";
- }
-
std::string description() override
{
return "query available packages";
@@ -253,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
@@ -277,4 +274,4 @@ struct CmdSearch : SourceExprCommand, MixJSON
}
};
-static RegisterCommand r1(make_ref<CmdSearch>());
+static auto r1 = registerCommand<CmdSearch>("search");
diff --git a/src/nix/shell.cc b/src/nix/shell.cc
new file mode 100644
index 000000000..50d0f9c88
--- /dev/null
+++ b/src/nix/shell.cc
@@ -0,0 +1,317 @@
+#include "eval.hh"
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+#include "affinity.hh"
+#include "progress-bar.hh"
+
+#include <regex>
+
+using namespace nix;
+
+struct Var
+{
+ bool exported;
+ std::string value; // quoted string or array
+};
+
+struct BuildEnvironment
+{
+ std::map<std::string, Var> env;
+ std::string bashFunctions;
+};
+
+BuildEnvironment readEnvironment(const Path & path)
+{
+ BuildEnvironment res;
+
+ std::set<std::string> exported;
+
+ auto file = readFile(path);
+
+ auto pos = file.cbegin();
+
+ static std::string varNameRegex =
+ R"re((?:[a-zA-Z_][a-zA-Z0-9_]*))re";
+
+ static std::regex declareRegex(
+ "^declare -x (" + varNameRegex + ")" +
+ R"re((?:="((?:[^"\\]|\\.)*)")?\n)re");
+
+ static std::string simpleStringRegex =
+ R"re((?:[a-zA-Z0-9_/:\.\-1\+]*))re";
+
+ static std::string quotedStringRegex =
+ R"re((?:\$?'[^']*'))re";
+
+ static std::string arrayRegex =
+ R"re((?:\(( *\[[^\]]+\]="(?:[^"\\]|\\.)*")*\)))re";
+
+ static std::regex varRegex(
+ "^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + quotedStringRegex + "|" + arrayRegex + ")\n");
+
+ static std::regex functionRegex(
+ "^" + varNameRegex + " \\(\\) *\n");
+
+ while (pos != file.end()) {
+
+ std::smatch match;
+
+ if (std::regex_search(pos, file.cend(), match, declareRegex)) {
+ pos = match[0].second;
+ exported.insert(match[1]);
+ }
+
+ else if (std::regex_search(pos, file.cend(), match, varRegex)) {
+ pos = match[0].second;
+ res.env.insert({match[1], Var { (bool) exported.count(match[1]), match[2] }});
+ }
+
+ else if (std::regex_search(pos, file.cend(), match, functionRegex)) {
+ res.bashFunctions = std::string(pos, file.cend());
+ break;
+ }
+
+ else throw Error("shell environment '%s' has unexpected line '%s'",
+ path, file.substr(pos - file.cbegin(), 60));
+ }
+
+ return res;
+}
+
+/* Given an existing derivation, return the shell environment as
+ initialised by stdenv's setup script. We do this by building a
+ modified derivation with the same dependencies and nearly the same
+ initial environment variables, that just writes the resulting
+ environment to a file and exits. */
+Path getDerivationEnvironment(ref<Store> store, Derivation drv)
+{
+ auto builder = baseNameOf(drv.builder);
+ if (builder != "bash")
+ throw Error("'nix shell' only works on derivations that use 'bash' as their builder");
+
+ drv.args = {
+ "-c",
+ "set -e; "
+ "export IN_NIX_SHELL=impure; "
+ "export dontAddDisableDepTrack=1; "
+ "if [[ -n $stdenv ]]; then "
+ " source $stdenv/setup; "
+ "fi; "
+ "export > $out; "
+ "set >> $out "};
+
+ /* Remove derivation checks. */
+ drv.env.erase("allowedReferences");
+ drv.env.erase("allowedRequisites");
+ drv.env.erase("disallowedReferences");
+ drv.env.erase("disallowedRequisites");
+
+ // FIXME: handle structured attrs
+
+ /* Rehash and write the derivation. FIXME: would be nice to use
+ 'buildDerivation', but that's privileged. */
+ auto drvName = drv.env["name"] + "-env";
+ for (auto & output : drv.outputs)
+ drv.env.erase(output.first);
+ drv.env["out"] = "";
+ drv.env["outputs"] = "out";
+ drv.outputs["out"] = DerivationOutput("", "", "");
+ Hash h = hashDerivationModulo(*store, drv);
+ Path shellOutPath = store->makeOutputPath("out", h, drvName);
+ drv.outputs["out"].path = shellOutPath;
+ drv.env["out"] = shellOutPath;
+ Path shellDrvPath2 = writeDerivation(store, drv, drvName);
+
+ /* Build the derivation. */
+ store->buildPaths({shellDrvPath2});
+
+ assert(store->isValidPath(shellOutPath));
+
+ return shellOutPath;
+}
+
+struct Common : InstallableCommand, MixProfile
+{
+ /*
+ std::set<string> keepVars{
+ "DISPLAY",
+ "HOME",
+ "IN_NIX_SHELL",
+ "LOGNAME",
+ "NIX_BUILD_SHELL",
+ "PAGER",
+ "PATH",
+ "TERM",
+ "TZ",
+ "USER",
+ };
+ */
+
+ std::set<string> ignoreVars{
+ "BASHOPTS",
+ "EUID",
+ "HOME", // FIXME: don't ignore in pure mode?
+ "NIX_BUILD_TOP",
+ "NIX_ENFORCE_PURITY",
+ "NIX_LOG_FD",
+ "PPID",
+ "PWD",
+ "SHELLOPTS",
+ "SHLVL",
+ "SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
+ "TEMP",
+ "TEMPDIR",
+ "TERM",
+ "TMP",
+ "TMPDIR",
+ "TZ",
+ "UID",
+ };
+
+ void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
+ {
+ out << "nix_saved_PATH=\"$PATH\"\n";
+
+ for (auto & i : buildEnvironment.env) {
+ if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) {
+ out << fmt("%s=%s\n", i.first, i.second.value);
+ if (i.second.exported)
+ out << fmt("export %s\n", i.first);
+ }
+ }
+
+ out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
+
+ out << buildEnvironment.bashFunctions << "\n";
+
+ // FIXME: set outputs
+
+ out << "export NIX_BUILD_TOP=\"$(mktemp -d --tmpdir nix-shell.XXXXXX)\"\n";
+ for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
+ out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
+
+ out << "eval \"$shellHook\"\n";
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()};
+ }
+
+ Path getShellOutPath(ref<Store> store)
+ {
+ auto path = installable->getStorePath();
+ if (path && hasSuffix(*path, "-env"))
+ return *path;
+ else {
+ auto drvs = toDerivations(store, {installable});
+
+ if (drvs.size() != 1)
+ throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
+ installable->what(), drvs.size());
+
+ auto & drvPath = *drvs.begin();
+
+ return getDerivationEnvironment(store, store->derivationFromPath(drvPath));
+ }
+ }
+
+ BuildEnvironment getBuildEnvironment(ref<Store> store)
+ {
+ auto shellOutPath = getShellOutPath(store);
+
+ updateProfile(shellOutPath);
+
+ return readEnvironment(shellOutPath);
+ }
+};
+
+struct CmdDevShell : Common
+{
+ std::string description() override
+ {
+ return "run a bash shell that provides the build environment of a derivation";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To get the build environment of GNU 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"
+ },
+ Example{
+ "To use a build environment previously recorded in a profile:",
+ "nix dev-shell /tmp/my-shell"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto buildEnvironment = getBuildEnvironment(store);
+
+ auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
+
+ std::ostringstream ss;
+ makeRcScript(buildEnvironment, ss);
+
+ ss << fmt("rm -f '%s'\n", rcFilePath);
+
+ writeFull(rcFileFd.get(), ss.str());
+
+ stopProgressBar();
+
+ auto shell = getEnv("SHELL", "bash");
+
+ auto args = Strings{baseNameOf(shell), "--rcfile", rcFilePath};
+
+ restoreAffinity();
+ restoreSignals();
+
+ execvp(shell.c_str(), stringsToCharPtrs(args).data());
+
+ throw SysError("executing shell '%s'", shell);
+ }
+};
+
+struct CmdPrintDevEnv : Common
+{
+ std::string description() override
+ {
+ return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To apply the build environment of GNU hello to the current shell:",
+ ". <(nix print-dev-env nixpkgs:hello)"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto buildEnvironment = getBuildEnvironment(store);
+
+ stopProgressBar();
+
+ makeRcScript(buildEnvironment, std::cout);
+ }
+};
+
+static auto r1 = registerCommand<CmdPrintDevEnv>("print-dev-env");
+static auto r2 = registerCommand<CmdDevShell>("dev-shell");
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index 86638b50d..87544f937 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -8,15 +8,6 @@ using namespace nix;
struct CmdShowConfig : Command, MixJSON
{
- CmdShowConfig()
- {
- }
-
- std::string name() override
- {
- return "show-config";
- }
-
std::string description() override
{
return "show the Nix configuration";
@@ -37,4 +28,4 @@ struct CmdShowConfig : Command, MixJSON
}
};
-static RegisterCommand r1(make_ref<CmdShowConfig>());
+static auto r1 = registerCommand<CmdShowConfig>("show-config");
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index ee94fded3..6065adc4d 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -22,11 +22,6 @@ struct CmdShowDerivation : InstallablesCommand
.set(&recursive, true);
}
- std::string name() override
- {
- return "show-derivation";
- }
-
std::string description() override
{
return "show the contents of a store derivation";
@@ -116,4 +111,4 @@ struct CmdShowDerivation : InstallablesCommand
}
};
-static RegisterCommand r1(make_ref<CmdShowDerivation>());
+static auto r1 = registerCommand<CmdShowDerivation>("show-derivation");
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index b1825c412..23bc83ad0 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -22,11 +22,6 @@ struct CmdCopySigs : StorePathsCommand
.handler([&](std::vector<std::string> ss) { substituterUris.push_back(ss[0]); });
}
- std::string name() override
- {
- return "copy-sigs";
- }
-
std::string description() override
{
return "copy path signatures from substituters (like binary caches)";
@@ -93,7 +88,7 @@ struct CmdCopySigs : StorePathsCommand
}
};
-static RegisterCommand r1(make_ref<CmdCopySigs>());
+static auto r1 = registerCommand<CmdCopySigs>("copy-sigs");
struct CmdSignPaths : StorePathsCommand
{
@@ -109,11 +104,6 @@ struct CmdSignPaths : StorePathsCommand
.dest(&secretKeyFile);
}
- std::string name() override
- {
- return "sign-paths";
- }
-
std::string description() override
{
return "sign the specified paths";
@@ -146,4 +136,4 @@ struct CmdSignPaths : StorePathsCommand
}
};
-static RegisterCommand r3(make_ref<CmdSignPaths>());
+static auto r2 = registerCommand<CmdSignPaths>("sign-paths");
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 35c44a70c..13d8504a6 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -30,11 +30,6 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
.dest(&storePathsUrl);
}
- std::string name() override
- {
- return "upgrade-nix";
- }
-
std::string description() override
{
return "upgrade Nix to the latest stable version";
@@ -157,4 +152,4 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
}
};
-static RegisterCommand r1(make_ref<CmdUpgradeNix>());
+static auto r1 = registerCommand<CmdUpgradeNix>("upgrade-nix");
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 4b0f80c62..fa1414196 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -30,11 +30,6 @@ struct CmdVerify : StorePathsCommand
mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
}
- std::string name() override
- {
- return "verify";
- }
-
std::string description() override
{
return "verify the integrity of store paths";
@@ -180,4 +175,4 @@ struct CmdVerify : StorePathsCommand
}
};
-static RegisterCommand r1(make_ref<CmdVerify>());
+static auto r1 = registerCommand<CmdVerify>("verify");
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 325a2be0a..3d13a77e4 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -44,11 +44,6 @@ struct CmdWhyDepends : SourceExprCommand
.set(&all, true);
}
- std::string name() override
- {
- return "why-depends";
- }
-
std::string description() override
{
return "show why a package has another package in its closure";
@@ -74,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(dependencyPath);
@@ -264,4 +259,4 @@ struct CmdWhyDepends : SourceExprCommand
}
};
-static RegisterCommand r1(make_ref<CmdWhyDepends>());
+static auto r1 = registerCommand<CmdWhyDepends>("why-depends");