aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2020-10-17 17:25:17 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2020-10-17 17:25:17 +0000
commit2546c63373149dea1511b892a9911768bd770cac (patch)
tree8766f3de7a9cd344e5b9067f83091ae7a28cfd0c /src/nix
parent7349f257da8278af9aae35544b15c9a204e2a57b (diff)
parentf66bbd8c7bb1472facf8917e58e3cd4f6ddfa1b5 (diff)
Merge commit 'f66bbd8c7bb1472facf8917e58e3cd4f6ddfa1b5' into auto-uid-allocation
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-to-store.cc35
-rw-r--r--src/nix/app.cc46
-rw-r--r--src/nix/build.cc45
-rw-r--r--src/nix/bundle.cc129
-rw-r--r--src/nix/cat.cc16
-rw-r--r--src/nix/command.cc46
-rw-r--r--src/nix/command.hh126
-rw-r--r--src/nix/copy.cc15
-rw-r--r--src/nix/describe-stores.cc44
-rw-r--r--src/nix/develop.cc191
-rw-r--r--src/nix/diff-closures.cc146
-rw-r--r--src/nix/doctor.cc6
-rw-r--r--src/nix/dump-path.cc2
-rw-r--r--src/nix/edit.cc5
-rw-r--r--src/nix/eval.cc38
-rw-r--r--src/nix/flake.cc968
-rw-r--r--src/nix/get-env.sh14
-rw-r--r--src/nix/hash.cc23
-rw-r--r--src/nix/installables.cc773
-rw-r--r--src/nix/installables.hh103
-rw-r--r--src/nix/local.mk4
-rw-r--r--src/nix/log.cc19
-rw-r--r--src/nix/ls.cc16
-rw-r--r--src/nix/main.cc54
-rw-r--r--src/nix/make-content-addressable.cc12
-rw-r--r--src/nix/markdown.cc50
-rw-r--r--src/nix/markdown.hh7
-rw-r--r--src/nix/optimise-store.cc2
-rw-r--r--src/nix/path-info.cc6
-rw-r--r--src/nix/ping-store.cc2
-rw-r--r--src/nix/profile.cc470
-rw-r--r--src/nix/registry.cc146
-rw-r--r--src/nix/repl.cc77
-rw-r--r--src/nix/run.cc84
-rw-r--r--src/nix/search.cc237
-rw-r--r--src/nix/show-config.cc8
-rw-r--r--src/nix/show-derivation.cc27
-rw-r--r--src/nix/sigs.cc7
-rw-r--r--src/nix/upgrade-nix.cc2
-rw-r--r--src/nix/verify.cc13
-rw-r--r--src/nix/why-depends.cc28
41 files changed, 3473 insertions, 569 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index f9d6de16e..7fe87d757 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -9,6 +9,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
{
Path path;
std::optional<std::string> namePart;
+ FileIngestionMethod ingestionMethod = FileIngestionMethod::Recursive;
CmdAddToStore()
{
@@ -21,6 +22,13 @@ struct CmdAddToStore : MixDryRun, StoreCommand
.labels = {"name"},
.handler = {&namePart},
});
+
+ addFlag({
+ .longName = "flat",
+ .shortName = 0,
+ .description = "add flat file to the Nix store",
+ .handler = {&ingestionMethod, FileIngestionMethod::Flat},
+ });
}
std::string description() override
@@ -28,6 +36,14 @@ struct CmdAddToStore : MixDryRun, StoreCommand
return "add a path to the Nix store";
}
+ std::string doc() override
+ {
+ return R"(
+ Copy the file or directory *path* to the Nix store, and
+ print the resulting store path on standard output.
+ )";
+ }
+
Examples examples() override
{
return {
@@ -45,12 +61,21 @@ struct CmdAddToStore : MixDryRun, StoreCommand
auto narHash = hashString(htSHA256, *sink.s);
- ValidPathInfo info(store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, *namePart));
- info.narHash = narHash;
+ Hash hash = narHash;
+ if (ingestionMethod == FileIngestionMethod::Flat) {
+ HashSink hsink(htSHA256);
+ readFile(path, hsink);
+ hash = hsink.finish().first;
+ }
+
+ ValidPathInfo info {
+ store->makeFixedOutputPath(ingestionMethod, hash, *namePart),
+ narHash,
+ };
info.narSize = sink.s->size();
info.ca = std::optional { FixedOutputHash {
- .method = FileIngestionMethod::Recursive,
- .hash = info.narHash,
+ .method = ingestionMethod,
+ .hash = hash,
} };
if (!dryRun) {
@@ -62,4 +87,4 @@ struct CmdAddToStore : MixDryRun, StoreCommand
}
};
-static auto r1 = registerCommand<CmdAddToStore>("add-to-store");
+static auto rCmdAddToStore = registerCommand<CmdAddToStore>("add-to-store");
diff --git a/src/nix/app.cc b/src/nix/app.cc
new file mode 100644
index 000000000..80acbf658
--- /dev/null
+++ b/src/nix/app.cc
@@ -0,0 +1,46 @@
+#include "installables.hh"
+#include "store-api.hh"
+#include "eval-inline.hh"
+#include "eval-cache.hh"
+#include "names.hh"
+
+namespace nix {
+
+App Installable::toApp(EvalState & state)
+{
+ auto [cursor, attrPath] = getCursor(state);
+
+ auto type = cursor->getAttr("type")->getString();
+
+ if (type == "app") {
+ auto [program, context] = cursor->getAttr("program")->getStringWithContext();
+
+ if (!state.store->isInStore(program))
+ throw Error("app program '%s' is not in the Nix store", program);
+
+ std::vector<StorePathWithOutputs> context2;
+ for (auto & [path, name] : context)
+ context2.push_back({state.store->parseStorePath(path), {name}});
+
+ return App {
+ .context = std::move(context2),
+ .program = program,
+ };
+ }
+
+ else if (type == "derivation") {
+ auto drvPath = cursor->forceDerivation();
+ auto outPath = cursor->getAttr(state.sOutPath)->getString();
+ auto outputName = cursor->getAttr(state.sOutputName)->getString();
+ auto name = cursor->getAttr(state.sName)->getString();
+ return App {
+ .context = { { drvPath, {outputName} } },
+ .program = outPath + "/bin/" + DrvName(name).name,
+ };
+ }
+
+ else
+ throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
+}
+
+}
diff --git a/src/nix/build.cc b/src/nix/build.cc
index 850e09ce8..d85a482db 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"
@@ -8,6 +9,7 @@ using namespace nix;
struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
{
Path outLink = "result";
+ BuildMode buildMode = bmNormal;
CmdBuild()
{
@@ -17,6 +19,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
.description = "path of the symlink to the build result",
.labels = {"path"},
.handler = {&outLink},
+ .completer = completePath
});
addFlag({
@@ -24,6 +27,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
.description = "do not create a symlink to the build result",
.handler = {&outLink, Path("")},
});
+
+ addFlag({
+ .longName = "rebuild",
+ .description = "rebuild an already built package and compare the result to the existing store paths",
+ .handler = {&buildMode, bmCheck},
+ });
}
std::string description() override
@@ -44,31 +53,39 @@ 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"
},
};
}
void run(ref<Store> store) override
{
- auto buildables = build(store, dryRun ? DryRun : Build, installables);
+ auto buildables = build(store, dryRun ? Realise::Nothing : Realise::Outputs, installables, buildMode);
if (dryRun) return;
- 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);
- }
- }
- }
+ if (outLink != "")
+ if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
+ for (size_t i = 0; i < buildables.size(); ++i)
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ std::string symlink = outLink;
+ if (i) symlink += fmt("-%d", i);
+ store2->addPermRoot(bo.path, absPath(symlink));
+ },
+ [&](BuildableFromDrv bfd) {
+ auto builtOutputs = store->queryDerivationOutputMap(bfd.drvPath);
+ for (auto & output : builtOutputs) {
+ 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));
+ }
+ },
+ }, buildables[i]);
updateProfile(buildables);
}
};
-static auto r1 = registerCommand<CmdBuild>("build");
+static auto rCmdBuild = registerCommand<CmdBuild>("build");
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
new file mode 100644
index 000000000..fc41da9e4
--- /dev/null
+++ b/src/nix/bundle.cc
@@ -0,0 +1,129 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+
+using namespace nix;
+
+struct CmdBundle : InstallableCommand
+{
+ std::string bundler = "github:matthewbauer/nix-bundle";
+ std::optional<Path> outLink;
+
+ CmdBundle()
+ {
+ addFlag({
+ .longName = "bundler",
+ .description = "use custom bundler",
+ .labels = {"flake-url"},
+ .handler = {&bundler},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getStore(), prefix);
+ }}
+ });
+
+ addFlag({
+ .longName = "out-link",
+ .shortName = 'o',
+ .description = "path of the symlink to the build result",
+ .labels = {"path"},
+ .handler = {&outLink},
+ .completer = completePath
+ });
+ }
+
+ std::string description() override
+ {
+ return "bundle an application so that it works outside of the Nix store";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To bundle Hello:",
+ "nix bundle hello"
+ },
+ };
+ }
+
+ Category category() override { return catSecondary; }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ Strings res{"defaultApp." + settings.thisSystem.get()};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
+ res.push_back(s);
+ return res;
+ }
+
+ Strings getDefaultFlakeAttrPathPrefixes() override
+ {
+ Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
+ res.push_back(s);
+ return res;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ auto app = installable->toApp(*evalState);
+ store->buildPaths(app.context);
+
+ auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
+ const flake::LockFlags lockFlags{ .writeLockFile = false };
+ auto bundler = InstallableFlake(
+ evalState, std::move(bundlerFlakeRef),
+ Strings{bundlerName == "" ? "defaultBundler" : bundlerName},
+ Strings({"bundlers."}), lockFlags);
+
+ Value * arg = evalState->allocValue();
+ evalState->mkAttrs(*arg, 2);
+
+ PathSet context;
+ for (auto & i : app.context)
+ context.insert("=" + store->printStorePath(i.path));
+ mkString(*evalState->allocAttr(*arg, evalState->symbols.create("program")), app.program, context);
+
+ mkString(*evalState->allocAttr(*arg, evalState->symbols.create("system")), settings.thisSystem.get());
+
+ arg->attrs->sort();
+
+ auto vRes = evalState->allocValue();
+ evalState->callFunction(*bundler.toValue(*evalState).first, *arg, *vRes, noPos);
+
+ if (!evalState->isDerivation(*vRes))
+ throw Error("the bundler '%s' does not produce a derivation", bundler.what());
+
+ auto attr1 = vRes->attrs->get(evalState->sDrvPath);
+ if (!attr1)
+ throw Error("the bundler '%s' does not produce a derivation", bundler.what());
+
+ PathSet context2;
+ StorePath drvPath = store->parseStorePath(evalState->coerceToPath(*attr1->pos, *attr1->value, context2));
+
+ auto attr2 = vRes->attrs->get(evalState->sOutPath);
+ if (!attr2)
+ throw Error("the bundler '%s' does not produce a derivation", bundler.what());
+
+ StorePath outPath = store->parseStorePath(evalState->coerceToPath(*attr2->pos, *attr2->value, context2));
+
+ store->buildPaths({{drvPath}});
+
+ auto outPathS = store->printStorePath(outPath);
+
+ auto info = store->queryPathInfo(outPath);
+ if (!info->references.empty())
+ throw Error("'%s' has references; a bundler must not leave any references", outPathS);
+
+ if (!outLink)
+ outLink = baseNameOf(app.program);
+
+ store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink));
+ }
+};
+
+static auto r2 = registerCommand<CmdBundle>("bundle");
diff --git a/src/nix/cat.cc b/src/nix/cat.cc
index c82819af8..eef172cfc 100644
--- a/src/nix/cat.cc
+++ b/src/nix/cat.cc
@@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat
{
CmdCatStore()
{
- expectArg("path", &path);
+ expectArgs({
+ .label = "path",
+ .handler = {&path},
+ .completer = completePath
+ });
}
std::string description() override
@@ -47,7 +51,11 @@ struct CmdCatNar : StoreCommand, MixCat
CmdCatNar()
{
- expectArg("nar", &narPath);
+ expectArgs({
+ .label = "nar",
+ .handler = {&narPath},
+ .completer = completePath
+ });
expectArg("path", &path);
}
@@ -64,5 +72,5 @@ struct CmdCatNar : StoreCommand, MixCat
}
};
-static auto r1 = registerCommand<CmdCatStore>("cat-store");
-static auto r2 = registerCommand<CmdCatNar>("cat-nar");
+static auto rCmdCatStore = registerCommand<CmdCatStore>("cat-store");
+static auto rCmdCatNar = registerCommand<CmdCatNar>("cat-nar");
diff --git a/src/nix/command.cc b/src/nix/command.cc
index 3651a9e9c..ba7de9fdd 100644
--- a/src/nix/command.cc
+++ b/src/nix/command.cc
@@ -4,12 +4,25 @@
#include "nixexpr.hh"
#include "profiles.hh"
+#include <nlohmann/json.hpp>
+
extern char * * environ __attribute__((weak));
namespace nix {
Commands * RegisterCommand::commands = nullptr;
+void NixMultiCommand::printHelp(const string & programName, std::ostream & out)
+{
+ MultiCommand::printHelp(programName, out);
+}
+
+nlohmann::json NixMultiCommand::toJSON()
+{
+ // FIXME: use Command::toJSON() as well.
+ return MultiCommand::toJSON();
+}
+
StoreCommand::StoreCommand()
{
}
@@ -63,7 +76,7 @@ void StorePathsCommand::run(ref<Store> store)
}
else {
- for (auto & p : toStorePaths(store, realiseMode, installables))
+ for (auto & p : toStorePaths(store, realiseMode, operateOn, installables))
storePaths.push_back(p);
if (recursive) {
@@ -80,7 +93,7 @@ void StorePathsCommand::run(ref<Store> store)
void StorePathCommand::run(ref<Store> store)
{
- auto storePaths = toStorePaths(store, NoBuild, installables);
+ auto storePaths = toStorePaths(store, Realise::Nothing, operateOn, installables);
if (storePaths.size() != 1)
throw UsageError("this command requires exactly one store path");
@@ -108,6 +121,7 @@ MixProfile::MixProfile()
.description = "profile to update",
.labels = {"path"},
.handler = {&profile},
+ .completer = completePath
});
}
@@ -120,27 +134,35 @@ void MixProfile::updateProfile(const StorePath & storePath)
switchLink(profile2,
createGeneration(
ref<LocalFSStore>(store),
- profile2, store->printStorePath(storePath)));
+ profile2, storePath));
}
void MixProfile::updateProfile(const Buildables & buildables)
{
if (!profile) return;
- std::optional<StorePath> result;
+ std::vector<StorePath> 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;
- }
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ result.push_back(bo.path);
+ },
+ [&](BuildableFromDrv bfd) {
+ for (auto & output : bfd.outputs) {
+ /* Output path should be known because we just tried to
+ build it. */
+ assert(output.second);
+ result.push_back(*output.second);
+ }
+ },
+ }, buildable);
}
- if (!result)
- throw Error("'--profile' requires that the arguments produce a single store path, but there are none");
+ if (result.size() != 1)
+ throw Error("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
- updateProfile(*result);
+ updateProfile(result[0]);
}
MixDefaultProfile::MixDefaultProfile()
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 959d5f19d..d60c8aeb6 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -4,16 +4,30 @@
#include "args.hh"
#include "common-eval-args.hh"
#include "path.hh"
-#include "eval.hh"
+#include "flake/lockfile.hh"
+#include "store-api.hh"
+
+#include <optional>
namespace nix {
extern std::string programPath;
+class EvalState;
+struct Pos;
+class Store;
+
static constexpr Command::Category catSecondary = 100;
static constexpr Command::Category catUtility = 101;
static constexpr Command::Category catNixInstallation = 102;
+struct NixMultiCommand : virtual MultiCommand, virtual Command
+{
+ void printHelp(const string & programName, std::ostream & out) override;
+
+ nlohmann::json toJSON() override;
+};
+
/* A command that requires a Nix store. */
struct StoreCommand : virtual Command
{
@@ -27,28 +41,64 @@ private:
std::shared_ptr<Store> _store;
};
-struct SourceExprCommand : virtual StoreCommand, MixEvalArgs
+struct EvalCommand : virtual StoreCommand, MixEvalArgs
+{
+ ref<EvalState> getEvalState();
+
+ std::shared_ptr<EvalState> evalState;
+};
+
+struct MixFlakeOptions : virtual Args, EvalCommand
+{
+ flake::LockFlags lockFlags;
+
+ MixFlakeOptions();
+
+ virtual std::optional<FlakeRef> getFlakeRefForCompletion()
+ { return {}; }
+};
+
+/* How to handle derivations in commands that operate on store paths. */
+enum class OperateOn {
+ /* Operate on the output path. */
+ Output,
+ /* Operate on the .drv path. */
+ Derivation
+};
+
+struct SourceExprCommand : virtual Args, MixFlakeOptions
{
- Path file;
+ std::optional<Path> file;
+ std::optional<std::string> expr;
+
+ // FIXME: move this; not all commands (e.g. 'nix run') use it.
+ OperateOn operateOn = OperateOn::Output;
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);
+ std::vector<std::shared_ptr<Installable>> parseInstallables(
+ ref<Store> store, std::vector<std::string> ss);
- ref<EvalState> getEvalState();
+ std::shared_ptr<Installable> parseInstallable(
+ ref<Store> store, const std::string & installable);
-private:
+ virtual Strings getDefaultFlakeAttrPaths();
- std::shared_ptr<EvalState> evalState;
+ virtual Strings getDefaultFlakeAttrPathPrefixes();
- RootValue vSourceExpr;
+ void completeInstallable(std::string_view prefix);
};
-enum RealiseMode { Build, NoBuild, DryRun };
+enum class Realise {
+ /* Build the derivation. Postcondition: the
+ derivation outputs exist. */
+ Outputs,
+ /* Don't build the derivation. Postcondition: the store derivation
+ exists. */
+ Derivation,
+ /* Evaluate in dry-run mode. Postcondition: nothing. */
+ Nothing
+};
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
@@ -56,15 +106,14 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
{
std::vector<std::shared_ptr<Installable>> installables;
- InstallablesCommand()
- {
- expectArgs("installables", &_installables);
- }
+ InstallablesCommand();
void prepare() override;
virtual bool useDefaultInstallables() { return true; }
+ std::optional<FlakeRef> getFlakeRefForCompletion() override;
+
private:
std::vector<std::string> _installables;
@@ -75,16 +124,18 @@ struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> installable;
- InstallableCommand()
- {
- expectArg("installable", &_installable);
- }
+ InstallableCommand();
void prepare() override;
+ std::optional<FlakeRef> getFlakeRefForCompletion() override
+ {
+ return parseFlakeRef(_installable, absPath("."));
+ }
+
private:
- std::string _installable;
+ std::string _installable{"."};
};
/* A command that operates on zero or more store paths. */
@@ -97,7 +148,7 @@ private:
protected:
- RealiseMode realiseMode = NoBuild;
+ Realise realiseMode = Realise::Derivation;
public:
@@ -141,17 +192,15 @@ 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, Realise mode,
+ std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode = bmNormal);
-Buildables build(ref<Store> store, RealiseMode mode,
+std::set<StorePath> toStorePaths(ref<Store> store,
+ Realise mode, OperateOn operateOn,
std::vector<std::shared_ptr<Installable>> installables);
-std::set<StorePath> toStorePaths(ref<Store> store, RealiseMode mode,
- std::vector<std::shared_ptr<Installable>> installables);
-
-StorePath toStorePath(ref<Store> store, RealiseMode mode,
+StorePath toStorePath(ref<Store> store,
+ Realise mode, OperateOn operateOn,
std::shared_ptr<Installable> installable);
std::set<StorePath> toDerivations(ref<Store> store,
@@ -194,4 +243,19 @@ struct MixEnvironment : virtual Args {
void setEnviron();
};
+void completeFlakeRef(ref<Store> store, std::string_view prefix);
+
+void completeFlakeRefWithFragment(
+ ref<EvalState> evalState,
+ flake::LockFlags lockFlags,
+ Strings attrPathPrefixes,
+ const Strings & defaultFlakeAttrPaths,
+ std::string_view prefix);
+
+void printClosureDiff(
+ ref<Store> store,
+ const StorePath & beforePath,
+ const StorePath & afterPath,
+ std::string_view indent);
+
}
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 64099f476..cb31aac8f 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -45,6 +45,8 @@ struct CmdCopy : StorePathsCommand
.description = "whether to try substitutes on the destination store (only supported by SSH)",
.handler = {&substitute, Substitute},
});
+
+ realiseMode = Realise::Outputs;
}
std::string description() override
@@ -70,11 +72,11 @@ struct CmdCopy : StorePathsCommand
#ifdef ENABLE_S3
Example{
"To copy Hello to an S3 binary cache:",
- "nix copy --to s3://my-bucket?region=eu-west-1 nixpkgs.hello"
+ "nix copy --to s3://my-bucket?region=eu-west-1 nixpkgs#hello"
},
Example{
"To copy Hello to an S3-compatible binary cache:",
- "nix copy --to s3://my-bucket?region=eu-west-1&endpoint=example.com nixpkgs.hello"
+ "nix copy --to s3://my-bucket?region=eu-west-1&endpoint=example.com nixpkgs#hello"
},
#endif
};
@@ -87,11 +89,16 @@ struct CmdCopy : StorePathsCommand
return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);
}
- void run(ref<Store> srcStore, StorePaths storePaths) override
+ void run(ref<Store> store) override
{
if (srcUri.empty() && dstUri.empty())
throw UsageError("you must pass '--from' and/or '--to'");
+ StorePathsCommand::run(store);
+ }
+
+ void run(ref<Store> srcStore, StorePaths storePaths) override
+ {
ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
copyPaths(srcStore, dstStore, StorePathSet(storePaths.begin(), storePaths.end()),
@@ -99,4 +106,4 @@ struct CmdCopy : StorePathsCommand
}
};
-static auto r1 = registerCommand<CmdCopy>("copy");
+static auto rCmdCopy = registerCommand<CmdCopy>("copy");
diff --git a/src/nix/describe-stores.cc b/src/nix/describe-stores.cc
new file mode 100644
index 000000000..1dd384c0e
--- /dev/null
+++ b/src/nix/describe-stores.cc
@@ -0,0 +1,44 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+#include <nlohmann/json.hpp>
+
+using namespace nix;
+
+struct CmdDescribeStores : Command, MixJSON
+{
+ std::string description() override
+ {
+ return "show registered store types and their available options";
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ auto res = nlohmann::json::object();
+ for (auto & implem : *Implementations::registered) {
+ auto storeConfig = implem.getConfig();
+ auto storeName = storeConfig->name();
+ res[storeName] = storeConfig->toJSON();
+ }
+ if (json) {
+ std::cout << res;
+ } else {
+ for (auto & [storeName, storeConfig] : res.items()) {
+ std::cout << "## " << storeName << std::endl << std::endl;
+ for (auto & [optionName, optionDesc] : storeConfig.items()) {
+ std::cout << "### " << optionName << std::endl << std::endl;
+ std::cout << optionDesc["description"].get<std::string>() << std::endl;
+ std::cout << "default: " << optionDesc["defaultValue"] << std::endl <<std::endl;
+ if (!optionDesc["aliases"].empty())
+ std::cout << "aliases: " << optionDesc["aliases"] << std::endl << std::endl;
+ }
+ }
+ }
+ }
+};
+
+static auto rDescribeStore = registerCommand<CmdDescribeStores>("describe-stores");
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index eb93f56fc..a46ea39b6 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -15,7 +15,7 @@ struct Var
{
bool exported = true;
bool associative = false;
- std::string value; // quoted string or array
+ std::string quoted; // quoted string or array
};
struct BuildEnvironment
@@ -68,22 +68,22 @@ BuildEnvironment readEnvironment(const Path & path)
std::smatch match;
- if (std::regex_search(pos, file.cend(), match, declareRegex)) {
+ if (std::regex_search(pos, file.cend(), match, declareRegex, std::regex_constants::match_continuous)) {
pos = match[0].second;
exported.insert(match[1]);
}
- else if (std::regex_search(pos, file.cend(), match, varRegex)) {
+ else if (std::regex_search(pos, file.cend(), match, varRegex, std::regex_constants::match_continuous)) {
pos = match[0].second;
- res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .value = match[2] }});
+ res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .quoted = match[2] }});
}
- else if (std::regex_search(pos, file.cend(), match, assocArrayRegex)) {
+ else if (std::regex_search(pos, file.cend(), match, assocArrayRegex, std::regex_constants::match_continuous)) {
pos = match[0].second;
- res.env.insert({match[1], Var { .associative = true, .value = match[2] }});
+ res.env.insert({match[1], Var { .associative = true, .quoted = match[2] }});
}
- else if (std::regex_search(pos, file.cend(), match, functionRegex)) {
+ else if (std::regex_search(pos, file.cend(), match, functionRegex, std::regex_constants::match_continuous)) {
res.bashFunctions = std::string(pos, file.cend());
break;
}
@@ -92,6 +92,8 @@ BuildEnvironment readEnvironment(const Path & path)
path, file.substr(pos - file.cbegin(), 60));
}
+ res.env.erase("__output");
+
return res;
}
@@ -124,27 +126,36 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
/* Rehash and write the derivation. FIXME: would be nice to use
'buildDerivation', but that's privileged. */
- auto drvName = std::string(drvPath.name());
- assert(hasSuffix(drvName, ".drv"));
- drvName.resize(drvName.size() - 4);
- drvName += "-env";
- for (auto & output : drv.outputs)
- drv.env.erase(output.first);
- drv.env["out"] = "";
- drv.env["outputs"] = "out";
+ drv.name += "-env";
+ for (auto & output : drv.outputs) {
+ output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
+ drv.env[output.first] = "";
+ }
drv.inputSrcs.insert(std::move(getEnvShPath));
- Hash h = hashDerivationModulo(*store, drv, true);
- auto shellOutPath = store->makeOutputPath("out", h, drvName);
- drv.outputs.insert_or_assign("out", DerivationOutput { .path = shellOutPath });
- drv.env["out"] = store->printStorePath(shellOutPath);
- auto shellDrvPath2 = writeDerivation(store, drv, drvName);
+ Hash h = std::get<0>(hashDerivationModulo(*store, drv, true));
- /* Build the derivation. */
- store->buildPaths({{shellDrvPath2}});
+ for (auto & output : drv.outputs) {
+ auto outPath = store->makeOutputPath(output.first, h, drv.name);
+ output.second = { .output = DerivationOutputInputAddressed { .path = outPath } };
+ drv.env[output.first] = store->printStorePath(outPath);
+ }
- assert(store->isValidPath(shellOutPath));
+ auto shellDrvPath = writeDerivation(*store, drv);
- return shellOutPath;
+ /* Build the derivation. */
+ store->buildPaths({{shellDrvPath}});
+
+ for (auto & [_0, outputAndOptPath] : drv.outputsAndOptPaths(*store)) {
+ auto & [_1, optPath] = outputAndOptPath;
+ assert(optPath);
+ auto & outPath = *optPath;
+ assert(store->isValidPath(outPath));
+ auto outPathS = store->toRealPath(outPath);
+ if (lstat(outPathS).st_size)
+ return outPath;
+ }
+
+ throw Error("get-env.sh failed to produce an environment");
}
struct Common : InstallableCommand, MixProfile
@@ -170,8 +181,12 @@ struct Common : InstallableCommand, MixProfile
"UID",
};
- void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
+ std::string makeRcScript(
+ const BuildEnvironment & buildEnvironment,
+ const Path & outputsDir = absPath(".") + "/outputs")
{
+ std::ostringstream out;
+
out << "unset shellHook\n";
out << "nix_saved_PATH=\"$PATH\"\n";
@@ -179,9 +194,9 @@ struct Common : InstallableCommand, MixProfile
for (auto & i : buildEnvironment.env) {
if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) {
if (i.second.associative)
- out << fmt("declare -A %s=(%s)\n", i.first, i.second.value);
+ out << fmt("declare -A %s=(%s)\n", i.first, i.second.quoted);
else {
- out << fmt("%s=%s\n", i.first, i.second.value);
+ out << fmt("%s=%s\n", i.first, i.second.quoted);
if (i.second.exported)
out << fmt("export %s\n", i.first);
}
@@ -192,13 +207,31 @@ struct Common : InstallableCommand, MixProfile
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";
+
+ /* Substitute occurrences of output paths. */
+ auto outputs = buildEnvironment.env.find("outputs");
+ assert(outputs != buildEnvironment.env.end());
+
+ // FIXME: properly unquote 'outputs'.
+ StringMap rewrites;
+ for (auto & outputName : tokenizeString<std::vector<std::string>>(replaceStrings(outputs->second.quoted, "'", ""))) {
+ auto from = buildEnvironment.env.find(outputName);
+ assert(from != buildEnvironment.env.end());
+ // FIXME: unquote
+ rewrites.insert({from->second.quoted, outputsDir + "/" + outputName});
+ }
+
+ return rewriteStrings(out.str(), rewrites);
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()};
}
StorePath getShellOutPath(ref<Store> store)
@@ -234,19 +267,57 @@ struct Common : InstallableCommand, MixProfile
struct CmdDevelop : Common, MixEnvironment
{
std::vector<std::string> command;
+ std::optional<std::string> phase;
CmdDevelop()
{
addFlag({
.longName = "command",
.shortName = 'c',
- .description = "command and arguments to be executed insted of an interactive shell",
+ .description = "command and arguments to be executed instead of an interactive shell",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
command = ss;
}}
});
+
+ addFlag({
+ .longName = "phase",
+ .description = "phase to run (e.g. `build` or `configure`)",
+ .labels = {"phase-name"},
+ .handler = {&phase},
+ });
+
+ addFlag({
+ .longName = "configure",
+ .description = "run the configure phase",
+ .handler = {&phase, {"configure"}},
+ });
+
+ addFlag({
+ .longName = "build",
+ .description = "run the build phase",
+ .handler = {&phase, {"build"}},
+ });
+
+ addFlag({
+ .longName = "check",
+ .description = "run the check phase",
+ .handler = {&phase, {"check"}},
+ });
+
+ addFlag({
+ .longName = "install",
+ .description = "run the install phase",
+ .handler = {&phase, {"install"}},
+ });
+
+ addFlag({
+ .longName = "installcheck",
+ .description = "run the installcheck phase",
+ .handler = {&phase, {"installCheck"}},
+ });
}
std::string description() override
@@ -259,11 +330,15 @@ struct CmdDevelop : Common, MixEnvironment
return {
Example{
"To get the build environment of GNU hello:",
- "nix develop nixpkgs.hello"
+ "nix develop nixpkgs#hello"
+ },
+ Example{
+ "To get the build environment of the default package of flake in the current directory:",
+ "nix develop"
},
Example{
"To store the build environment in a profile:",
- "nix develop --profile /tmp/my-shell nixpkgs.hello"
+ "nix develop --profile /tmp/my-shell nixpkgs#hello"
},
Example{
"To use a build environment previously recorded in a profile:",
@@ -278,28 +353,56 @@ struct CmdDevelop : Common, MixEnvironment
auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
- std::ostringstream ss;
- makeRcScript(buildEnvironment, ss);
+ auto script = makeRcScript(buildEnvironment);
+
+ if (verbosity >= lvlDebug)
+ script += "set -x\n";
- ss << fmt("rm -f '%s'\n", rcFilePath);
+ script += fmt("rm -f '%s'\n", rcFilePath);
- if (!command.empty()) {
+ if (phase) {
+ if (!command.empty())
+ throw UsageError("you cannot use both '--command' and '--phase'");
+ // FIXME: foundMakefile is set by buildPhase, need to get
+ // rid of that.
+ script += fmt("foundMakefile=1\n");
+ script += fmt("runHook %1%Phase\n", *phase);
+ script += fmt("exit 0\n", *phase);
+ }
+
+ else if (!command.empty()) {
std::vector<std::string> args;
for (auto s : command)
args.push_back(shellEscape(s));
- ss << fmt("exec %s\n", concatStringsSep(" ", args));
+ script += fmt("exec %s\n", concatStringsSep(" ", args));
}
- writeFull(rcFileFd.get(), ss.str());
+ writeFull(rcFileFd.get(), script);
stopProgressBar();
- auto shell = getEnv("SHELL").value_or("bash");
-
setEnviron();
// prevent garbage collection until shell exits
setenv("NIX_GCROOT", gcroot.data(), 1);
+ Path shell = "bash";
+
+ try {
+ auto state = getEvalState();
+
+ auto bashInstallable = std::make_shared<InstallableFlake>(
+ state,
+ installable->nixpkgsFlakeRef(),
+ Strings{"bashInteractive"},
+ Strings{"legacyPackages." + settings.thisSystem.get() + "."},
+ lockFlags);
+
+ shell = state->store->printStorePath(
+ toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash";
+ } catch (Error &) {
+ ignoreException();
+ }
+
auto args = Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
restoreAffinity();
@@ -323,7 +426,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)"
},
};
}
@@ -336,9 +439,9 @@ struct CmdPrintDevEnv : Common
stopProgressBar();
- makeRcScript(buildEnvironment, std::cout);
+ std::cout << makeRcScript(buildEnvironment);
}
};
-static auto r1 = registerCommand<CmdPrintDevEnv>("print-dev-env");
-static auto r2 = registerCommand<CmdDevelop>("develop");
+static auto rCmdPrintDevEnv = registerCommand<CmdPrintDevEnv>("print-dev-env");
+static auto rCmdDevelop = registerCommand<CmdDevelop>("develop");
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
new file mode 100644
index 000000000..30e7b20e1
--- /dev/null
+++ b/src/nix/diff-closures.cc
@@ -0,0 +1,146 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "common-args.hh"
+#include "names.hh"
+
+#include <regex>
+
+namespace nix {
+
+struct Info
+{
+ std::string outputName;
+};
+
+// name -> version -> store paths
+typedef std::map<std::string, std::map<std::string, std::map<StorePath, Info>>> GroupedPaths;
+
+GroupedPaths getClosureInfo(ref<Store> store, const StorePath & toplevel)
+{
+ StorePathSet closure;
+ store->computeFSClosure({toplevel}, closure);
+
+ GroupedPaths groupedPaths;
+
+ for (auto & path : closure) {
+ /* Strip the output name. Unfortunately this is ambiguous (we
+ can't distinguish between output names like "bin" and
+ version suffixes like "unstable"). */
+ static std::regex regex("(.*)-([a-z]+|lib32|lib64)");
+ std::smatch match;
+ std::string name(path.name());
+ std::string outputName;
+ if (std::regex_match(name, match, regex)) {
+ name = match[1];
+ outputName = match[2];
+ }
+
+ DrvName drvName(name);
+ groupedPaths[drvName.name][drvName.version].emplace(path, Info { .outputName = outputName });
+ }
+
+ return groupedPaths;
+}
+
+std::string showVersions(const std::set<std::string> & versions)
+{
+ if (versions.empty()) return "∅";
+ std::set<std::string> versions2;
+ for (auto & version : versions)
+ versions2.insert(version.empty() ? "ε" : version);
+ return concatStringsSep(", ", versions2);
+}
+
+void printClosureDiff(
+ ref<Store> store,
+ const StorePath & beforePath,
+ const StorePath & afterPath,
+ std::string_view indent)
+{
+ auto beforeClosure = getClosureInfo(store, beforePath);
+ auto afterClosure = getClosureInfo(store, afterPath);
+
+ std::set<std::string> allNames;
+ for (auto & [name, _] : beforeClosure) allNames.insert(name);
+ for (auto & [name, _] : afterClosure) allNames.insert(name);
+
+ for (auto & name : allNames) {
+ auto & beforeVersions = beforeClosure[name];
+ auto & afterVersions = afterClosure[name];
+
+ auto totalSize = [&](const std::map<std::string, std::map<StorePath, Info>> & versions)
+ {
+ uint64_t sum = 0;
+ for (auto & [_, paths] : versions)
+ for (auto & [path, _] : paths)
+ sum += store->queryPathInfo(path)->narSize;
+ return sum;
+ };
+
+ auto beforeSize = totalSize(beforeVersions);
+ auto afterSize = totalSize(afterVersions);
+ auto sizeDelta = (int64_t) afterSize - (int64_t) beforeSize;
+ auto showDelta = std::abs(sizeDelta) >= 8 * 1024;
+
+ std::set<std::string> removed, unchanged;
+ for (auto & [version, _] : beforeVersions)
+ if (!afterVersions.count(version)) removed.insert(version); else unchanged.insert(version);
+
+ std::set<std::string> added;
+ for (auto & [version, _] : afterVersions)
+ if (!beforeVersions.count(version)) added.insert(version);
+
+ if (showDelta || !removed.empty() || !added.empty()) {
+ std::vector<std::string> items;
+ if (!removed.empty() || !added.empty())
+ items.push_back(fmt("%s → %s", showVersions(removed), showVersions(added)));
+ if (showDelta)
+ items.push_back(fmt("%s%+.1f KiB" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, sizeDelta / 1024.0));
+ std::cout << fmt("%s%s: %s\n", indent, name, concatStringsSep(", ", items));
+ }
+ }
+}
+
+}
+
+using namespace nix;
+
+struct CmdDiffClosures : SourceExprCommand
+{
+ std::string _before, _after;
+
+ CmdDiffClosures()
+ {
+ expectArg("before", &_before);
+ expectArg("after", &_after);
+ }
+
+ std::string description() override
+ {
+ return "show what packages and versions were added and removed between two closures";
+ }
+
+ Category category() override { return catSecondary; }
+
+ Examples examples() override
+ {
+ return {
+ {
+ "To show what got added and removed between two versions of the NixOS system profile:",
+ "nix diff-closures /nix/var/nix/profiles/system-655-link /nix/var/nix/profiles/system-658-link",
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto before = parseInstallable(store, _before);
+ auto beforePath = toStorePath(store, Realise::Outputs, operateOn, before);
+ auto after = parseInstallable(store, _after);
+ auto afterPath = toStorePath(store, Realise::Outputs, operateOn, after);
+ printClosureDiff(store, beforePath, afterPath, "");
+ }
+};
+
+static auto rCmdDiffClosures = registerCommand<CmdDiffClosures>("diff-closures");
diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc
index 82e92cdd0..4588ac05e 100644
--- a/src/nix/doctor.cc
+++ b/src/nix/doctor.cc
@@ -49,9 +49,7 @@ struct CmdDoctor : StoreCommand
{
logger->log("Running checks against store uri: " + store->getUri());
- auto type = getStoreType();
-
- if (type < tOther) {
+ if (store.dynamic_pointer_cast<LocalFSStore>()) {
success &= checkNixInPath();
success &= checkProfileRoots(store);
}
@@ -133,4 +131,4 @@ struct CmdDoctor : StoreCommand
}
};
-static auto r1 = registerCommand<CmdDoctor>("doctor");
+static auto rCmdDoctor = registerCommand<CmdDoctor>("doctor");
diff --git a/src/nix/dump-path.cc b/src/nix/dump-path.cc
index e1de71bf8..6fd197531 100644
--- a/src/nix/dump-path.cc
+++ b/src/nix/dump-path.cc
@@ -30,4 +30,4 @@ struct CmdDumpPath : StorePathCommand
}
};
-static auto r1 = registerCommand<CmdDumpPath>("dump-path");
+static auto rDumpPath = registerCommand<CmdDumpPath>("dump-path");
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 067d3a973..51c16f5a9 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -20,7 +20,7 @@ struct CmdEdit : InstallableCommand
return {
Example{
"To open the Nix expression of the GNU Hello package:",
- "nix edit nixpkgs.hello"
+ "nix edit nixpkgs#hello"
},
};
}
@@ -45,6 +45,7 @@ struct CmdEdit : InstallableCommand
auto args = editorFor(pos);
+ restoreSignals();
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
std::string command;
@@ -53,4 +54,4 @@ struct CmdEdit : InstallableCommand
}
};
-static auto r1 = registerCommand<CmdEdit>("edit");
+static auto rCmdEdit = registerCommand<CmdEdit>("edit");
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 26e98ac2a..43ce46546 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -12,10 +12,18 @@ using namespace nix;
struct CmdEval : MixJSON, InstallableCommand
{
bool raw = false;
+ std::optional<std::string> apply;
CmdEval()
{
mkFlag(0, "raw", "print strings unquoted", &raw);
+
+ addFlag({
+ .longName = "apply",
+ .description = "apply a function to each argument",
+ .labels = {"expr"},
+ .handler = {&apply},
+ });
}
std::string description() override
@@ -26,21 +34,25 @@ struct CmdEval : MixJSON, InstallableCommand
Examples examples() override
{
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:",
- "nix eval -f channel:nixos-17.09 hello.name"
+ "nix eval -f ./my-nixpkgs hello.name"
},
- Example{
+ {
"To get the current version of Nixpkgs:",
- "nix eval --raw nixpkgs.lib.version"
+ "nix eval --raw nixpkgs#lib.version"
},
- Example{
+ {
"To print the store path of the Hello package:",
- "nix eval --raw nixpkgs.hello"
+ "nix eval --raw nixpkgs#hello"
+ },
+ {
+ "To get a list of checks in the 'nix' flake:",
+ "nix eval nix#checks.x86_64-linux --apply builtins.attrNames"
},
};
}
@@ -57,6 +69,14 @@ struct CmdEval : MixJSON, InstallableCommand
auto v = installable->toValue(*state).first;
PathSet context;
+ if (apply) {
+ auto vApply = state->allocValue();
+ state->eval(state->parseExprFromString(*apply, absPath(".")), *vApply);
+ auto vRes = state->allocValue();
+ state->callFunction(*vApply, *v, *vRes, noPos);
+ v = vRes;
+ }
+
if (raw) {
stopProgressBar();
std::cout << state->coerceToString(noPos, *v, context);
@@ -70,4 +90,4 @@ struct CmdEval : MixJSON, InstallableCommand
}
};
-static auto r1 = registerCommand<CmdEval>("eval");
+static auto rCmdEval = registerCommand<CmdEval>("eval");
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
new file mode 100644
index 000000000..d45f13029
--- /dev/null
+++ b/src/nix/flake.cc
@@ -0,0 +1,968 @@
+#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 "eval-cache.hh"
+
+#include <nlohmann/json.hpp>
+#include <queue>
+#include <iomanip>
+
+using namespace nix;
+using namespace nix::flake;
+
+class FlakeCommand : virtual Args, public MixFlakeOptions
+{
+ std::string flakeUrl = ".";
+
+public:
+
+ FlakeCommand()
+ {
+ expectArgs({
+ .label = "flake-url",
+ .optional = true,
+ .handler = {&flakeUrl},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getStore(), prefix);
+ }}
+ });
+ }
+
+ 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);
+ }
+
+ std::optional<FlakeRef> getFlakeRefForCompletion() override
+ {
+ return getFlakeRef();
+ }
+};
+
+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 (auto revCount = flake.lockedRef.input.getRevCount())
+ logger->stdout("Revisions: %s", *revCount);
+ if (auto lastModified = flake.lockedRef.input.getLastModified())
+ logger->stdout("Last modified: %s",
+ std::put_time(std::localtime(&*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());
+ if (auto rev = flake.lockedRef.input.getRev())
+ j["revision"] = rev->to_string(Base16, false);
+ if (auto revCount = flake.lockedRef.input.getRevCount())
+ j["revCount"] = *revCount;
+ if (auto lastModified = flake.lockedRef.input.getLastModified())
+ j["lastModified"] = *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("%s", 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("%s", flake.lockFile.toJson());
+ else {
+ logger->stdout("%s", flake.flake.lockedRef);
+
+ std::unordered_set<std::shared_ptr<Node>> visited;
+
+ 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)) {
+ bool last = i + 1 == node.inputs.size();
+
+ if (auto lockedNode = std::get_if<0>(&input.second)) {
+ logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
+ prefix + (last ? treeLast : treeConn), input.first,
+ *lockedNode ? (*lockedNode)->lockedRef : flake.flake.lockedRef);
+
+ bool firstVisit = visited.insert(*lockedNode).second;
+
+ if (firstVisit) recurse(**lockedNode, prefix + (last ? treeNull : treeLine));
+ } else if (auto follows = std::get_if<1>(&input.second)) {
+ logger->stdout("%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
+ prefix + (last ? treeLast : treeConn), input.first,
+ printInputPath(*follows));
+ }
+ }
+ };
+
+ visited.insert(flake.lockFile.root);
+ recurse(*flake.lockFile.root, "");
+ }
+ }
+};
+
+struct CmdFlakeCheck : FlakeCommand
+{
+ bool build = true;
+
+ CmdFlakeCheck()
+ {
+ addFlag({
+ .longName = "no-build",
+ .description = "do not build checks",
+ .handler = {&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();
+
+ // FIXME: rewrite to use EvalCache.
+
+ auto checkSystemName = [&](const std::string & system, const Pos & pos) {
+ // FIXME: what's the format of "system"?
+ if (system.find('-') == std::string::npos)
+ throw Error("'%s' is not a valid system type, at %s", system, pos);
+ };
+
+ 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.addTrace(pos, hintfmt("while checking the derivation '%s'", attrPath));
+ throw;
+ }
+ };
+
+ std::vector<StorePathWithOutputs> drvPaths;
+
+ auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ #if 0
+ // FIXME
+ auto app = App(*state, v);
+ for (auto & i : app.context) {
+ auto [drvPathS, outputName] = decodeContext(i);
+ store->parseStorePath(drvPathS);
+ }
+ #endif
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking the app definition '%s'", attrPath));
+ 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.addTrace(pos, hintfmt("while checking the overlay '%s'", attrPath));
+ 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.addTrace(*attr.pos, hintfmt("while evaluating the option '%s'", attr.name));
+ throw;
+ }
+ } else
+ throw Error("module must be a function or an attribute set");
+ // FIXME: if we have a 'nixpkgs' input, use it to
+ // check the module.
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking the NixOS module '%s'", attrPath));
+ 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.addTrace(pos, hintfmt("while checking the Hydra jobset '%s'", attrPath));
+ 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.addTrace(pos, hintfmt("while checking the NixOS configuration '%s'", attrPath));
+ throw;
+ }
+ };
+
+ auto checkTemplate = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("checking template '%s'", attrPath));
+
+ state->forceAttrs(v, pos);
+
+ if (auto attr = v.attrs->get(state->symbols.create("path"))) {
+ if (attr->name == state->symbols.create("path")) {
+ PathSet context;
+ auto path = state->coerceToPath(*attr->pos, *attr->value, context);
+ if (!store->isInStore(path))
+ throw Error("template '%s' has a bad 'path' attribute");
+ // TODO: recursively check the flake in 'path'.
+ }
+ } else
+ throw Error("template '%s' lacks attribute 'path'", attrPath);
+
+ if (auto attr = v.attrs->get(state->symbols.create("description")))
+ state->forceStringNoCtx(*attr->value, *attr->pos);
+ else
+ throw Error("template '%s' lacks attribute 'description'", attrPath);
+
+ for (auto & attr : *v.attrs) {
+ std::string name(attr.name);
+ if (name != "path" && name != "description")
+ throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
+ }
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
+ throw;
+ }
+ };
+
+ auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ state->forceValue(v, pos);
+ if (v.type != tLambda)
+ throw Error("bundler must be a function");
+ if (!v.lambda.fun->formals ||
+ v.lambda.fun->formals->argNames.find(state->symbols.create("program")) == v.lambda.fun->formals->argNames.end() ||
+ v.lambda.fun->formals->argNames.find(state->symbols.create("system")) == v.lambda.fun->formals->argNames.end())
+ throw Error("bundler must take formal arguments 'program' and 'system'");
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
+ 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.push_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 if (name == "defaultTemplate")
+ checkTemplate(name, vOutput, pos);
+
+ else if (name == "templates") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkTemplate(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
+ else if (name == "defaultBundler")
+ checkBundler(name, vOutput, pos);
+
+ else if (name == "bundlers") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkBundler(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
+ else
+ warn("unknown flake output '%s'", name);
+
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking flake output '%s'", name));
+ throw;
+ }
+ });
+ }
+
+ if (build && !drvPaths.empty()) {
+ Activity act(*logger, lvlInfo, actUnknown, "running flake checks");
+ store->buildPaths(drvPaths);
+ }
+ }
+};
+
+struct CmdFlakeInitCommon : virtual Args, EvalCommand
+{
+ std::string templateUrl = "templates";
+ Path destDir;
+
+ const Strings attrsPathPrefixes{"templates."};
+ const LockFlags lockFlags{ .writeLockFile = false };
+
+ CmdFlakeInitCommon()
+ {
+ addFlag({
+ .longName = "template",
+ .shortName = 't',
+ .description = "the template to use",
+ .labels = {"template"},
+ .handler = {&templateUrl},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRefWithFragment(
+ getEvalState(),
+ lockFlags,
+ attrsPathPrefixes,
+ {"defaultTemplate"},
+ prefix);
+ }}
+ });
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto flakeDir = absPath(destDir);
+
+ auto evalState = getEvalState();
+
+ auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
+
+ auto installable = InstallableFlake(
+ evalState, std::move(templateFlakeRef),
+ Strings{templateName == "" ? "defaultTemplate" : templateName},
+ Strings(attrsPathPrefixes), lockFlags);
+
+ auto [cursor, attrPath] = installable.getCursor(*evalState);
+
+ auto templateDir = cursor->getAttr("path")->getString();
+
+ assert(store->isInStore(templateDir));
+
+ std::vector<Path> files;
+
+ std::function<void(const Path & from, const Path & to)> copyDir;
+ copyDir = [&](const Path & from, const Path & to)
+ {
+ createDirs(to);
+
+ for (auto & entry : readDirectory(from)) {
+ auto from2 = from + "/" + entry.name;
+ auto to2 = to + "/" + entry.name;
+ auto st = lstat(from2);
+ if (S_ISDIR(st.st_mode))
+ copyDir(from2, to2);
+ else if (S_ISREG(st.st_mode)) {
+ auto contents = readFile(from2);
+ if (pathExists(to2)) {
+ auto contents2 = readFile(to2);
+ if (contents != contents2)
+ throw Error("refusing to overwrite existing file '%s'", to2);
+ } else
+ writeFile(to2, contents);
+ }
+ else if (S_ISLNK(st.st_mode)) {
+ auto target = readLink(from2);
+ if (pathExists(to2)) {
+ if (readLink(to2) != target)
+ throw Error("refusing to overwrite existing symlink '%s'", to2);
+ } else
+ createSymlink(target, to2);
+ }
+ else
+ throw Error("file '%s' has unsupported type", from2);
+ files.push_back(to2);
+ }
+ };
+
+ copyDir(templateDir, flakeDir);
+
+ if (pathExists(flakeDir + "/.git")) {
+ Strings args = { "-C", flakeDir, "add", "--intent-to-add", "--force", "--" };
+ for (auto & s : files) args.push_back(s);
+ runProgram("git", true, args);
+ }
+ }
+};
+
+struct CmdFlakeInit : CmdFlakeInitCommon
+{
+ std::string description() override
+ {
+ return "create a flake in the current directory from a template";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To create a flake using the default template:",
+ "nix flake init"
+ },
+ Example{
+ "To see available templates:",
+ "nix flake show templates"
+ },
+ Example{
+ "To create a flake from a specific template:",
+ "nix flake init -t templates#nixos-container"
+ },
+ };
+ }
+
+ CmdFlakeInit()
+ {
+ destDir = ".";
+ }
+};
+
+struct CmdFlakeNew : CmdFlakeInitCommon
+{
+ std::string description() override
+ {
+ return "create a flake in the specified directory from a template";
+ }
+
+ CmdFlakeNew()
+ {
+ expectArgs({
+ .label = "dest-dir",
+ .handler = {&destDir},
+ .completer = completePath
+ });
+ }
+};
+
+struct CmdFlakeClone : FlakeCommand
+{
+ Path destDir;
+
+ std::string description() override
+ {
+ return "clone flake repository";
+ }
+
+ CmdFlakeClone()
+ {
+ addFlag({
+ .longName = "dest",
+ .shortName = 'f',
+ .description = "destination path",
+ .labels = {"path"},
+ .handler = {&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()
+ {
+ addFlag({
+ .longName = "to",
+ .description = "URI of the destination Nix store",
+ .labels = {"store-uri"},
+ .handler = {&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);
+ 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 & [inputName, input] : node.inputs) {
+ if (auto inputNode = std::get_if<0>(&input)) {
+ auto jsonObj3 = jsonObj2 ? jsonObj2->object(inputName) : std::optional<JSONObject>();
+ auto storePath =
+ dryRun
+ ? (*inputNode)->lockedRef.input.computeStorePath(*store)
+ : (*inputNode)->lockedRef.input.fetch(store).first.storePath;
+ if (jsonObj3)
+ jsonObj3->attr("path", store->printStorePath(storePath));
+ sources.insert(std::move(storePath));
+ traverse(**inputNode, jsonObj3);
+ }
+ }
+ };
+
+ traverse(*flake.lockFile.root, jsonRoot);
+
+ if (!dryRun && !dstUri.empty()) {
+ ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
+ copyPaths(store, dstStore, sources);
+ }
+ }
+};
+
+struct CmdFlakeShow : FlakeCommand
+{
+ bool showLegacy = false;
+
+ CmdFlakeShow()
+ {
+ addFlag({
+ .longName = "legacy",
+ .description = "show the contents of the 'legacyPackages' output",
+ .handler = {&showLegacy, true}
+ });
+ }
+
+ std::string description() override
+ {
+ return "show the outputs provided by a flake";
+ }
+
+ void run(nix::ref<nix::Store> store) override
+ {
+ auto state = getEvalState();
+ auto flake = std::make_shared<LockedFlake>(lockFlake());
+
+ std::function<void(eval_cache::AttrCursor & visitor, const std::vector<Symbol> & attrPath, const std::string & headerPrefix, const std::string & nextPrefix)> visit;
+
+ visit = [&](eval_cache::AttrCursor & visitor, const std::vector<Symbol> & attrPath, const std::string & headerPrefix, const std::string & nextPrefix)
+ {
+ Activity act(*logger, lvlInfo, actUnknown,
+ fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
+ try {
+ auto recurse = [&]()
+ {
+ logger->stdout("%s", headerPrefix);
+ auto attrs = visitor.getAttrs();
+ for (const auto & [i, attr] : enumerate(attrs)) {
+ bool last = i + 1 == attrs.size();
+ auto visitor2 = visitor.getAttr(attr);
+ auto attrPath2(attrPath);
+ attrPath2.push_back(attr);
+ visit(*visitor2, attrPath2,
+ fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr),
+ nextPrefix + (last ? treeNull : treeLine));
+ }
+ };
+
+ auto showDerivation = [&]()
+ {
+ auto name = visitor.getAttr(state->sName)->getString();
+
+ /*
+ std::string description;
+
+ if (auto aMeta = visitor.maybeGetAttr("meta")) {
+ if (auto aDescription = aMeta->maybeGetAttr("description"))
+ description = aDescription->getString();
+ }
+ */
+
+ logger->stdout("%s: %s '%s'",
+ headerPrefix,
+ attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" :
+ attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" :
+ attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" :
+ "package",
+ name);
+ };
+
+ if (attrPath.size() == 0
+ || (attrPath.size() == 1 && (
+ attrPath[0] == "defaultPackage"
+ || attrPath[0] == "devShell"
+ || attrPath[0] == "nixosConfigurations"
+ || attrPath[0] == "nixosModules"
+ || attrPath[0] == "defaultApp"
+ || attrPath[0] == "templates"))
+ || ((attrPath.size() == 1 || attrPath.size() == 2)
+ && (attrPath[0] == "checks"
+ || attrPath[0] == "packages"
+ || attrPath[0] == "apps"))
+ )
+ {
+ recurse();
+ }
+
+ else if (
+ (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell"))
+ || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages"))
+ )
+ {
+ if (visitor.isDerivation())
+ showDerivation();
+ else
+ throw Error("expected a derivation");
+ }
+
+ else if (attrPath.size() > 0 && attrPath[0] == "hydraJobs") {
+ if (visitor.isDerivation())
+ showDerivation();
+ else
+ recurse();
+ }
+
+ else if (attrPath.size() > 0 && attrPath[0] == "legacyPackages") {
+ if (attrPath.size() == 1)
+ recurse();
+ else if (!showLegacy)
+ logger->stdout("%s: " ANSI_YELLOW "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix);
+ else {
+ if (visitor.isDerivation())
+ showDerivation();
+ else if (attrPath.size() <= 2)
+ // FIXME: handle recurseIntoAttrs
+ recurse();
+ }
+ }
+
+ else if (
+ (attrPath.size() == 2 && attrPath[0] == "defaultApp") ||
+ (attrPath.size() == 3 && attrPath[0] == "apps"))
+ {
+ auto aType = visitor.maybeGetAttr("type");
+ if (!aType || aType->getString() != "app")
+ throw EvalError("not an app definition");
+ logger->stdout("%s: app", headerPrefix);
+ }
+
+ else if (
+ (attrPath.size() == 1 && attrPath[0] == "defaultTemplate") ||
+ (attrPath.size() == 2 && attrPath[0] == "templates"))
+ {
+ auto description = visitor.getAttr("description")->getString();
+ logger->stdout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
+ }
+
+ else {
+ logger->stdout("%s: %s",
+ headerPrefix,
+ attrPath.size() == 1 && attrPath[0] == "overlay" ? "Nixpkgs overlay" :
+ attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? "NixOS configuration" :
+ attrPath.size() == 2 && attrPath[0] == "nixosModules" ? "NixOS module" :
+ ANSI_YELLOW "unknown" ANSI_NORMAL);
+ }
+ } catch (EvalError & e) {
+ if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
+ throw;
+ }
+ };
+
+ auto cache = openEvalCache(*state, flake);
+
+ visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
+ }
+};
+
+struct CmdFlake : NixMultiCommand
+{
+ CmdFlake()
+ : MultiCommand({
+ {"update", []() { return make_ref<CmdFlakeUpdate>(); }},
+ {"info", []() { return make_ref<CmdFlakeInfo>(); }},
+ {"list-inputs", []() { return make_ref<CmdFlakeListInputs>(); }},
+ {"check", []() { return make_ref<CmdFlakeCheck>(); }},
+ {"init", []() { return make_ref<CmdFlakeInit>(); }},
+ {"new", []() { return make_ref<CmdFlakeNew>(); }},
+ {"clone", []() { return make_ref<CmdFlakeClone>(); }},
+ {"archive", []() { return make_ref<CmdFlakeArchive>(); }},
+ {"show", []() { return make_ref<CmdFlakeShow>(); }},
+ })
+ {
+ }
+
+ std::string description() override
+ {
+ return "manage Nix flakes";
+ }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix flake' requires a sub-command.");
+ settings.requireExperimentalFeature("flakes");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdFlake = registerCommand<CmdFlake>("flake");
diff --git a/src/nix/get-env.sh b/src/nix/get-env.sh
index a25ec43a9..091c0f573 100644
--- a/src/nix/get-env.sh
+++ b/src/nix/get-env.sh
@@ -1,9 +1,19 @@
set -e
if [ -e .attrs.sh ]; then source .attrs.sh; fi
+
export IN_NIX_SHELL=impure
export dontAddDisableDepTrack=1
+
if [[ -n $stdenv ]]; then
source $stdenv/setup
fi
-export > $out
-set >> $out
+
+for __output in $outputs; do
+ if [[ -z $__done ]]; then
+ export > ${!__output}
+ set >> ${!__output}
+ __done=1
+ else
+ echo -n >> ${!__output}
+ fi
+done
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index b97c6d21f..1d23bb0e2 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -31,7 +31,11 @@ struct CmdHash : Command
.labels({"modulus"})
.dest(&modulus);
#endif
- expectArgs("paths", &paths);
+ expectArgs({
+ .label = "paths",
+ .handler = {&paths},
+ .completer = completePath
+ });
}
std::string description() override
@@ -40,6 +44,7 @@ struct CmdHash : Command
switch (mode) {
case FileIngestionMethod::Flat:
d = "print cryptographic hash of a regular file";
+ break;
case FileIngestionMethod::Recursive:
d = "print cryptographic hash of the NAR serialisation of a path";
};
@@ -74,8 +79,8 @@ struct CmdHash : Command
}
};
-static RegisterCommand r1("hash-file", [](){ return make_ref<CmdHash>(FileIngestionMethod::Flat); });
-static RegisterCommand r2("hash-path", [](){ return make_ref<CmdHash>(FileIngestionMethod::Recursive); });
+static RegisterCommand rCmdHashFile("hash-file", [](){ return make_ref<CmdHash>(FileIngestionMethod::Flat); });
+static RegisterCommand rCmdHashPath("hash-path", [](){ return make_ref<CmdHash>(FileIngestionMethod::Recursive); });
struct CmdToBase : Command
{
@@ -103,14 +108,14 @@ struct CmdToBase : Command
void run() override
{
for (auto s : args)
- logger->stdout(Hash(s, ht).to_string(base, base == SRI));
+ logger->stdout(Hash::parseAny(s, ht).to_string(base, base == 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); });
+static RegisterCommand rCmdToBase16("to-base16", [](){ return make_ref<CmdToBase>(Base16); });
+static RegisterCommand rCmdToBase32("to-base32", [](){ return make_ref<CmdToBase>(Base32); });
+static RegisterCommand rCmdToBase64("to-base64", [](){ return make_ref<CmdToBase>(Base64); });
+static RegisterCommand rCmdToSRI("to-sri", [](){ return make_ref<CmdToBase>(SRI); });
/* Legacy nix-hash command. */
static int compatNixHash(int argc, char * * argv)
@@ -162,4 +167,4 @@ static int compatNixHash(int argc, char * * argv)
return 0;
}
-static RegisterLegacyCommand s1("nix-hash", compatNixHash);
+static RegisterLegacyCommand r_nix_hash("nix-hash", compatNixHash);
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 708a0dc88..9bf6b7caa 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,80 +8,261 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
+#include "flake/flake.hh"
+#include "eval-cache.hh"
+#include "url.hh"
+#include "registry.hh"
#include <regex>
+#include <queue>
namespace nix {
+void completeFlakeInputPath(
+ ref<EvalState> evalState,
+ const FlakeRef & flakeRef,
+ std::string_view prefix)
+{
+ auto flake = flake::getFlake(*evalState, flakeRef, true);
+ for (auto & input : flake.inputs)
+ if (hasPrefix(input.first, prefix))
+ completions->insert(input.first);
+}
-SourceExprCommand::SourceExprCommand()
+MixFlakeOptions::MixFlakeOptions()
{
addFlag({
- .longName = "file",
- .shortName = 'f',
- .description = "evaluate FILE rather than the default",
- .labels = {"file"},
- .handler = {&file}
+ .longName = "recreate-lock-file",
+ .description = "recreate lock file from scratch",
+ .handler = {&lockFlags.recreateLockFile, true}
});
-}
-Value * SourceExprCommand::getSourceExpr(EvalState & state)
-{
- if (vSourceExpr) return *vSourceExpr;
+ addFlag({
+ .longName = "no-update-lock-file",
+ .description = "do not allow any updates to the lock file",
+ .handler = {&lockFlags.updateLockFile, false}
+ });
- auto sToplevel = state.symbols.create("_toplevel");
+ addFlag({
+ .longName = "no-write-lock-file",
+ .description = "do not write the newly generated lock file",
+ .handler = {&lockFlags.writeLockFile, false}
+ });
+
+ addFlag({
+ .longName = "no-registries",
+ .description = "don't use flake registries",
+ .handler = {&lockFlags.useRegistries, false}
+ });
- vSourceExpr = allocRootValue(state.allocValue());
+ addFlag({
+ .longName = "commit-lock-file",
+ .description = "commit changes to the lock file",
+ .handler = {&lockFlags.commitLockFile, true}
+ });
- if (file != "")
- state.evalFile(lookupFileArg(state, file), **vSourceExpr);
+ addFlag({
+ .longName = "update-input",
+ .description = "update a specific flake input",
+ .labels = {"input-path"},
+ .handler = {[&](std::string s) {
+ lockFlags.inputUpdates.insert(flake::parseInputPath(s));
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ if (auto flakeRef = getFlakeRefForCompletion())
+ completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
+ }}
+ });
- else {
+ addFlag({
+ .longName = "override-input",
+ .description = "override a specific flake input (e.g. `dwarffs/nixpkgs`)",
+ .labels = {"input-path", "flake-url"},
+ .handler = {[&](std::string inputPath, std::string flakeRef) {
+ lockFlags.inputOverrides.insert_or_assign(
+ flake::parseInputPath(inputPath),
+ parseFlakeRef(flakeRef, absPath(".")));
+ }}
+ });
- /* Construct the installation source from $NIX_PATH. */
+ addFlag({
+ .longName = "inputs-from",
+ .description = "use the inputs of the specified flake as registry entries",
+ .labels = {"flake-url"},
+ .handler = {[&](std::string flakeRef) {
+ auto evalState = getEvalState();
+ auto flake = flake::lockFlake(
+ *evalState,
+ parseFlakeRef(flakeRef, absPath(".")),
+ { .writeLockFile = false });
+ for (auto & [inputName, input] : flake.lockFile.root->inputs) {
+ auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
+ if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) {
+ overrideRegistry(
+ fetchers::Input::fromAttrs({{"type","indirect"}, {"id", inputName}}),
+ input3->lockedRef.input,
+ {});
+ }
+ }
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getEvalState()->store, prefix);
+ }}
+ });
+}
- auto searchPath = state.getSearchPath();
+SourceExprCommand::SourceExprCommand()
+{
+ addFlag({
+ .longName = "file",
+ .shortName = 'f',
+ .description = "evaluate *file* rather than the default",
+ .labels = {"file"},
+ .handler = {&file},
+ .completer = completePath
+ });
- state.mkAttrs(**vSourceExpr, 1024);
+ addFlag({
+ .longName ="expr",
+ .description = "evaluate attributes from *expr*",
+ .labels = {"expr"},
+ .handler = {&expr}
+ });
- mkBool(*state.allocAttr(**vSourceExpr, sToplevel), true);
+ addFlag({
+ .longName ="derivation",
+ .description = "operate on the store derivation rather than its outputs",
+ .handler = {&operateOn, OperateOn::Derivation},
+ });
+}
- std::unordered_set<std::string> seen;
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
+{
+ return {"defaultPackage." + settings.thisSystem.get()};
+}
- 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);
- };
+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() + "."
+ };
+}
+
+void SourceExprCommand::completeInstallable(std::string_view prefix)
+{
+ if (file) return; // FIXME
+
+ completeFlakeRefWithFragment(
+ getEvalState(),
+ lockFlags,
+ getDefaultFlakeAttrPathPrefixes(),
+ getDefaultFlakeAttrPaths(),
+ prefix);
+}
- 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();
+void completeFlakeRefWithFragment(
+ ref<EvalState> evalState,
+ flake::LockFlags lockFlags,
+ Strings attrPathPrefixes,
+ const Strings & defaultFlakeAttrPaths,
+ std::string_view prefix)
+{
+ /* Look for flake output attributes that match the
+ prefix. */
+ try {
+ auto hash = prefix.find('#');
+ if (hash != std::string::npos) {
+ auto fragment = prefix.substr(hash + 1);
+ auto flakeRefS = std::string(prefix.substr(0, hash));
+ // FIXME: do tilde expansion.
+ auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
+
+ auto evalCache = openEvalCache(*evalState,
+ std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
+
+ auto root = evalCache->getRoot();
+
+ /* Complete 'fragment' relative to all the
+ attrpath prefixes as well as the root of the
+ flake. */
+ attrPathPrefixes.push_back("");
+
+ for (auto & attrPathPrefixS : attrPathPrefixes) {
+ auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
+ auto attrPathS = attrPathPrefixS + std::string(fragment);
+ auto attrPath = parseAttrPath(*evalState, attrPathS);
+
+ std::string lastAttr;
+ if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
+ lastAttr = attrPath.back();
+ attrPath.pop_back();
+ }
+
+ auto attr = root->findAlongAttrPath(attrPath);
+ if (!attr) continue;
+
+ for (auto & attr2 : attr->getAttrs()) {
+ if (hasPrefix(attr2, lastAttr)) {
+ auto attrPath2 = attr->getAttrPath(attr2);
+ /* Strip the attrpath prefix. */
+ attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
+ completions->insert(flakeRefS + "#" + concatStringsSep(".", attrPath2));
+ }
+ }
+ }
+
+ /* And add an empty completion for the default
+ attrpaths. */
+ if (fragment.empty()) {
+ for (auto & attrPath : defaultFlakeAttrPaths) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
+ if (!attr) continue;
+ completions->insert(flakeRefS + "#");
+ }
+ }
+ }
+ } catch (Error & e) {
+ warn(e.msg());
}
- return *vSourceExpr;
+ completeFlakeRef(evalState->store, prefix);
}
-ref<EvalState> SourceExprCommand::getEvalState()
+ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState)
evalState = std::make_shared<EvalState>(searchPath, getStore());
return ref<EvalState>(evalState);
}
+void completeFlakeRef(ref<Store> store, std::string_view prefix)
+{
+ if (prefix == "")
+ completions->insert(".");
+
+ completeDir(0, prefix);
+
+ /* Look for registry entries that match the prefix. */
+ for (auto & registry : fetchers::getRegistries(store)) {
+ for (auto & entry : registry->entries) {
+ auto from = entry.from.to_string();
+ if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
+ std::string from2(from, 6);
+ if (hasPrefix(from2, prefix))
+ completions->insert(from2);
+ } else {
+ if (hasPrefix(from, prefix))
+ completions->insert(from);
+ }
+ }
+ }
+}
+
Buildable Installable::toBuildable()
{
auto buildables = toBuildables();
@@ -89,27 +271,54 @@ Buildable Installable::toBuildable()
return std::move(buildables[0]);
}
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+Installable::getCursors(EvalState & state)
+{
+ auto evalCache =
+ std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
+ [&]() { return toValue(state).first; });
+ return {{evalCache->getRoot(), ""}};
+}
+
+std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+Installable::getCursor(EvalState & state)
+{
+ auto cursors = getCursors(state);
+ if (cursors.empty())
+ throw Error("cannot find flake attribute '%s'", what());
+ return cursors[0];
+}
+
struct InstallableStorePath : Installable
{
ref<Store> store;
StorePath storePath;
- InstallableStorePath(ref<Store> store, const Path & storePath)
- : store(store), storePath(store->parseStorePath(storePath)) { }
+ InstallableStorePath(ref<Store> store, StorePath && storePath)
+ : store(store), storePath(std::move(storePath)) { }
std::string what() override { return store->printStorePath(storePath); }
Buildables toBuildables() override
{
- std::map<std::string, StorePath> outputs;
- outputs.insert_or_assign("out", storePath);
- Buildable b{
- .drvPath = storePath.isDerivation() ? storePath : std::optional<StorePath>(),
- .outputs = std::move(outputs)
- };
- Buildables bs;
- bs.push_back(std::move(b));
- return bs;
+ if (storePath.isDerivation()) {
+ std::map<std::string, std::optional<StorePath>> outputs;
+ auto drv = store->readDerivation(storePath);
+ for (auto & [name, output] : drv.outputsAndOptPaths(*store))
+ outputs.emplace(name, output.second);
+ return {
+ BuildableFromDrv {
+ .drvPath = storePath,
+ .outputs = std::move(outputs)
+ }
+ };
+ } else {
+ return {
+ BuildableOpaque {
+ .path = storePath,
+ }
+ };
+ }
}
std::optional<StorePath> getStorePath() override
@@ -118,146 +327,309 @@ struct InstallableStorePath : Installable
}
};
-struct InstallableValue : Installable
+Buildables InstallableValue::toBuildables()
+{
+ Buildables res;
+
+ std::map<StorePath, std::map<std::string, std::optional<StorePath>>> drvsToOutputs;
+
+ // Group by derivation, helps with .all in particular
+ for (auto & drv : toDerivations()) {
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
+ drvsToOutputs[drv.drvPath].insert_or_assign(outputName, drv.outPath);
+ }
+
+ for (auto & i : drvsToOutputs)
+ res.push_back(BuildableFromDrv { i.first, i.second });
+
+ return res;
+}
+
+struct InstallableAttrPath : InstallableValue
{
SourceExprCommand & cmd;
+ RootValue v;
+ std::string attrPath;
- InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+ InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
+ : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
+ { }
- Buildables toBuildables() override
+ std::string what() override { return attrPath; }
+
+ std::pair<Value *, Pos> toValue(EvalState & state) override
{
- auto state = cmd.getEvalState();
+ auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
+ state.forceValue(*vRes);
+ return {vRes, pos};
+ }
+
+ virtual std::vector<InstallableValue::DerivationInfo> toDerivations() override;
+};
- auto v = toValue(*state).first;
+std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations()
+{
+ auto v = toValue(*state).first;
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- Buildables res;
+ std::vector<DerivationInfo> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ state->store->parseStorePath(drvInfo.queryDrvPath()),
+ state->store->parseStorePath(drvInfo.queryOutPath()),
+ drvInfo.queryOutputName()
+ });
+ }
- StorePathSet drvPaths;
+ return res;
+}
- for (auto & drv : drvs) {
- Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())};
- drvPaths.insert(*b.drvPath);
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
- b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath()));
+ for (auto & s : attrPaths)
+ res.push_back(s);
- res.push_back(std::move(b));
- }
+ 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;
+}
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake)
+{
+ auto fingerprint = lockedFlake->getFingerprint();
+ return make_ref<nix::eval_cache::EvalCache>(
+ evalSettings.useEvalCache && evalSettings.pureEval
+ ? std::optional { std::cref(fingerprint) }
+ : std::nullopt,
+ state,
+ [&state, lockedFlake]()
+ {
+ /* For testing whether the evaluation cache is
+ complete. */
+ if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
+ throw Error("not everything is cached, but evaluation is not allowed");
+
+ auto vFlake = state.allocValue();
+ flake::callFlake(state, *lockedFlake, *vFlake);
+
+ state.forceAttrs(*vFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ return aOutputs->value;
+ });
+}
- // Hack to recognize .all: if all drvs have the same drvPath,
- // merge the buildables.
- if (drvPaths.size() == 1) {
- Buildable b{.drvPath = *drvPaths.begin()};
- for (auto & b2 : res)
- for (auto & output : b2.outputs)
- b.outputs.insert_or_assign(output.first, output.second);
- Buildables bs;
- bs.push_back(std::move(b));
- return bs;
- } else
- return res;
+static std::string showAttrPaths(const std::vector<std::string> & paths)
+{
+ std::string s;
+ for (const auto & [n, i] : enumerate(paths)) {
+ if (n > 0) s += n + 1 == paths.size() ? " or " : ", ";
+ s += '\''; s += i; s += '\'';
}
-};
+ return s;
+}
-struct InstallableExpr : InstallableValue
+std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
- std::string text;
+ auto lockedFlake = getLockedFlake();
- InstallableExpr(SourceExprCommand & cmd, const std::string & text)
- : InstallableValue(cmd), text(text) { }
+ auto cache = openEvalCache(*state, lockedFlake);
+ auto root = cache->getRoot();
- std::string what() override { return text; }
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
+ if (!attr) continue;
- std::pair<Value *, Pos> toValue(EvalState & state) override
- {
- auto v = state.allocValue();
- state.eval(state.parseExprFromString(text, absPath(".")), *v);
- return {v, noPos};
+ if (!attr->isDerivation())
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
+
+ auto drvPath = attr->forceDerivation();
+
+ auto drvInfo = DerivationInfo{
+ std::move(drvPath),
+ state->store->parseStorePath(attr->getAttr(state->sOutPath)->getString()),
+ attr->getAttr(state->sOutputName)->getString()
+ };
+
+ return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
}
-};
-struct InstallableAttrPath : InstallableValue
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, showAttrPaths(getActualAttrPaths()));
+}
+
+std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
{
- std::string attrPath;
+ std::vector<DerivationInfo> res;
+ res.push_back(std::get<2>(toDerivation()));
+ return res;
+}
- InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath)
- : InstallableValue(cmd), attrPath(attrPath)
- { }
+std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
+{
+ auto lockedFlake = getLockedFlake();
- std::string what() override { return attrPath; }
+ auto vOutputs = getFlakeOutputs(state, *lockedFlake);
- std::pair<Value *, Pos> toValue(EvalState & state) override
- {
- auto source = cmd.getSourceExpr(state);
+ 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, showAttrPaths(getActualAttrPaths()));
+}
+
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+InstallableFlake::getCursors(EvalState & state)
+{
+ auto evalCache = openEvalCache(state,
+ std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+ auto root = evalCache->getRoot();
- auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first;
- state.forceValue(*v);
+ std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res;
- return {v, noPos};
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
+ if (attr) res.push_back({attr, attrPath});
}
-};
-// FIXME: extend
-std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)";
-static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex));
+ return res;
+}
+
+std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
+{
+ if (!_lockedFlake)
+ _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags));
+ return _lockedFlake;
+}
-static std::vector<std::shared_ptr<Installable>> parseInstallables(
- SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables)
+FlakeRef InstallableFlake::nixpkgsFlakeRef() const
{
- std::vector<std::shared_ptr<Installable>> result;
+ auto lockedFlake = getLockedFlake();
- if (ss.empty() && useDefaultInstallables) {
- if (cmd.file == "")
- cmd.file = ".";
- ss = {""};
+ if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
+ if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
+ debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
+ return std::move(lockedNode->lockedRef);
+ }
}
- for (auto & s : ss) {
+ return Installable::nixpkgsFlakeRef();
+}
- if (s.compare(0, 1, "(") == 0)
- result.push_back(std::make_shared<InstallableExpr>(cmd, s));
+std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
+ ref<Store> store, std::vector<std::string> ss)
+{
+ std::vector<std::shared_ptr<Installable>> result;
- else if (s.find("/") != std::string::npos) {
+ if (file || expr) {
+ if (file && expr)
+ throw UsageError("'--file' and '--expr' are exclusive");
- auto path = store->toStorePath(store->followLinksToStore(s));
+ // FIXME: backward compatibility hack
+ if (file) evalSettings.pureEval = false;
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(store, path));
+ auto state = getEvalState();
+ auto vFile = state->allocValue();
+
+ if (file)
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
+ else {
+ auto e = state->parseExprFromString(*expr, absPath("."));
+ state->eval(e, *vFile);
}
- else if (s == "" || std::regex_match(s, attrPathRegex))
- result.push_back(std::make_shared<InstallableAttrPath>(cmd, s));
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
+
+ } else {
+
+ for (auto & s : ss) {
+ std::exception_ptr ex;
+
+ try {
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
+ result.push_back(std::make_shared<InstallableFlake>(
+ getEvalState(), std::move(flakeRef),
+ fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
+ getDefaultFlakeAttrPathPrefixes(), lockFlags));
+ continue;
+ } catch (...) {
+ ex = std::current_exception();
+ }
+
+ if (s.find('/') != std::string::npos) {
+ try {
+ result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s)));
+ continue;
+ } catch (BadStorePath &) {
+ } catch (...) {
+ if (!ex)
+ ex = std::current_exception();
+ }
+ }
- else
- throw UsageError("don't know what to do with argument '%s'", s);
+ std::rethrow_exception(ex);
+
+ /*
+ throw Error(
+ pathExists(s)
+ ? "path '%s' is not a flake or a store path"
+ : "don't know how to handle 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();
}
-Buildables build(ref<Store> store, RealiseMode mode,
- std::vector<std::shared_ptr<Installable>> installables)
+Buildables build(ref<Store> store, Realise mode,
+ std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode)
{
- if (mode != Build)
+ if (mode == Realise::Nothing)
settings.readOnlyMode = true;
Buildables buildables;
@@ -266,45 +638,70 @@ Buildables build(ref<Store> store, RealiseMode mode,
for (auto & i : installables) {
for (auto & b : i->toBuildables()) {
- if (b.drvPath) {
- StringSet outputNames;
- for (auto & output : b.outputs)
- outputNames.insert(output.first);
- pathsToBuild.push_back({*b.drvPath, outputNames});
- } else
- for (auto & output : b.outputs)
- pathsToBuild.push_back({output.second});
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ pathsToBuild.push_back({bo.path});
+ },
+ [&](BuildableFromDrv bfd) {
+ StringSet outputNames;
+ for (auto & output : bfd.outputs)
+ outputNames.insert(output.first);
+ pathsToBuild.push_back({bfd.drvPath, outputNames});
+ },
+ }, b);
buildables.push_back(std::move(b));
}
}
- if (mode == DryRun)
+ if (mode == Realise::Nothing)
printMissing(store, pathsToBuild, lvlError);
- else if (mode == Build)
- store->buildPaths(pathsToBuild);
+ else if (mode == Realise::Outputs)
+ store->buildPaths(pathsToBuild, bMode);
return buildables;
}
-StorePathSet toStorePaths(ref<Store> store, RealiseMode mode,
+StorePathSet toStorePaths(ref<Store> store,
+ Realise mode, OperateOn operateOn,
std::vector<std::shared_ptr<Installable>> installables)
{
StorePathSet outPaths;
- for (auto & b : build(store, mode, installables))
- for (auto & output : b.outputs)
- outPaths.insert(output.second);
+ if (operateOn == OperateOn::Output) {
+ for (auto & b : build(store, mode, installables))
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ outPaths.insert(bo.path);
+ },
+ [&](BuildableFromDrv bfd) {
+ for (auto & output : bfd.outputs) {
+ if (!output.second)
+ throw Error("Cannot operate on output of unbuilt CA drv");
+ outPaths.insert(*output.second);
+ }
+ },
+ }, b);
+ } else {
+ if (mode == Realise::Nothing)
+ settings.readOnlyMode = true;
+
+ for (auto & i : installables)
+ for (auto & b : i->toBuildables())
+ if (auto bfd = std::get_if<BuildableFromDrv>(&b))
+ outPaths.insert(bfd->drvPath);
+ }
return outPaths;
}
-StorePath toStorePath(ref<Store> store, RealiseMode mode,
+StorePath toStorePath(ref<Store> store,
+ Realise mode, OperateOn operateOn,
std::shared_ptr<Installable> installable)
{
- auto paths = toStorePaths(store, mode, {installable});
+ auto paths = toStorePaths(store, mode, operateOn, {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();
}
@@ -315,32 +712,70 @@ StorePathSet toDerivations(ref<Store> store,
StorePathSet drvPaths;
for (auto & i : installables)
- for (auto & b : i->toBuildables()) {
- if (!b.drvPath) {
- if (!useDeriver)
- throw Error("argument '%s' did not evaluate to a derivation", i->what());
- for (auto & output : b.outputs) {
- auto derivers = store->queryValidDerivers(output.second);
+ for (auto & b : i->toBuildables())
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ if (!useDeriver)
+ throw Error("argument '%s' did not evaluate to a derivation", i->what());
+ auto derivers = store->queryValidDerivers(bo.path);
if (derivers.empty())
throw Error("'%s' does not have a known deriver", i->what());
// FIXME: use all derivers?
drvPaths.insert(*derivers.begin());
- }
- } else
- drvPaths.insert(*b.drvPath);
- }
+ },
+ [&](BuildableFromDrv bfd) {
+ drvPaths.insert(bfd.drvPath);
+ },
+ }, b);
return drvPaths;
}
+InstallablesCommand::InstallablesCommand()
+{
+ expectArgs({
+ .label = "installables",
+ .handler = {&_installables},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeInstallable(prefix);
+ }}
+ });
+}
+
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);
+}
+
+std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
+{
+ if (_installables.empty()) {
+ if (useDefaultInstallables())
+ return parseFlakeRef(".", absPath("."));
+ return {};
+ }
+ return parseFlakeRef(_installables.front(), absPath("."));
+}
+
+InstallableCommand::InstallableCommand()
+{
+ expectArgs({
+ .label = "installable",
+ .optional = true,
+ .handler = {&_installable},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeInstallable(prefix);
+ }}
+ });
}
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..c7c2f8981 100644
--- a/src/nix/installables.hh
+++ b/src/nix/installables.hh
@@ -3,32 +3,52 @@
#include "util.hh"
#include "path.hh"
#include "eval.hh"
+#include "flake/flake.hh"
#include <optional>
namespace nix {
-struct Buildable
-{
- std::optional<StorePath> drvPath;
- std::map<std::string, StorePath> outputs;
+struct DrvInfo;
+struct SourceExprCommand;
+
+namespace eval_cache { class EvalCache; class AttrCursor; }
+
+struct BuildableOpaque {
+ StorePath path;
+};
+
+struct BuildableFromDrv {
+ StorePath drvPath;
+ std::map<std::string, std::optional<StorePath>> outputs;
};
+typedef std::variant<
+ BuildableOpaque,
+ BuildableFromDrv
+> Buildable;
+
typedef std::vector<Buildable> Buildables;
+struct App
+{
+ std::vector<StorePathWithOutputs> context;
+ Path program;
+ // FIXME: add args, sandbox settings, metadata, ...
+};
+
struct Installable
{
virtual ~Installable() { }
virtual std::string what() = 0;
- virtual Buildables toBuildables()
- {
- throw Error("argument '%s' cannot be built", what());
- }
+ virtual Buildables toBuildables() = 0;
Buildable toBuildable();
+ App toApp(EvalState & state);
+
virtual std::pair<Value *, Pos> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
@@ -40,6 +60,73 @@ struct Installable
{
return {};
}
+
+ virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ getCursors(EvalState & state);
+
+ std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+ getCursor(EvalState & state);
+
+ virtual FlakeRef nixpkgsFlakeRef() const
+ {
+ return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
+ }
};
+struct InstallableValue : Installable
+{
+ ref<EvalState> state;
+
+ InstallableValue(ref<EvalState> state) : state(state) {}
+
+ struct DerivationInfo
+ {
+ StorePath drvPath;
+ std::optional<StorePath> outPath;
+ std::string outputName;
+ };
+
+ virtual std::vector<DerivationInfo> toDerivations() = 0;
+
+ Buildables toBuildables() override;
+};
+
+struct InstallableFlake : InstallableValue
+{
+ FlakeRef flakeRef;
+ Strings attrPaths;
+ Strings prefixes;
+ const flake::LockFlags & lockFlags;
+ mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
+
+ InstallableFlake(ref<EvalState> state, FlakeRef && flakeRef,
+ Strings && attrPaths, Strings && prefixes, const flake::LockFlags & lockFlags)
+ : InstallableValue(state), flakeRef(flakeRef), attrPaths(attrPaths),
+ prefixes(prefixes), lockFlags(lockFlags)
+ { }
+
+ 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, DerivationInfo> toDerivation();
+
+ std::vector<DerivationInfo> toDerivations() override;
+
+ std::pair<Value *, Pos> toValue(EvalState & state) override;
+
+ std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ getCursors(EvalState & state) override;
+
+ std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
+
+ FlakeRef nixpkgsFlakeRef() const override;
+};
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake);
+
}
diff --git a/src/nix/local.mk b/src/nix/local.mk
index b057b7cc6..f37b73384 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -19,7 +19,7 @@ nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr
nix_LIBS = libexpr libmain libfetchers libstore libutil
-nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system
+nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -llowdown
$(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, \
@@ -29,3 +29,5 @@ $(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote))
src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
src/nix/develop.cc: src/nix/get-env.sh.gen.hh
+
+src/nix-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
diff --git a/src/nix/log.cc b/src/nix/log.cc
index 3fe22f6c2..33a3053f5 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -18,7 +18,7 @@ struct CmdLog : InstallableCommand
return {
Example{
"To get the build log of GNU Hello:",
- "nix log nixpkgs.hello"
+ "nix log nixpkgs#hello"
},
Example{
"To get the build log of a specific path:",
@@ -26,7 +26,7 @@ struct CmdLog : InstallableCommand
},
Example{
"To get a build log from a specific binary cache:",
- "nix log --store https://cache.nixos.org nixpkgs.hello"
+ "nix log --store https://cache.nixos.org nixpkgs#hello"
},
};
}
@@ -45,11 +45,14 @@ struct CmdLog : InstallableCommand
RunPager pager;
for (auto & sub : subs) {
- auto log = b.drvPath ? sub->getBuildLog(*b.drvPath) : nullptr;
- for (auto & output : b.outputs) {
- if (log) break;
- log = sub->getBuildLog(output.second);
- }
+ auto log = std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ return sub->getBuildLog(bo.path);
+ },
+ [&](BuildableFromDrv bfd) {
+ return sub->getBuildLog(bfd.drvPath);
+ },
+ }, b);
if (!log) continue;
stopProgressBar();
printInfo("got build log for '%s' from '%s'", installable->what(), sub->getUri());
@@ -61,4 +64,4 @@ struct CmdLog : InstallableCommand
}
};
-static auto r1 = registerCommand<CmdLog>("log");
+static auto rCmdLog = registerCommand<CmdLog>("log");
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index d2157f2d4..baca54431 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs
{
CmdLsStore()
{
- expectArg("path", &path);
+ expectArgs({
+ .label = "path",
+ .handler = {&path},
+ .completer = completePath
+ });
}
Examples examples() override
@@ -117,7 +121,11 @@ struct CmdLsNar : Command, MixLs
CmdLsNar()
{
- expectArg("nar", &narPath);
+ expectArgs({
+ .label = "nar",
+ .handler = {&narPath},
+ .completer = completePath
+ });
expectArg("path", &path);
}
@@ -144,5 +152,5 @@ struct CmdLsNar : Command, MixLs
}
};
-static auto r1 = registerCommand<CmdLsStore>("ls-store");
-static auto r2 = registerCommand<CmdLsNar>("ls-nar");
+static auto rCmdLsStore = registerCommand<CmdLsStore>("ls-store");
+static auto rCmdLsNar = registerCommand<CmdLsNar>("ls-nar");
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 203901168..1e9e07bc0 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -7,7 +7,6 @@
#include "legacy.hh"
#include "shared.hh"
#include "store-api.hh"
-#include "progress-bar.hh"
#include "filetransfer.hh"
#include "finally.hh"
#include "loggers.hh"
@@ -18,6 +17,8 @@
#include <netdb.h>
#include <netinet/in.h>
+#include <nlohmann/json.hpp>
+
extern std::string chrootHelperName;
void chrootHelper(int argc, char * * argv);
@@ -69,7 +70,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
.description = "show usage information",
- .handler = {[&]() { showHelpAndExit(); }},
+ .handler = {[&]() { if (!completions) showHelpAndExit(); }},
});
addFlag({
@@ -97,7 +98,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "version",
.description = "show version information",
- .handler = {[&]() { printVersion(programName); }},
+ .handler = {[&]() { if (!completions) printVersion(programName); }},
});
addFlag({
@@ -141,6 +142,11 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
printHelp(programName, std::cout);
throw Exit();
}
+
+ std::string description() override
+ {
+ return "a tool for reproducible and declarative configuration management";
+ }
};
void mainWrapped(int argc, char * * argv)
@@ -165,6 +171,7 @@ void mainWrapped(int argc, char * * argv)
verbosity = lvlWarn;
settings.verboseBuild = false;
+ evalSettings.pureEval = true;
setLogFormat("bar");
@@ -172,7 +179,46 @@ void mainWrapped(int argc, char * * argv)
NixArgs args;
- args.parseCmdline(argvToStrings(argc, argv));
+ if (argc == 2 && std::string(argv[1]) == "__dump-args") {
+ std::cout << args.toJSON().dump() << "\n";
+ return;
+ }
+
+ if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
+ evalSettings.pureEval = false;
+ EvalState state({}, openStore("dummy://"));
+ auto res = nlohmann::json::object();
+ auto builtins = state.baseEnv.values[0]->attrs;
+ for (auto & builtin : *builtins) {
+ auto b = nlohmann::json::object();
+ if (builtin.value->type != tPrimOp) continue;
+ auto primOp = builtin.value->primOp;
+ if (!primOp->doc) continue;
+ b["arity"] = primOp->arity;
+ b["args"] = primOp->args;
+ b["doc"] = trim(stripIndentation(primOp->doc));
+ res[(std::string) builtin.name] = std::move(b);
+ }
+ std::cout << res.dump() << "\n";
+ return;
+ }
+
+ Finally printCompletions([&]()
+ {
+ if (completions) {
+ std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n");
+ for (auto & s : *completions)
+ std::cout << s << "\n";
+ }
+ });
+
+ try {
+ args.parseCmdline(argvToStrings(argc, argv));
+ } catch (UsageError &) {
+ if (!completions) throw;
+ }
+
+ if (completions) return;
initPlugins();
diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc
index fb36fc410..df3ec5194 100644
--- a/src/nix/make-content-addressable.cc
+++ b/src/nix/make-content-addressable.cc
@@ -10,7 +10,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
{
CmdMakeContentAddressable()
{
- realiseMode = Build;
+ realiseMode = Realise::Outputs;
}
std::string description() override
@@ -23,7 +23,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
return {
Example{
"To create a content-addressable representation of GNU Hello (but not its dependencies):",
- "nix make-content-addressable nixpkgs.hello"
+ "nix make-content-addressable nixpkgs#hello"
},
Example{
"To compute a content-addressable representation of the current NixOS system closure:",
@@ -77,10 +77,12 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
auto narHash = hashModuloSink.finish().first;
- ValidPathInfo info(store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference));
+ ValidPathInfo info {
+ store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference),
+ narHash,
+ };
info.references = std::move(references);
if (hasSelfReference) info.references.insert(info.path);
- info.narHash = narHash;
info.narSize = sink.s->size();
info.ca = FixedOutputHash {
.method = FileIngestionMethod::Recursive,
@@ -106,4 +108,4 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
}
};
-static auto r1 = registerCommand<CmdMakeContentAddressable>("make-content-addressable");
+static auto rCmdMakeContentAddressable = registerCommand<CmdMakeContentAddressable>("make-content-addressable");
diff --git a/src/nix/markdown.cc b/src/nix/markdown.cc
new file mode 100644
index 000000000..40788a42f
--- /dev/null
+++ b/src/nix/markdown.cc
@@ -0,0 +1,50 @@
+#include "markdown.hh"
+#include "util.hh"
+#include "finally.hh"
+
+#include <sys/queue.h>
+extern "C" {
+#include <lowdown.h>
+}
+
+namespace nix {
+
+std::string renderMarkdownToTerminal(std::string_view markdown)
+{
+ struct lowdown_opts opts {
+ .type = LOWDOWN_TERM,
+ .maxdepth = 20,
+ .cols = std::min(getWindowSize().second, (unsigned short) 80),
+ .hmargin = 0,
+ .vmargin = 0,
+ .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
+ .oflags = 0,
+ };
+
+ auto doc = lowdown_doc_new(&opts);
+ if (!doc)
+ throw Error("cannot allocate Markdown document");
+ Finally freeDoc([&]() { lowdown_doc_free(doc); });
+
+ size_t maxn = 0;
+ auto node = lowdown_doc_parse(doc, &maxn, markdown.data(), markdown.size());
+ if (!node)
+ throw Error("cannot parse Markdown document");
+ Finally freeNode([&]() { lowdown_node_free(node); });
+
+ auto renderer = lowdown_term_new(&opts);
+ if (!renderer)
+ throw Error("cannot allocate Markdown renderer");
+ Finally freeRenderer([&]() { lowdown_term_free(renderer); });
+
+ auto buf = lowdown_buf_new(16384);
+ if (!buf)
+ throw Error("cannot allocate Markdown output buffer");
+ Finally freeBuffer([&]() { lowdown_buf_free(buf); });
+
+ lowdown_term_rndr(buf, nullptr, renderer, node);
+
+ return std::string(buf->data, buf->size);
+}
+
+}
diff --git a/src/nix/markdown.hh b/src/nix/markdown.hh
new file mode 100644
index 000000000..78320fcf5
--- /dev/null
+++ b/src/nix/markdown.hh
@@ -0,0 +1,7 @@
+#include "types.hh"
+
+namespace nix {
+
+std::string renderMarkdownToTerminal(std::string_view markdown);
+
+}
diff --git a/src/nix/optimise-store.cc b/src/nix/optimise-store.cc
index b45951879..51a7a9756 100644
--- a/src/nix/optimise-store.cc
+++ b/src/nix/optimise-store.cc
@@ -31,4 +31,4 @@ struct CmdOptimiseStore : StoreCommand
}
};
-static auto r1 = registerCommand<CmdOptimiseStore>("optimise-store");
+static auto rCmdOptimiseStore = registerCommand<CmdOptimiseStore>("optimise-store");
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index b89a44f83..63cf885f9 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -40,7 +40,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
},
Example{
"To show a package's closure size and all its dependencies with human readable sizes:",
- "nix path-info -rsSh nixpkgs.rust"
+ "nix path-info -rsSh nixpkgs#rust"
},
Example{
"To check the existence of a path in a binary cache:",
@@ -61,7 +61,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
};
}
- void printSize(unsigned long long value)
+ void printSize(uint64_t value)
{
if (!humanReadable) {
std::cout << fmt("\t%11d", value);
@@ -127,4 +127,4 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
}
};
-static auto r1 = registerCommand<CmdPathInfo>("path-info");
+static auto rCmdPathInfo = registerCommand<CmdPathInfo>("path-info");
diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc
index 127397a29..8db78d591 100644
--- a/src/nix/ping-store.cc
+++ b/src/nix/ping-store.cc
@@ -29,4 +29,4 @@ struct CmdPingStore : StoreCommand
}
};
-static auto r1 = registerCommand<CmdPingStore>("ping-store");
+static auto rCmdPingStore = registerCommand<CmdPingStore>("ping-store");
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
new file mode 100644
index 000000000..01aef2f9b
--- /dev/null
+++ b/src/nix/profile.cc
@@ -0,0 +1,470 @@
+#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 "profiles.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 = {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);
+ }
+ }
+
+ 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(FileIngestionMethod::Recursive, narHash, "profile", references),
+ narHash,
+ };
+ info.references = std::move(references);
+ info.narSize = sink.s->size();
+ info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash };
+
+ auto source = StringSource { *sink.s };
+ store->addToStore(info, source);
+
+ 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;
+ if (!drv.outPath)
+ throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
+ element.storePaths = {*drv.outPath}; // FIXME
+ element.source = ProfileElementSource{
+ installable2->flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.push_back({drv.drvPath, StringSet{"out"}}); // FIXME
+
+ manifest.elements.emplace_back(std::move(element));
+ } else
+ throw UnimplementedError("'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(getEvalState(), FlakeRef(element.source->originalRef), {element.source->attrPath}, {}, lockFlags);
+
+ 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);
+
+ if (!drv.outPath)
+ throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
+ element.storePaths = {*drv.outPath}; // FIXME
+ element.source = ProfileElementSource{
+ installable.flakeRef,
+ resolvedRef,
+ attrPath,
+ };
+
+ pathsToBuild.push_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 CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
+{
+ std::string description() override
+ {
+ return "show the closure difference between each generation of a profile";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To show what changed between each generation of the NixOS system profile:",
+ "nix profile diff-closure --profile /nix/var/nix/profiles/system"
+ },
+ };
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto [gens, curGen] = findGenerations(*profile);
+
+ std::optional<Generation> prevGen;
+ bool first = true;
+
+ for (auto & gen : gens) {
+ if (prevGen) {
+ if (!first) std::cout << "\n";
+ first = false;
+ std::cout << fmt("Generation %d -> %d:\n", prevGen->number, gen.number);
+ printClosureDiff(store,
+ store->followLinksToStorePath(prevGen->path),
+ store->followLinksToStorePath(gen.path),
+ " ");
+ }
+
+ prevGen = gen;
+ }
+ }
+};
+
+struct CmdProfile : NixMultiCommand
+{
+ CmdProfile()
+ : MultiCommand({
+ {"install", []() { return make_ref<CmdProfileInstall>(); }},
+ {"remove", []() { return make_ref<CmdProfileRemove>(); }},
+ {"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
+ {"info", []() { return make_ref<CmdProfileInfo>(); }},
+ {"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
+ })
+ { }
+
+ std::string description() override
+ {
+ return "manage Nix profiles";
+ }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix profile' requires a sub-command.");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdProfile = registerCommand<CmdProfile>("profile");
diff --git a/src/nix/registry.cc b/src/nix/registry.cc
new file mode 100644
index 000000000..8e8983ad0
--- /dev/null
+++ b/src/nix/registry.cc
@@ -0,0 +1,146 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "flake/flake.hh"
+#include "store-api.hh"
+#include "fetchers.hh"
+#include "registry.hh"
+
+using namespace nix;
+using namespace nix::flake;
+
+struct CmdRegistryList : StoreCommand
+{
+ 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.toURLString(),
+ entry.to.toURLString(attrsToQuery(entry.extraAttrs)));
+ }
+ }
+ }
+};
+
+struct CmdRegistryAdd : MixEvalArgs, Command
+{
+ std::string fromUrl, toUrl;
+
+ std::string description() override
+ {
+ return "add/replace flake in user flake registry";
+ }
+
+ CmdRegistryAdd()
+ {
+ 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 CmdRegistryRemove : virtual Args, MixEvalArgs, Command
+{
+ std::string url;
+
+ std::string description() override
+ {
+ return "remove flake from user flake registry";
+ }
+
+ CmdRegistryRemove()
+ {
+ expectArg("url", &url);
+ }
+
+ void run() override
+ {
+ auto userRegistry = fetchers::getUserRegistry();
+ userRegistry->remove(parseFlakeRef(url).input);
+ userRegistry->write(fetchers::getUserRegistryPath());
+ }
+};
+
+struct CmdRegistryPin : virtual Args, EvalCommand
+{
+ std::string url;
+
+ std::string description() override
+ {
+ return "pin a flake to its current version in user flake registry";
+ }
+
+ CmdRegistryPin()
+ {
+ 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.fetch(store);
+ fetchers::Attrs extraAttrs;
+ if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
+ userRegistry->add(ref.input, resolved, extraAttrs);
+ userRegistry->write(fetchers::getUserRegistryPath());
+ }
+};
+
+struct CmdRegistry : virtual NixMultiCommand
+{
+ CmdRegistry()
+ : MultiCommand({
+ {"list", []() { return make_ref<CmdRegistryList>(); }},
+ {"add", []() { return make_ref<CmdRegistryAdd>(); }},
+ {"remove", []() { return make_ref<CmdRegistryRemove>(); }},
+ {"pin", []() { return make_ref<CmdRegistryPin>(); }},
+ })
+ {
+ }
+
+ std::string description() override
+ {
+ return "manage the flake registry";
+ }
+
+ Category category() override { return catSecondary; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix registry' requires a sub-command.");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdRegistry = registerCommand<CmdRegistry>("registry");
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index fdacf604b..9ff386b1d 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -32,13 +32,19 @@ extern "C" {
#include "globals.hh"
#include "command.hh"
#include "finally.hh"
+#include "markdown.hh"
+#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
#include <gc/gc_cpp.h>
+#endif
namespace nix {
-struct NixRepl : gc
+struct NixRepl
+ #if HAVE_BOEHMGC
+ : gc
+ #endif
{
string curDir;
std::unique_ptr<EvalState> state;
@@ -59,7 +65,7 @@ struct NixRepl : gc
void mainLoop(const std::vector<std::string> & files);
StringSet completePrefix(string prefix);
bool getLine(string & input, const std::string &prompt);
- Path getDerivationPath(Value & v);
+ StorePath getDerivationPath(Value & v);
bool processLine(string line);
void loadFile(const Path & path);
void initEnv();
@@ -370,13 +376,16 @@ bool isVarName(const string & s)
}
-Path NixRepl::getDerivationPath(Value & v) {
+StorePath NixRepl::getDerivationPath(Value & v) {
auto drvInfo = getDerivation(*state, v, false);
if (!drvInfo)
throw Error("expression does not evaluate to a derivation, so I can't build it");
- Path drvPath = drvInfo->queryDrvPath();
- if (drvPath == "" || !state->store->isValidPath(state->store->parseStorePath(drvPath)))
- throw Error("expression did not evaluate to a valid derivation");
+ Path drvPathRaw = drvInfo->queryDrvPath();
+ if (drvPathRaw == "")
+ throw Error("expression did not evaluate to a valid derivation (no drv path)");
+ StorePath drvPath = state->store->parseStorePath(drvPathRaw);
+ if (!state->store->isValidPath(drvPath))
+ throw Error("expression did not evaluate to a valid derivation (invalid drv path)");
return drvPath;
}
@@ -411,7 +420,8 @@ bool NixRepl::processLine(string line)
<< " :r Reload all files\n"
<< " :s <expr> Build dependencies of derivation, then start nix-shell\n"
<< " :t <expr> Describe result of evaluation\n"
- << " :u <expr> Build derivation, then start nix-shell\n";
+ << " :u <expr> Build derivation, then start nix-shell\n"
+ << " :doc <expr> Show documentation of a builtin function\n";
}
else if (command == ":a" || command == ":add") {
@@ -469,29 +479,30 @@ bool NixRepl::processLine(string line)
evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
state->callFunction(f, v, result, Pos());
- Path drvPath = getDerivationPath(result);
- runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPath});
+ StorePath drvPath = getDerivationPath(result);
+ runProgram(settings.nixBinDir + "/nix-shell", Strings{state->store->printStorePath(drvPath)});
}
else if (command == ":b" || command == ":i" || command == ":s") {
Value v;
evalString(arg, v);
- Path drvPath = getDerivationPath(v);
+ StorePath drvPath = getDerivationPath(v);
+ Path drvPathRaw = state->store->printStorePath(drvPath);
if (command == ":b") {
/* We could do the build in this process using buildPaths(),
but doing it in a child makes it easier to recover from
problems / SIGINT. */
- if (runProgram(settings.nixBinDir + "/nix", Strings{"build", "--no-link", drvPath}) == 0) {
- auto drv = readDerivation(*state->store, drvPath);
+ if (runProgram(settings.nixBinDir + "/nix", Strings{"build", "--no-link", drvPathRaw}) == 0) {
+ auto drv = state->store->readDerivation(drvPath);
std::cout << std::endl << "this derivation produced the following outputs:" << std::endl;
- for (auto & i : drv.outputs)
- std::cout << fmt(" %s -> %s\n", i.first, state->store->printStorePath(i.second.path));
+ for (auto & i : drv.outputsAndOptPaths(*state->store))
+ std::cout << fmt(" %s -> %s\n", i.first, state->store->printStorePath(*i.second.second));
}
} else if (command == ":i") {
- runProgram(settings.nixBinDir + "/nix-env", Strings{"-i", drvPath});
+ runProgram(settings.nixBinDir + "/nix-env", Strings{"-i", drvPathRaw});
} else {
- runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPath});
+ runProgram(settings.nixBinDir + "/nix-shell", Strings{drvPathRaw});
}
}
@@ -504,6 +515,29 @@ bool NixRepl::processLine(string line)
else if (command == ":q" || command == ":quit")
return false;
+ else if (command == ":doc") {
+ Value v;
+ evalString(arg, v);
+ if (auto doc = state->getDoc(v)) {
+ std::string markdown;
+
+ if (!doc->args.empty() && doc->name) {
+ auto args = doc->args;
+ for (auto & arg : args)
+ arg = "*" + arg + "*";
+
+ markdown +=
+ "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
+ + concatStringsSep(" ", args) + "\n\n";
+ }
+
+ markdown += trim(stripIndentation(doc->doc));
+
+ std::cout << renderMarkdownToTerminal(markdown);
+ } else
+ throw Error("value does not have documentation");
+ }
+
else if (command != "")
throw Error("unknown command '%1%'", command);
@@ -760,7 +794,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs
CmdRepl()
{
- expectArgs("files", &files);
+ expectArgs({
+ .label = "files",
+ .handler = {&files},
+ .completer = completePath
+ });
}
std::string description() override
@@ -773,19 +811,20 @@ struct CmdRepl : StoreCommand, MixEvalArgs
return {
Example{
"Display all special commands within the REPL:",
- "nix repl\n nix-repl> :?"
+ "nix repl\nnix-repl> :?"
}
};
}
void run(ref<Store> store) override
{
+ evalSettings.pureEval = false;
auto repl = std::make_unique<NixRepl>(searchPath, openStore());
repl->autoArgs = getAutoArgs(*repl->state);
repl->mainLoop(files);
}
};
-static auto r1 = registerCommand<CmdRepl>("repl");
+static auto rCmdRepl = registerCommand<CmdRepl>("repl");
}
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 321ee1d11..790784382 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -84,31 +84,30 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
{
return {
Example{
- "To start a shell providing GNU Hello from NixOS 17.03:",
- "nix shell -f channel:nixos-17.03 hello"
+ "To start a shell providing GNU Hello from NixOS 20.03:",
+ "nix shell nixpkgs/nixos-20.03#hello"
},
Example{
"To start a shell providing youtube-dl from your 'nixpkgs' channel:",
- "nix shell nixpkgs.youtube-dl"
+ "nix shell nixpkgs#youtube-dl"
},
Example{
"To run GNU Hello:",
- "nix shell nixpkgs.hello -c hello --greeting 'Hi everybody!'"
+ "nix shell nixpkgs#hello -c hello --greeting 'Hi everybody!'"
},
Example{
"To run GNU Hello in a chroot store:",
- "nix shell --store ~/my-nix nixpkgs.hello -c hello"
+ "nix shell --store ~/my-nix nixpkgs#hello -c hello"
},
};
}
void run(ref<Store> store) override
{
- auto outPaths = toStorePaths(store, Build, installables);
+ auto outPaths = toStorePaths(store, Realise::Outputs, OperateOn::Output, installables);
auto accessor = store->getFSAccessor();
-
std::unordered_set<StorePath> done;
std::queue<StorePath> todo;
for (auto & path : outPaths) todo.push(path);
@@ -141,7 +140,76 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
}
};
-static auto r1 = registerCommand<CmdShell>("shell");
+static auto rCmdShell = registerCommand<CmdShell>("shell");
+
+struct CmdRun : InstallableCommand, RunCommon
+{
+ std::vector<std::string> args;
+
+ CmdRun()
+ {
+ expectArgs({
+ .label = "args",
+ .handler = {&args},
+ .completer = completePath
+ });
+ }
+
+ std::string description() override
+ {
+ return "run a Nix application";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To run Blender:",
+ "nix run blender-bin"
+ },
+ Example{
+ "To run vim from nixpkgs:",
+ "nix run nixpkgs#vim"
+ },
+ Example{
+ "To run vim from nixpkgs with arguments:",
+ "nix run nixpkgs#vim -- --help"
+ },
+ };
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ Strings res{"defaultApp." + settings.thisSystem.get()};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
+ res.push_back(s);
+ return res;
+ }
+
+ Strings getDefaultFlakeAttrPathPrefixes() override
+ {
+ Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
+ res.push_back(s);
+ return res;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+
+ auto app = installable->toApp(*state);
+
+ state->store->buildPaths(app.context);
+
+ Strings allArgs{app.program};
+ for (auto & i : args) allArgs.push_back(i);
+
+ runProgram(store, app.program, allArgs);
+ }
+};
+
+static auto rCmdRun = registerCommand<CmdRun>("run");
void chrootHelper(int argc, char * * argv)
{
diff --git a/src/nix/search.cc b/src/nix/search.cc
index 93c3f3f83..d4326dc84 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -6,8 +6,9 @@
#include "get-drvs.hh"
#include "common-args.hh"
#include "json.hh"
-#include "json-to-value.hh"
#include "shared.hh"
+#include "eval-cache.hh"
+#include "attr-path.hh"
#include <regex>
#include <fstream>
@@ -25,33 +26,17 @@ std::string hilite(const std::string & s, const std::smatch & m, std::string pos
m.empty()
? s
: std::string(m.prefix())
- + ANSI_RED + std::string(m.str()) + postfix
+ + ANSI_GREEN + std::string(m.str()) + postfix
+ std::string(m.suffix());
}
-struct CmdSearch : SourceExprCommand, MixJSON
+struct CmdSearch : InstallableCommand, MixJSON
{
std::vector<std::string> res;
- bool writeCache = true;
- bool useCache = true;
-
CmdSearch()
{
expectArgs("regex", &res);
-
- addFlag({
- .longName = "update-cache",
- .shortName = 'u',
- .description = "update the package search cache",
- .handler = {[&]() { writeCache = true; useCache = false; }}
- });
-
- addFlag({
- .longName = "no-cache",
- .description = "do not use or update the package search cache",
- .handler = {[&]() { writeCache = false; useCache = false; }}
- });
}
std::string description() override
@@ -63,24 +48,32 @@ struct CmdSearch : SourceExprCommand, MixJSON
{
return {
Example{
- "To show all available packages:",
+ "To show all packages in the flake in the current directory:",
"nix search"
},
Example{
- "To show any packages containing 'blender' in its name or description:",
- "nix search blender"
+ "To show packages in the 'nixpkgs' flake containing 'blender' in its name or description:",
+ "nix search nixpkgs blender"
},
Example{
"To search for Firefox or Chromium:",
- "nix search 'firefox|chromium'"
+ "nix search nixpkgs 'firefox|chromium'"
},
Example{
- "To search for git and frontend or gui:",
- "nix search git 'frontend|gui'"
+ "To search for packages containing 'git' and either 'frontend' or 'gui':",
+ "nix search nixpkgs git 'frontend|gui'"
}
};
}
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {
+ "packages." + settings.thisSystem.get() + ".",
+ "legacyPackages." + settings.thisSystem.get() + "."
+ };
+ }
+
void run(ref<Store> store) override
{
settings.readOnlyMode = true;
@@ -88,190 +81,108 @@ struct CmdSearch : SourceExprCommand, MixJSON
// Empty search string should match all packages
// Use "^" here instead of ".*" due to differences in resulting highlighting
// (see #1893 -- libc++ claims empty search string is not in POSIX grammar)
- if (res.empty()) {
+ if (res.empty())
res.push_back("^");
- }
std::vector<std::regex> regexes;
regexes.reserve(res.size());
- for (auto &re : res) {
+ for (auto & re : res)
regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
- }
auto state = getEvalState();
auto jsonOut = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
- auto sToplevel = state->symbols.create("_toplevel");
- auto sRecurse = state->symbols.create("recurseForDerivations");
-
- bool fromCache = false;
+ uint64_t results = 0;
- std::map<std::string, std::string> results;
-
- std::function<void(Value *, std::string, bool, JSONObject *)> doExpr;
-
- doExpr = [&](Value * v, std::string attrPath, bool toplevel, JSONObject * cache) {
- debug("at attribute '%s'", attrPath);
+ std::function<void(eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath)> visit;
+ visit = [&](eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath)
+ {
+ Activity act(*logger, lvlInfo, actUnknown,
+ fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
try {
- uint found = 0;
+ auto recurse = [&]()
+ {
+ for (const auto & attr : cursor.getAttrs()) {
+ auto cursor2 = cursor.getAttr(attr);
+ auto attrPath2(attrPath);
+ attrPath2.push_back(attr);
+ visit(*cursor2, attrPath2);
+ }
+ };
- state->forceValue(*v);
+ if (cursor.isDerivation()) {
+ size_t found = 0;
- if (v->type == tLambda && toplevel) {
- Value * v2 = state->allocValue();
- state->autoCallFunction(*state->allocBindings(1), *v, *v2);
- v = v2;
- state->forceValue(*v);
- }
+ DrvName name(cursor.getAttr("name")->getString());
- if (state->isDerivation(*v)) {
+ auto aMeta = cursor.maybeGetAttr("meta");
+ auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr;
+ auto description = aDescription ? aDescription->getString() : "";
+ std::replace(description.begin(), description.end(), '\n', ' ');
+ auto attrPath2 = concatStringsSep(".", attrPath);
- DrvInfo drv(*state, attrPath, v->attrs);
- std::string description;
std::smatch attrPathMatch;
std::smatch descriptionMatch;
std::smatch nameMatch;
- std::string name;
-
- DrvName parsed(drv.queryName());
-
- for (auto &regex : regexes) {
- std::regex_search(attrPath, attrPathMatch, regex);
- name = parsed.name;
- std::regex_search(name, nameMatch, regex);
-
- description = drv.queryMetaString("description");
- std::replace(description.begin(), description.end(), '\n', ' ');
+ for (auto & regex : regexes) {
+ std::regex_search(attrPath2, attrPathMatch, regex);
+ std::regex_search(name.name, nameMatch, regex);
std::regex_search(description, descriptionMatch, regex);
-
if (!attrPathMatch.empty()
|| !nameMatch.empty()
|| !descriptionMatch.empty())
- {
found++;
- }
}
if (found == res.size()) {
+ results++;
if (json) {
-
- auto jsonElem = jsonOut->object(attrPath);
-
- jsonElem.attr("pkgName", parsed.name);
- jsonElem.attr("version", parsed.version);
+ auto jsonElem = jsonOut->object(attrPath2);
+ jsonElem.attr("pname", name.name);
+ jsonElem.attr("version", name.version);
jsonElem.attr("description", description);
-
} else {
- auto name = hilite(parsed.name, nameMatch, "\e[0;2m")
- + std::string(parsed.fullName, parsed.name.length());
- results[attrPath] = fmt(
- "* %s (%s)\n %s\n",
- wrap("\e[0;1m", hilite(attrPath, attrPathMatch, "\e[0;1m")),
- wrap("\e[0;2m", hilite(name, nameMatch, "\e[0;2m")),
- hilite(description, descriptionMatch, ANSI_NORMAL));
- }
- }
-
- if (cache) {
- cache->attr("type", "derivation");
- cache->attr("name", drv.queryName());
- cache->attr("system", drv.querySystem());
- if (description != "") {
- auto meta(cache->object("meta"));
- meta.attr("description", description);
+ auto name2 = hilite(name.name, nameMatch, "\e[0;2m");
+ if (results > 1) logger->stdout("");
+ logger->stdout(
+ "* %s%s",
+ wrap("\e[0;1m", hilite(attrPath2, attrPathMatch, "\e[0;1m")),
+ name.version != "" ? " (" + name.version + ")" : "");
+ if (description != "")
+ logger->stdout(
+ " %s", hilite(description, descriptionMatch, ANSI_NORMAL));
}
}
}
- else if (v->type == tAttrs) {
+ else if (
+ attrPath.size() == 0
+ || (attrPath[0] == "legacyPackages" && attrPath.size() <= 2)
+ || (attrPath[0] == "packages" && attrPath.size() <= 2))
+ recurse();
- if (!toplevel) {
- auto attrs = v->attrs;
- Bindings::iterator j = attrs->find(sRecurse);
- if (j == attrs->end() || !state->forceBool(*j->value, *j->pos)) {
- debug("skip attribute '%s'", attrPath);
- return;
- }
- }
-
- bool toplevel2 = false;
- if (!fromCache) {
- Bindings::iterator j = v->attrs->find(sToplevel);
- toplevel2 = j != v->attrs->end() && state->forceBool(*j->value, *j->pos);
- }
-
- for (auto & i : *v->attrs) {
- auto cache2 =
- cache ? std::make_unique<JSONObject>(cache->object(i.name)) : nullptr;
- doExpr(i.value,
- attrPath == "" ? (std::string) i.name : attrPath + "." + (std::string) i.name,
- toplevel2 || fromCache, cache2 ? cache2.get() : nullptr);
- }
+ else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) {
+ auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations);
+ if (attr && attr->getBool())
+ recurse();
}
- } catch (AssertionError & e) {
- } catch (Error & e) {
- if (!toplevel) {
- e.addTrace(std::nullopt, "While evaluating the attribute '%s'", attrPath);
+ } catch (EvalError & e) {
+ if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages"))
throw;
- }
}
};
- Path jsonCacheFileName = getCacheDir() + "/nix/package-search.json";
-
- if (useCache && pathExists(jsonCacheFileName)) {
-
- warn("using cached results; pass '-u' to update the cache");
-
- Value vRoot;
- parseJSON(*state, readFile(jsonCacheFileName), vRoot);
-
- fromCache = true;
-
- doExpr(&vRoot, "", true, nullptr);
- }
+ for (auto & [cursor, prefix] : installable->getCursors(*state))
+ visit(*cursor, parseAttrPath(*state, prefix));
- else {
- createDirs(dirOf(jsonCacheFileName));
-
- Path tmpFile = fmt("%s.tmp.%d", jsonCacheFileName, getpid());
-
- std::ofstream jsonCacheFile;
-
- try {
- // iostream considered harmful
- jsonCacheFile.exceptions(std::ofstream::failbit);
- jsonCacheFile.open(tmpFile);
-
- auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr;
-
- doExpr(getSourceExpr(*state), "", true, cache.get());
-
- } catch (std::exception &) {
- /* Fun fact: catching std::ios::failure does not work
- due to C++11 ABI shenanigans.
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145 */
- if (!jsonCacheFile)
- throw Error("error writing to %s", tmpFile);
- throw;
- }
-
- if (writeCache && rename(tmpFile.c_str(), jsonCacheFileName.c_str()) == -1)
- throw SysError("cannot rename '%s' to '%s'", tmpFile, jsonCacheFileName);
- }
-
- if (!json && results.size() == 0)
+ if (!json && !results)
throw Error("no results for the given search term(s)!");
-
- RunPager pager;
- for (auto el : results) std::cout << el.second << "\n";
-
}
};
-static auto r1 = registerCommand<CmdSearch>("search");
+static auto rCmdSearch = registerCommand<CmdSearch>("search");
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index 4fd8886de..1ef54a33a 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -2,7 +2,8 @@
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
-#include "json.hh"
+
+#include <nlohmann/json.hpp>
using namespace nix;
@@ -19,8 +20,7 @@ struct CmdShowConfig : Command, MixJSON
{
if (json) {
// FIXME: use appropriate JSON types (bool, ints, etc).
- JSONObject jsonObj(std::cout);
- globalConfig.toJSON(jsonObj);
+ logger->stdout("%s", globalConfig.toJSON().dump());
} else {
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
@@ -30,4 +30,4 @@ struct CmdShowConfig : Command, MixJSON
}
};
-static auto r1 = registerCommand<CmdShowConfig>("show-config");
+static auto rShowConfig = registerCommand<CmdShowConfig>("show-config");
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index 5d77cfdca..2542537d3 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -33,7 +33,7 @@ struct CmdShowDerivation : InstallablesCommand
return {
Example{
"To show the store derivation that results from evaluating the Hello package:",
- "nix show-derivation nixpkgs.hello"
+ "nix show-derivation nixpkgs#hello"
},
Example{
"To show the full derivation graph (if available) that produced your NixOS system:",
@@ -67,13 +67,22 @@ struct CmdShowDerivation : InstallablesCommand
{
auto outputsObj(drvObj.object("outputs"));
- for (auto & output : drv.outputs) {
- auto outputObj(outputsObj.object(output.first));
- outputObj.attr("path", store->printStorePath(output.second.path));
- if (output.second.hash) {
- outputObj.attr("hashAlgo", output.second.hash->printMethodAlgo());
- outputObj.attr("hash", output.second.hash->hash.to_string(Base16, false));
- }
+ for (auto & [_outputName, output] : drv.outputs) {
+ auto & outputName = _outputName; // work around clang bug
+ auto outputObj { outputsObj.object(outputName) };
+ std::visit(overloaded {
+ [&](DerivationOutputInputAddressed doi) {
+ outputObj.attr("path", store->printStorePath(doi.path));
+ },
+ [&](DerivationOutputCAFixed dof) {
+ outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName)));
+ outputObj.attr("hashAlgo", dof.hash.printMethodAlgo());
+ outputObj.attr("hash", dof.hash.hash.to_string(Base16, false));
+ },
+ [&](DerivationOutputCAFloating dof) {
+ outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
+ },
+ }, output.output);
}
}
@@ -114,4 +123,4 @@ struct CmdShowDerivation : InstallablesCommand
}
};
-static auto r1 = registerCommand<CmdShowDerivation>("show-derivation");
+static auto rCmdShowDerivation = registerCommand<CmdShowDerivation>("show-derivation");
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 6c9b9a792..44916c77f 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -92,7 +92,7 @@ struct CmdCopySigs : StorePathsCommand
}
};
-static auto r1 = registerCommand<CmdCopySigs>("copy-sigs");
+static auto rCmdCopySigs = registerCommand<CmdCopySigs>("copy-sigs");
struct CmdSignPaths : StorePathsCommand
{
@@ -105,7 +105,8 @@ struct CmdSignPaths : StorePathsCommand
.shortName = 'k',
.description = "file containing the secret signing key",
.labels = {"file"},
- .handler = {&secretKeyFile}
+ .handler = {&secretKeyFile},
+ .completer = completePath
});
}
@@ -143,4 +144,4 @@ struct CmdSignPaths : StorePathsCommand
}
};
-static auto r2 = registerCommand<CmdSignPaths>("sign-paths");
+static auto rCmdSignPaths = registerCommand<CmdSignPaths>("sign-paths");
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index a880bdae0..66ecc5b34 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -158,4 +158,4 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
}
};
-static auto r1 = registerCommand<CmdUpgradeNix>("upgrade-nix");
+static auto rCmdUpgradeNix = registerCommand<CmdUpgradeNix>("upgrade-nix");
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index bb5e4529b..ec7333d03 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -77,20 +77,23 @@ struct CmdVerify : StorePathsCommand
try {
checkInterrupt();
- Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", storePath));
-
MaintainCount<std::atomic<size_t>> mcActive(active);
update();
auto info = store->queryPathInfo(store->parseStorePath(storePath));
+ // Note: info->path can be different from storePath
+ // for binary cache stores when using --all (since we
+ // can't enumerate names efficiently).
+ Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", store->printStorePath(info->path)));
+
if (!noContents) {
std::unique_ptr<AbstractHashSink> hashSink;
if (!info->ca)
- hashSink = std::make_unique<HashSink>(*info->narHash.type);
+ hashSink = std::make_unique<HashSink>(info->narHash.type);
else
- hashSink = std::make_unique<HashModuloSink>(*info->narHash.type, std::string(info->path.hashPart()));
+ hashSink = std::make_unique<HashModuloSink>(info->narHash.type, std::string(info->path.hashPart()));
store->narFromPath(info->path, *hashSink);
@@ -186,4 +189,4 @@ struct CmdVerify : StorePathsCommand
}
};
-static auto r1 = registerCommand<CmdVerify>("verify");
+static auto rCmdVerify = registerCommand<CmdVerify>("verify");
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 167c974ee..63bf087e6 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -55,15 +55,15 @@ struct CmdWhyDepends : SourceExprCommand
return {
Example{
"To show one path through the dependency graph leading from Hello to Glibc:",
- "nix why-depends nixpkgs.hello nixpkgs.glibc"
+ "nix why-depends nixpkgs#hello nixpkgs#glibc"
},
Example{
"To show all files and paths in the dependency graph leading from Thunderbird to libX11:",
- "nix why-depends --all nixpkgs.thunderbird nixpkgs.xorg.libX11"
+ "nix why-depends --all nixpkgs#thunderbird nixpkgs#xorg.libX11"
},
Example{
"To show why Glibc depends on itself:",
- "nix why-depends nixpkgs.glibc nixpkgs.glibc"
+ "nix why-depends nixpkgs#glibc nixpkgs#glibc"
},
};
}
@@ -72,17 +72,19 @@ struct CmdWhyDepends : SourceExprCommand
void run(ref<Store> store) override
{
- auto package = parseInstallable(*this, store, _package, false);
- auto packagePath = toStorePath(store, Build, package);
- auto dependency = parseInstallable(*this, store, _dependency, false);
- auto dependencyPath = toStorePath(store, NoBuild, dependency);
+ auto package = parseInstallable(store, _package);
+ auto packagePath = toStorePath(store, Realise::Outputs, operateOn, package);
+ auto dependency = parseInstallable(store, _dependency);
+ auto dependencyPath = toStorePath(store, Realise::Derivation, operateOn, dependency);
auto dependencyPathHash = dependencyPath.hashPart();
StorePathSet closure;
store->computeFSClosure({packagePath}, closure, false, false);
if (!closure.count(dependencyPath)) {
- printError("'%s' does not depend on '%s'", package->what(), dependency->what());
+ printError("'%s' does not depend on '%s'",
+ store->printStorePath(packagePath),
+ store->printStorePath(dependencyPath));
return;
}
@@ -106,7 +108,11 @@ struct CmdWhyDepends : SourceExprCommand
std::map<StorePath, Node> graph;
for (auto & path : closure)
- graph.emplace(path, Node { .path = path, .refs = store->queryPathInfo(path)->references });
+ graph.emplace(path, Node {
+ .path = path,
+ .refs = store->queryPathInfo(path)->references,
+ .dist = path == dependencyPath ? 0 : inf
+ });
// Transpose the graph.
for (auto & node : graph)
@@ -115,8 +121,6 @@ struct CmdWhyDepends : SourceExprCommand
/* Run Dijkstra's shortest path algorithm to get the distance
of every path in the closure to 'dependency'. */
- graph.emplace(dependencyPath, Node { .path = dependencyPath, .dist = 0 });
-
std::priority_queue<Node *> queue;
queue.push(&graph.at(dependencyPath));
@@ -259,4 +263,4 @@ struct CmdWhyDepends : SourceExprCommand
}
};
-static auto r1 = registerCommand<CmdWhyDepends>("why-depends");
+static auto rCmdWhyDepends = registerCommand<CmdWhyDepends>("why-depends");