aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2021-02-25 21:58:41 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2021-02-25 21:58:41 +0000
commit90d76fa399de4e207ea14ec4c0dd65434f60c152 (patch)
tree3c52e982cba5bcf7b91c99d1b63ba967b4ea8b92 /src/nix
parent4636cc9a1f6de70947abbfb17a0ad91981d1cad7 (diff)
parentca0994819d68aee26a2906c37a47ae609ac46c4c (diff)
Merge remote-tracking branch 'obsidian/path-info' into ca-drv-exotic
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-file.md28
-rw-r--r--src/nix/add-path.md29
-rw-r--r--src/nix/add-to-store.cc78
-rw-r--r--src/nix/app.cc19
-rw-r--r--src/nix/build.cc37
-rw-r--r--src/nix/build.md92
-rw-r--r--src/nix/bundle.cc22
-rw-r--r--src/nix/bundle.md36
-rw-r--r--src/nix/cat.cc18
-rw-r--r--src/nix/command.cc221
-rw-r--r--src/nix/command.hh261
-rw-r--r--src/nix/copy.cc39
-rw-r--r--src/nix/copy.md58
-rw-r--r--src/nix/daemon.cc359
-rw-r--r--src/nix/daemon.md21
-rw-r--r--src/nix/develop.cc140
-rw-r--r--src/nix/develop.md94
-rw-r--r--src/nix/diff-closures.cc15
-rw-r--r--src/nix/diff-closures.md51
-rw-r--r--src/nix/dump-path.cc53
-rw-r--r--src/nix/edit.cc11
-rw-r--r--src/nix/edit.md31
-rw-r--r--src/nix/eval.cc92
-rw-r--r--src/nix/eval.md74
-rw-r--r--src/nix/flake-archive.md29
-rw-r--r--src/nix/flake-check.md68
-rw-r--r--src/nix/flake-clone.md18
-rw-r--r--src/nix/flake-info.md99
-rw-r--r--src/nix/flake-init.md54
-rw-r--r--src/nix/flake-list-inputs.md23
-rw-r--r--src/nix/flake-new.md34
-rw-r--r--src/nix/flake-prefetch.md28
-rw-r--r--src/nix/flake-show.md38
-rw-r--r--src/nix/flake-update.md53
-rw-r--r--src/nix/flake.cc208
-rw-r--r--src/nix/flake.md566
-rw-r--r--src/nix/hash.cc101
-rw-r--r--src/nix/help.md17
-rw-r--r--src/nix/installables.cc781
-rw-r--r--src/nix/installables.hh132
-rw-r--r--src/nix/key-convert-secret-to-public.md19
-rw-r--r--src/nix/key-generate-secret.md48
-rw-r--r--src/nix/legacy.cc7
-rw-r--r--src/nix/legacy.hh23
-rw-r--r--src/nix/local.mk5
-rw-r--r--src/nix/log.cc19
-rw-r--r--src/nix/log.md40
-rw-r--r--src/nix/ls.cc63
-rw-r--r--src/nix/main.cc183
-rw-r--r--src/nix/make-content-addressable.cc27
-rw-r--r--src/nix/make-content-addressable.md59
-rw-r--r--src/nix/markdown.cc50
-rw-r--r--src/nix/markdown.hh7
-rw-r--r--src/nix/nar-cat.md19
-rw-r--r--src/nix/nar-dump-path.md17
-rw-r--r--src/nix/nar-ls.md24
-rw-r--r--src/nix/nar.cc33
-rw-r--r--src/nix/nar.md13
-rw-r--r--src/nix/nix.md119
-rw-r--r--src/nix/optimise-store.cc15
-rw-r--r--src/nix/optimise-store.md23
-rw-r--r--src/nix/path-info.cc65
-rw-r--r--src/nix/path-info.md94
-rw-r--r--src/nix/ping-store.cc17
-rw-r--r--src/nix/ping-store.md33
-rw-r--r--src/nix/prefetch.cc323
-rw-r--r--src/nix/print-dev-env.md19
-rw-r--r--src/nix/profile-diff-closures.md28
-rw-r--r--src/nix/profile-history.md26
-rw-r--r--src/nix/profile-install.md27
-rw-r--r--src/nix/profile-list.md31
-rw-r--r--src/nix/profile-remove.md32
-rw-r--r--src/nix/profile-upgrade.md41
-rw-r--r--src/nix/profile.cc249
-rw-r--r--src/nix/profile.md107
-rw-r--r--src/nix/registry-add.md33
-rw-r--r--src/nix/registry-list.md29
-rw-r--r--src/nix/registry-pin.md38
-rw-r--r--src/nix/registry-remove.md16
-rw-r--r--src/nix/registry.cc37
-rw-r--r--src/nix/registry.md98
-rw-r--r--src/nix/repl.cc71
-rw-r--r--src/nix/repl.md57
-rw-r--r--src/nix/run.cc66
-rw-r--r--src/nix/run.md88
-rw-r--r--src/nix/search.cc42
-rw-r--r--src/nix/search.md72
-rw-r--r--src/nix/shell.md48
-rw-r--r--src/nix/show-config.cc4
-rw-r--r--src/nix/show-derivation.cc21
-rw-r--r--src/nix/show-derivation.md103
-rw-r--r--src/nix/sigs.cc105
-rw-r--r--src/nix/store-cat.md19
-rw-r--r--src/nix/store-delete.cc44
-rw-r--r--src/nix/store-delete.md24
-rw-r--r--src/nix/store-dump-path.md23
-rw-r--r--src/nix/store-gc.cc43
-rw-r--r--src/nix/store-gc.md21
-rw-r--r--src/nix/store-ls.md27
-rw-r--r--src/nix/store-prefetch-file.md32
-rw-r--r--src/nix/store-repair.cc27
-rw-r--r--src/nix/store-repair.md32
-rw-r--r--src/nix/store.cc26
-rw-r--r--src/nix/upgrade-nix.cc24
-rw-r--r--src/nix/upgrade-nix.md28
-rw-r--r--src/nix/verify.cc63
-rw-r--r--src/nix/verify.md49
-rw-r--r--src/nix/why-depends.cc23
-rw-r--r--src/nix/why-depends.md80
109 files changed, 5324 insertions, 2222 deletions
diff --git a/src/nix/add-file.md b/src/nix/add-file.md
new file mode 100644
index 000000000..ed237a035
--- /dev/null
+++ b/src/nix/add-file.md
@@ -0,0 +1,28 @@
+R""(
+
+# Description
+
+Copy the regular file *path* to the Nix store, and print the resulting
+store path on standard output.
+
+> **Warning**
+>
+> The resulting store path is not registered as a garbage
+> collector root, so it could be deleted before you have a
+> chance to register it.
+
+# Examples
+
+Add a regular file to the store:
+
+```console
+# echo foo > bar
+
+# nix store add-file ./bar
+/nix/store/cbv2s4bsvzjri77s2gb8g8bpcb6dpa8w-bar
+
+# cat /nix/store/cbv2s4bsvzjri77s2gb8g8bpcb6dpa8w-bar
+foo
+```
+
+)""
diff --git a/src/nix/add-path.md b/src/nix/add-path.md
new file mode 100644
index 000000000..87473611d
--- /dev/null
+++ b/src/nix/add-path.md
@@ -0,0 +1,29 @@
+R""(
+
+# Description
+
+Copy *path* to the Nix store, and print the resulting store path on
+standard output.
+
+> **Warning**
+>
+> The resulting store path is not registered as a garbage
+> collector root, so it could be deleted before you have a
+> chance to register it.
+
+# Examples
+
+Add a directory to the store:
+
+```console
+# mkdir dir
+# echo foo > dir/bar
+
+# nix store add-path ./dir
+/nix/store/6pmjx56pm94n66n4qw1nff0y1crm8nqg-dir
+
+# cat /nix/store/6pmjx56pm94n66n4qw1nff0y1crm8nqg-dir/bar
+foo
+```
+
+)""
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index c9d07bdcc..ab2b62da2 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -9,49 +9,22 @@ struct CmdAddToStore : MixDryRun, StoreCommand
{
Path path;
std::optional<std::string> namePart;
- FileIngestionMethod ingestionMethod = FileIngestionMethod::Recursive;
+ FileIngestionMethod ingestionMethod;
CmdAddToStore()
{
+ // FIXME: completion
expectArg("path", &path);
addFlag({
.longName = "name",
.shortName = 'n',
- .description = "name component of the store path",
+ .description = "Override the name component of the store path. It defaults to the base name of *path*.",
.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
- {
- 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 {
- };
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store) override
{
if (!namePart) namePart = baseNameOf(path);
@@ -89,8 +62,49 @@ struct CmdAddToStore : MixDryRun, StoreCommand
store->addToStore(info, source);
}
- logger->stdout("%s", store->printStorePath(info.path));
+ logger->cout("%s", store->printStorePath(info.path));
+ }
+};
+
+struct CmdAddFile : CmdAddToStore
+{
+ CmdAddFile()
+ {
+ ingestionMethod = FileIngestionMethod::Flat;
+ }
+
+ std::string description() override
+ {
+ return "add a regular file to the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "add-file.md"
+ ;
+ }
+};
+
+struct CmdAddPath : CmdAddToStore
+{
+ CmdAddPath()
+ {
+ ingestionMethod = FileIngestionMethod::Recursive;
+ }
+
+ std::string description() override
+ {
+ return "add a path to the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "add-path.md"
+ ;
}
};
-static auto rCmdAddToStore = registerCommand<CmdAddToStore>("add-to-store");
+static auto rCmdAddFile = registerCommand2<CmdAddFile>({"store", "add-file"});
+static auto rCmdAddPath = registerCommand2<CmdAddPath>({"store", "add-path"});
diff --git a/src/nix/app.cc b/src/nix/app.cc
index 80acbf658..cf147c631 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -12,11 +12,16 @@ App Installable::toApp(EvalState & state)
auto type = cursor->getAttr("type")->getString();
+ auto checkProgram = [&](const Path & program)
+ {
+ if (!state.store->isInStore(program))
+ throw Error("app program '%s' is not in the Nix store", program);
+ };
+
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);
+ checkProgram(program);
std::vector<StorePathWithOutputs> context2;
for (auto & [path, name] : context)
@@ -33,9 +38,17 @@ App Installable::toApp(EvalState & state)
auto outPath = cursor->getAttr(state.sOutPath)->getString();
auto outputName = cursor->getAttr(state.sOutputName)->getString();
auto name = cursor->getAttr(state.sName)->getString();
+ auto aMeta = cursor->maybeGetAttr("meta");
+ auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
+ auto mainProgram =
+ aMainProgram
+ ? aMainProgram->getString()
+ : DrvName(name).name;
+ auto program = outPath + "/bin/" + mainProgram;
+ checkProgram(program);
return App {
.context = { { drvPath, {outputName} } },
- .program = outPath + "/bin/" + DrvName(name).name,
+ .program = program,
};
}
diff --git a/src/nix/build.cc b/src/nix/build.cc
index 65708e98b..724ce9d79 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -5,9 +5,11 @@
#include "store-api.hh"
#include "local-fs-store.hh"
+#include <nlohmann/json.hpp>
+
using namespace nix;
-struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
+struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
{
Path outLink = "result";
BuildMode buildMode = bmNormal;
@@ -17,7 +19,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
addFlag({
.longName = "out-link",
.shortName = 'o',
- .description = "path of the symlink to the build result",
+ .description = "Use *path* as prefix for the symlinks to the build results. It defaults to `result`.",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
@@ -25,13 +27,13 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
addFlag({
.longName = "no-link",
- .description = "do not create a symlink to the build result",
+ .description = "Do not create symlinks to the build results.",
.handler = {&outLink, Path("")},
});
addFlag({
.longName = "rebuild",
- .description = "rebuild an already built package and compare the result to the existing store paths",
+ .description = "Rebuild an already built package and compare the result to the existing store paths.",
.handler = {&buildMode, bmCheck},
});
}
@@ -41,22 +43,11 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
return "build a derivation or fetch a store path";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To build and run GNU Hello from NixOS 17.03:",
- "nix build -f channel:nixos-17.03 hello; ./result/bin/hello"
- },
- Example{
- "To build the build.x86_64-linux attribute from release.nix:",
- "nix build -f release.nix build.x86_64-linux"
- },
- Example{
- "To make a profile point at GNU Hello:",
- "nix build --profile /tmp/profile nixpkgs#hello"
- },
- };
+ return
+ #include "build.md"
+ ;
}
void run(ref<Store> store) override
@@ -67,7 +58,8 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
- for (size_t i = 0; i < buildables.size(); ++i)
+ for (const auto & [_i, buildable] : enumerate(buildables)) {
+ auto i = _i;
std::visit(overloaded {
[&](BuildableOpaque bo) {
std::string symlink = outLink;
@@ -83,9 +75,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
store2->addPermRoot(output.second, absPath(symlink));
}
},
- }, buildables[i]);
+ }, buildable);
+ }
updateProfile(buildables);
+
+ if (json) logger->cout("%s", buildablesToJSON(buildables, store).dump());
}
};
diff --git a/src/nix/build.md b/src/nix/build.md
new file mode 100644
index 000000000..c2f3e387a
--- /dev/null
+++ b/src/nix/build.md
@@ -0,0 +1,92 @@
+R""(
+
+# Examples
+
+* Build the default package from the flake in the current directory:
+
+ ```console
+ # nix build
+ ```
+
+* Build and run GNU Hello from the `nixpkgs` flake:
+
+ ```console
+ # nix build nixpkgs#hello
+ # ./result/bin/hello
+ Hello, world!
+ ```
+
+* Build GNU Hello and Cowsay, leaving two result symlinks:
+
+ ```console
+ # nix build nixpkgs#hello nixpkgs#cowsay
+ # ls -l result*
+ lrwxrwxrwx 1 … result -> /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
+ lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2
+ ```
+
+* Build a specific output:
+
+ ```console
+ # nix build nixpkgs#glibc.dev
+ # ls -ld ./result-dev
+ lrwxrwxrwx 1 … ./result-dev -> /nix/store/dkm3gwl0xrx0wrw6zi5x3px3lpgjhlw4-glibc-2.32-dev
+ ```
+
+* Build attribute `build.x86_64-linux` from (non-flake) Nix expression
+ `release.nix`:
+
+ ```console
+ # nix build -f release.nix build.x86_64-linux
+ ```
+
+* Build a NixOS system configuration from a flake, and make a profile
+ point to the result:
+
+ ```console
+ # nix build --profile /nix/var/nix/profiles/system \
+ ~/my-configurations#nixosConfigurations.machine.config.system.build.toplevel
+ ```
+
+ (This is essentially what `nixos-rebuild` does.)
+
+* Build an expression specified on the command line:
+
+ ```console
+ # nix build --impure --expr \
+ 'with import <nixpkgs> {};
+ runCommand "foo" {
+ buildInputs = [ hello ];
+ }
+ "hello > $out"'
+ # cat ./result
+ Hello, world!
+ ```
+
+ Note that `--impure` is needed because we're using `<nixpkgs>`,
+ which relies on the `$NIX_PATH` environment variable.
+
+* Fetch a store path from the configured substituters, if it doesn't
+ already exist:
+
+ ```console
+ # nix build /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2
+ ```
+
+# Description
+
+`nix build` builds the specified *installables*. Installables that
+resolve to derivations are built (or substituted if possible). Store
+path installables are substituted.
+
+Unless `--no-link` is specified, after a successful build, it creates
+symlinks to the store paths of the installables. These symlinks have
+the prefix `./result` by default; this can be overriden using the
+`--out-link` option. Each symlink has a suffix `-<N>-<outname>`, where
+*N* is the index of the installable (with the left-most installable
+having index 0), and *outname* is the symbolic derivation output name
+(e.g. `bin`, `dev` or `lib`). `-<N>` is omitted if *N* = 0, and
+`-<outname>` is omitted if *outname* = `out` (denoting the default
+output).
+
+)""
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 4a936c385..48f4eb6e3 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -16,7 +16,7 @@ struct CmdBundle : InstallableCommand
{
addFlag({
.longName = "bundler",
- .description = "use custom bundler",
+ .description = fmt("Use a custom bundler instead of the default (`%s`).", bundler),
.labels = {"flake-url"},
.handler = {&bundler},
.completer = {[&](size_t, std::string_view prefix) {
@@ -27,11 +27,12 @@ struct CmdBundle : InstallableCommand
addFlag({
.longName = "out-link",
.shortName = 'o',
- .description = "path of the symlink to the build result",
+ .description = "Override the name of the symlink to the build result. It defaults to the base name of the app.",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
});
+
}
std::string description() override
@@ -39,14 +40,11 @@ struct CmdBundle : InstallableCommand
return "bundle an application so that it works outside of the Nix store";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To bundle Hello:",
- "nix bundle hello"
- },
- };
+ return
+ #include "bundle.md"
+ ;
}
Category category() override { return catSecondary; }
@@ -76,7 +74,7 @@ struct CmdBundle : InstallableCommand
auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false };
- auto bundler = InstallableFlake(
+ auto bundler = InstallableFlake(this,
evalState, std::move(bundlerFlakeRef),
Strings{bundlerName == "" ? "defaultBundler" : bundlerName},
Strings({"bundlers."}), lockFlags);
@@ -116,10 +114,6 @@ struct CmdBundle : InstallableCommand
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);
diff --git a/src/nix/bundle.md b/src/nix/bundle.md
new file mode 100644
index 000000000..5e2298376
--- /dev/null
+++ b/src/nix/bundle.md
@@ -0,0 +1,36 @@
+R""(
+
+# Examples
+
+* Bundle Hello:
+
+ ```console
+ # nix bundle nixpkgs#hello
+ # ./hello
+ Hello, world!
+ ```
+
+* Bundle a specific version of Nix:
+
+ ```console
+ # nix bundle github:NixOS/nix/e3ddffb27e5fc37a209cfd843c6f7f6a9460a8ec
+ # ./nix --version
+ nix (Nix) 2.4pre20201215_e3ddffb
+ ```
+
+# Description
+
+`nix bundle` packs the closure of the [Nix app](./nix3-run.md)
+*installable* into a single self-extracting executable. See the
+[`nix-bundle` homepage](https://github.com/matthewbauer/nix-bundle)
+for more details.
+
+> **Note**
+>
+> This command only works on Linux.
+
+# Bundler definitions
+
+TODO
+
+)""
diff --git a/src/nix/cat.cc b/src/nix/cat.cc
index eef172cfc..e28ee3c50 100644
--- a/src/nix/cat.cc
+++ b/src/nix/cat.cc
@@ -37,7 +37,12 @@ struct CmdCatStore : StoreCommand, MixCat
return "print the contents of a file in the Nix store on stdout";
}
- Category category() override { return catUtility; }
+ std::string doc() override
+ {
+ return
+ #include "store-cat.md"
+ ;
+ }
void run(ref<Store> store) override
{
@@ -64,7 +69,12 @@ struct CmdCatNar : StoreCommand, MixCat
return "print the contents of a file inside a NAR file on stdout";
}
- Category category() override { return catUtility; }
+ std::string doc() override
+ {
+ return
+ #include "nar-cat.md"
+ ;
+ }
void run(ref<Store> store) override
{
@@ -72,5 +82,5 @@ struct CmdCatNar : StoreCommand, MixCat
}
};
-static auto rCmdCatStore = registerCommand<CmdCatStore>("cat-store");
-static auto rCmdCatNar = registerCommand<CmdCatNar>("cat-nar");
+static auto rCmdCatStore = registerCommand2<CmdCatStore>({"store", "cat"});
+static auto rCmdCatNar = registerCommand2<CmdCatNar>({"nar", "cat"});
diff --git a/src/nix/command.cc b/src/nix/command.cc
deleted file mode 100644
index 9a38c77f1..000000000
--- a/src/nix/command.cc
+++ /dev/null
@@ -1,221 +0,0 @@
-#include "command.hh"
-#include "store-api.hh"
-#include "local-fs-store.hh"
-#include "derivations.hh"
-#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()
-{
-}
-
-ref<Store> StoreCommand::getStore()
-{
- if (!_store)
- _store = createStore();
- return ref<Store>(_store);
-}
-
-ref<Store> StoreCommand::createStore()
-{
- return openStore();
-}
-
-void StoreCommand::run()
-{
- run(getStore());
-}
-
-StorePathsCommand::StorePathsCommand(bool recursive)
- : recursive(recursive)
-{
- if (recursive)
- addFlag({
- .longName = "no-recursive",
- .description = "apply operation to specified paths only",
- .handler = {&this->recursive, false},
- });
- else
- addFlag({
- .longName = "recursive",
- .shortName = 'r',
- .description = "apply operation to closure of the specified paths",
- .handler = {&this->recursive, true},
- });
-
- mkFlag(0, "all", "apply operation to the entire store", &all);
-}
-
-void StorePathsCommand::run(ref<Store> store)
-{
- StorePaths storePaths;
-
- if (all) {
- if (installables.size())
- throw UsageError("'--all' does not expect arguments");
- for (auto & p : store->queryAllValidPaths())
- storePaths.push_back(p);
- }
-
- else {
- for (auto & p : toStorePaths(store, realiseMode, operateOn, installables))
- storePaths.push_back(p);
-
- if (recursive) {
- StorePathSet closure;
- store->computeFSClosure(StorePathSet(storePaths.begin(), storePaths.end()), closure, false, false);
- storePaths.clear();
- for (auto & p : closure)
- storePaths.push_back(p);
- }
- }
-
- run(store, std::move(storePaths));
-}
-
-void StorePathCommand::run(ref<Store> store)
-{
- auto storePaths = toStorePaths(store, Realise::Nothing, operateOn, installables);
-
- if (storePaths.size() != 1)
- throw UsageError("this command requires exactly one store path");
-
- run(store, *storePaths.begin());
-}
-
-Strings editorFor(const Pos & pos)
-{
- auto editor = getEnv("EDITOR").value_or("cat");
- auto args = tokenizeString<Strings>(editor);
- if (pos.line > 0 && (
- editor.find("emacs") != std::string::npos ||
- editor.find("nano") != std::string::npos ||
- editor.find("vim") != std::string::npos))
- args.push_back(fmt("+%d", pos.line));
- args.push_back(pos.file);
- return args;
-}
-
-MixProfile::MixProfile()
-{
- addFlag({
- .longName = "profile",
- .description = "profile to update",
- .labels = {"path"},
- .handler = {&profile},
- .completer = completePath
- });
-}
-
-void MixProfile::updateProfile(const StorePath & storePath)
-{
- if (!profile) return;
- auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
- if (!store) throw Error("'--profile' is not supported for this Nix store");
- auto profile2 = absPath(*profile);
- switchLink(profile2,
- createGeneration(
- ref<LocalFSStore>(store),
- profile2, storePath));
-}
-
-void MixProfile::updateProfile(const Buildables & buildables)
-{
- if (!profile) return;
-
- std::vector<StorePath> result;
-
- for (auto & buildable : buildables) {
- 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.size() != 1)
- throw Error("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
-
- updateProfile(result[0]);
-}
-
-MixDefaultProfile::MixDefaultProfile()
-{
- profile = getDefaultProfile();
-}
-
-MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
-{
- addFlag({
- .longName = "ignore-environment",
- .shortName = 'i',
- .description = "clear the entire environment (except those specified with --keep)",
- .handler = {&ignoreEnvironment, true},
- });
-
- addFlag({
- .longName = "keep",
- .shortName = 'k',
- .description = "keep specified environment variable",
- .labels = {"name"},
- .handler = {[&](std::string s) { keep.insert(s); }},
- });
-
- addFlag({
- .longName = "unset",
- .shortName = 'u',
- .description = "unset specified environment variable",
- .labels = {"name"},
- .handler = {[&](std::string s) { unset.insert(s); }},
- });
-}
-
-void MixEnvironment::setEnviron() {
- if (ignoreEnvironment) {
- if (!unset.empty())
- throw UsageError("--unset does not make sense with --ignore-environment");
-
- for (const auto & var : keep) {
- auto val = getenv(var.c_str());
- if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
- }
-
- vectorEnv = stringsToCharPtrs(stringsEnv);
- environ = vectorEnv.data();
- } else {
- if (!keep.empty())
- throw UsageError("--keep does not make sense without --ignore-environment");
-
- for (const auto & var : unset)
- unsetenv(var.c_str());
- }
-}
-
-}
diff --git a/src/nix/command.hh b/src/nix/command.hh
deleted file mode 100644
index d60c8aeb6..000000000
--- a/src/nix/command.hh
+++ /dev/null
@@ -1,261 +0,0 @@
-#pragma once
-
-#include "installables.hh"
-#include "args.hh"
-#include "common-eval-args.hh"
-#include "path.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
-{
- StoreCommand();
- void run() override;
- ref<Store> getStore();
- virtual ref<Store> createStore();
- virtual void run(ref<Store>) = 0;
-
-private:
- std::shared_ptr<Store> _store;
-};
-
-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
-{
- 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();
-
- std::vector<std::shared_ptr<Installable>> parseInstallables(
- ref<Store> store, std::vector<std::string> ss);
-
- std::shared_ptr<Installable> parseInstallable(
- ref<Store> store, const std::string & installable);
-
- virtual Strings getDefaultFlakeAttrPaths();
-
- virtual Strings getDefaultFlakeAttrPathPrefixes();
-
- void completeInstallable(std::string_view prefix);
-};
-
-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. */
-struct InstallablesCommand : virtual Args, SourceExprCommand
-{
- std::vector<std::shared_ptr<Installable>> installables;
-
- InstallablesCommand();
-
- void prepare() override;
-
- virtual bool useDefaultInstallables() { return true; }
-
- std::optional<FlakeRef> getFlakeRefForCompletion() override;
-
-private:
-
- std::vector<std::string> _installables;
-};
-
-/* A command that operates on exactly one "installable" */
-struct InstallableCommand : virtual Args, SourceExprCommand
-{
- std::shared_ptr<Installable> installable;
-
- InstallableCommand();
-
- void prepare() override;
-
- std::optional<FlakeRef> getFlakeRefForCompletion() override
- {
- return parseFlakeRef(_installable, absPath("."));
- }
-
-private:
-
- std::string _installable{"."};
-};
-
-/* A command that operates on zero or more store paths. */
-struct StorePathsCommand : public InstallablesCommand
-{
-private:
-
- bool recursive = false;
- bool all = false;
-
-protected:
-
- Realise realiseMode = Realise::Derivation;
-
-public:
-
- StorePathsCommand(bool recursive = false);
-
- using StoreCommand::run;
-
- virtual void run(ref<Store> store, std::vector<StorePath> storePaths) = 0;
-
- void run(ref<Store> store) override;
-
- bool useDefaultInstallables() override { return !all; }
-};
-
-/* A command that operates on exactly one store path. */
-struct StorePathCommand : public InstallablesCommand
-{
- using StoreCommand::run;
-
- virtual void run(ref<Store> store, const StorePath & storePath) = 0;
-
- void run(ref<Store> store) override;
-};
-
-/* A helper class for registering commands globally. */
-struct RegisterCommand
-{
- static Commands * commands;
-
- RegisterCommand(const std::string & name,
- std::function<ref<Command>()> command)
- {
- if (!commands) commands = new Commands;
- commands->emplace(name, command);
- }
-};
-
-template<class T>
-static RegisterCommand registerCommand(const std::string & name)
-{
- return RegisterCommand(name, [](){ return make_ref<T>(); });
-}
-
-Buildables build(ref<Store> store, Realise mode,
- std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode = bmNormal);
-
-std::set<StorePath> toStorePaths(ref<Store> store,
- Realise mode, OperateOn operateOn,
- std::vector<std::shared_ptr<Installable>> installables);
-
-StorePath toStorePath(ref<Store> store,
- Realise mode, OperateOn operateOn,
- std::shared_ptr<Installable> installable);
-
-std::set<StorePath> toDerivations(ref<Store> store,
- std::vector<std::shared_ptr<Installable>> installables,
- bool useDeriver = false);
-
-/* Helper function to generate args that invoke $EDITOR on
- filename:lineno. */
-Strings editorFor(const Pos & pos);
-
-struct MixProfile : virtual StoreCommand
-{
- std::optional<Path> profile;
-
- MixProfile();
-
- /* If 'profile' is set, make it point at 'storePath'. */
- void updateProfile(const StorePath & storePath);
-
- /* If 'profile' is set, make it point at the store path produced
- by 'buildables'. */
- void updateProfile(const Buildables & buildables);
-};
-
-struct MixDefaultProfile : MixProfile
-{
- MixDefaultProfile();
-};
-
-struct MixEnvironment : virtual Args {
-
- StringSet keep, unset;
- Strings stringsEnv;
- std::vector<char*> vectorEnv;
- bool ignoreEnvironment;
-
- MixEnvironment();
-
- /* Modify global environ based on ignoreEnvironment, keep, and unset. It's expected that exec will be called before this class goes out of scope, otherwise environ will become invalid. */
- 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 cb31aac8f..c56a1def1 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -16,33 +16,35 @@ struct CmdCopy : StorePathsCommand
SubstituteFlag substitute = NoSubstitute;
+ using StorePathsCommand::run;
+
CmdCopy()
: StorePathsCommand(true)
{
addFlag({
.longName = "from",
- .description = "URI of the source Nix store",
+ .description = "URL of the source Nix store.",
.labels = {"store-uri"},
.handler = {&srcUri},
});
addFlag({
.longName = "to",
- .description = "URI of the destination Nix store",
+ .description = "URL of the destination Nix store.",
.labels = {"store-uri"},
.handler = {&dstUri},
});
addFlag({
.longName = "no-check-sigs",
- .description = "do not require that paths are signed by trusted keys",
+ .description = "Do not require that paths are signed by trusted keys.",
.handler = {&checkSigs, NoCheckSigs},
});
addFlag({
.longName = "substitute-on-destination",
.shortName = 's',
- .description = "whether to try substitutes on the destination store (only supported by SSH)",
+ .description = "Whether to try substitutes on the destination store (only supported by SSH stores).",
.handler = {&substitute, Substitute},
});
@@ -54,32 +56,11 @@ struct CmdCopy : StorePathsCommand
return "copy paths between Nix stores";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To copy Firefox from the local store to a binary cache in file:///tmp/cache:",
- "nix copy --to file:///tmp/cache $(type -p firefox)"
- },
- Example{
- "To copy the entire current NixOS system closure to another machine via SSH:",
- "nix copy --to ssh://server /run/current-system"
- },
- Example{
- "To copy a closure from another machine via SSH:",
- "nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
- },
-#ifdef ENABLE_S3
- Example{
- "To copy Hello to an S3 binary cache:",
- "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"
- },
-#endif
- };
+ return
+ #include "copy.md"
+ ;
}
Category category() override { return catSecondary; }
diff --git a/src/nix/copy.md b/src/nix/copy.md
new file mode 100644
index 000000000..25e0ddadc
--- /dev/null
+++ b/src/nix/copy.md
@@ -0,0 +1,58 @@
+R""(
+
+# Examples
+
+* Copy Firefox from the local store to a binary cache in `/tmp/cache`:
+
+ ```console
+ # nix copy --to file:///tmp/cache $(type -p firefox)
+ ```
+
+ Note the `file://` - without this, the destination is a chroot
+ store, not a binary cache.
+
+* Copy the entire current NixOS system closure to another machine via
+ SSH:
+
+ ```console
+ # nix copy -s --to ssh://server /run/current-system
+ ```
+
+ The `-s` flag causes the remote machine to try to substitute missing
+ store paths, which may be faster if the link between the local and
+ remote machines is slower than the link between the remote machine
+ and its substituters (e.g. `https://cache.nixos.org`).
+
+* Copy a closure from another machine via SSH:
+
+ ```console
+ # nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2
+ ```
+
+* Copy Hello to a binary cache in an Amazon S3 bucket:
+
+ ```console
+ # nix copy --to s3://my-bucket?region=eu-west-1 nixpkgs#hello
+ ```
+
+ or to an S3-compatible storage system:
+
+ ```console
+ # nix copy --to s3://my-bucket?region=eu-west-1&endpoint=example.com nixpkgs#hello
+ ```
+
+ Note that this only works if Nix is built with AWS support.
+
+* Copy a closure from `/nix/store` to the chroot store `/tmp/nix/nix/store`:
+
+ ```console
+ # nix copy --to /tmp/nix nixpkgs#hello --no-check-sigs
+ ```
+
+# Description
+
+`nix copy` copies store path closures between two Nix stores. The
+source store is specified using `--from` and the destination using
+`--to`. If one of these is omitted, it defaults to the local store.
+
+)""
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
new file mode 100644
index 000000000..2cf2a04c9
--- /dev/null
+++ b/src/nix/daemon.cc
@@ -0,0 +1,359 @@
+#include "command.hh"
+#include "shared.hh"
+#include "local-store.hh"
+#include "remote-store.hh"
+#include "util.hh"
+#include "serialise.hh"
+#include "archive.hh"
+#include "globals.hh"
+#include "derivations.hh"
+#include "finally.hh"
+#include "legacy.hh"
+#include "daemon.hh"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+
+#if __APPLE__ || __FreeBSD__
+#include <sys/ucred.h>
+#endif
+
+using namespace nix;
+using namespace nix::daemon;
+
+#ifndef __linux__
+#define SPLICE_F_MOVE 0
+static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
+{
+ // We ignore most parameters, we just have them for conformance with the linux syscall
+ std::vector<char> buf(8192);
+ auto read_count = read(fd_in, buf.data(), buf.size());
+ if (read_count == -1)
+ return read_count;
+ auto write_count = decltype(read_count)(0);
+ while (write_count < read_count) {
+ auto res = write(fd_out, buf.data() + write_count, read_count - write_count);
+ if (res == -1)
+ return res;
+ write_count += res;
+ }
+ return read_count;
+}
+#endif
+
+
+static void sigChldHandler(int sigNo)
+{
+ // Ensure we don't modify errno of whatever we've interrupted
+ auto saved_errno = errno;
+ // Reap all dead children.
+ while (waitpid(-1, 0, WNOHANG) > 0) ;
+ errno = saved_errno;
+}
+
+
+static void setSigChldAction(bool autoReap)
+{
+ struct sigaction act, oact;
+ act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGCHLD, &act, &oact))
+ throw SysError("setting SIGCHLD handler");
+}
+
+
+bool matchUser(const string & user, const string & group, const Strings & users)
+{
+ if (find(users.begin(), users.end(), "*") != users.end())
+ return true;
+
+ if (find(users.begin(), users.end(), user) != users.end())
+ return true;
+
+ for (auto & i : users)
+ if (string(i, 0, 1) == "@") {
+ if (group == string(i, 1)) return true;
+ struct group * gr = getgrnam(i.c_str() + 1);
+ if (!gr) continue;
+ for (char * * mem = gr->gr_mem; *mem; mem++)
+ if (user == string(*mem)) return true;
+ }
+
+ return false;
+}
+
+
+struct PeerInfo
+{
+ bool pidKnown;
+ pid_t pid;
+ bool uidKnown;
+ uid_t uid;
+ bool gidKnown;
+ gid_t gid;
+};
+
+
+// Get the identity of the caller, if possible.
+static PeerInfo getPeerInfo(int remote)
+{
+ PeerInfo peer = { false, 0, false, 0, false, 0 };
+
+#if defined(SO_PEERCRED)
+
+ ucred cred;
+ socklen_t credLen = sizeof(cred);
+ if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
+ throw SysError("getting peer credentials");
+ peer = { true, cred.pid, true, cred.uid, true, cred.gid };
+
+#elif defined(LOCAL_PEERCRED)
+
+#if !defined(SOL_LOCAL)
+#define SOL_LOCAL 0
+#endif
+
+ xucred cred;
+ socklen_t credLen = sizeof(cred);
+ if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == -1)
+ throw SysError("getting peer credentials");
+ peer = { false, 0, true, cred.cr_uid, false, 0 };
+
+#endif
+
+ return peer;
+}
+
+
+#define SD_LISTEN_FDS_START 3
+
+
+static ref<Store> openUncachedStore()
+{
+ Store::Params params; // FIXME: get params from somewhere
+ // Disable caching since the client already does that.
+ params["path-info-cache-size"] = "0";
+ return openStore(settings.storeUri, params);
+}
+
+
+static void daemonLoop()
+{
+ if (chdir("/") == -1)
+ throw SysError("cannot change current directory");
+
+ // Get rid of children automatically; don't let them become zombies.
+ setSigChldAction(true);
+
+ AutoCloseFD fdSocket;
+
+ // Handle socket-based activation by systemd.
+ auto listenFds = getEnv("LISTEN_FDS");
+ if (listenFds) {
+ if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1")
+ throw Error("unexpected systemd environment variables");
+ fdSocket = SD_LISTEN_FDS_START;
+ closeOnExec(fdSocket.get());
+ }
+
+ // Otherwise, create and bind to a Unix domain socket.
+ else {
+ createDirs(dirOf(settings.nixDaemonSocketFile));
+ fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666);
+ }
+
+ // Loop accepting connections.
+ while (1) {
+
+ try {
+ // Accept a connection.
+ struct sockaddr_un remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ AutoCloseFD remote = accept(fdSocket.get(),
+ (struct sockaddr *) &remoteAddr, &remoteAddrLen);
+ checkInterrupt();
+ if (!remote) {
+ if (errno == EINTR) continue;
+ throw SysError("accepting connection");
+ }
+
+ closeOnExec(remote.get());
+
+ TrustedFlag trusted = NotTrusted;
+ PeerInfo peer = getPeerInfo(remote.get());
+
+ struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
+ string user = pw ? pw->pw_name : std::to_string(peer.uid);
+
+ struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
+ string group = gr ? gr->gr_name : std::to_string(peer.gid);
+
+ Strings trustedUsers = settings.trustedUsers;
+ Strings allowedUsers = settings.allowedUsers;
+
+ if (matchUser(user, group, trustedUsers))
+ trusted = Trusted;
+
+ if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
+ throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
+
+ printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""))
+ % (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>")
+ % (peer.uidKnown ? user : "<unknown>"));
+
+ // Fork a child to handle the connection.
+ ProcessOptions options;
+ options.errorPrefix = "unexpected Nix daemon error: ";
+ options.dieWithParent = false;
+ options.runExitHandlers = true;
+ options.allowVfork = false;
+ startProcess([&]() {
+ fdSocket = -1;
+
+ // Background the daemon.
+ if (setsid() == -1)
+ throw SysError("creating a new session");
+
+ // Restore normal handling of SIGCHLD.
+ setSigChldAction(false);
+
+ // For debugging, stuff the pid into argv[1].
+ if (peer.pidKnown && savedArgv[1]) {
+ string processName = std::to_string(peer.pid);
+ strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1]));
+ }
+
+ // Handle the connection.
+ FdSource from(remote.get());
+ FdSink to(remote.get());
+ processConnection(openUncachedStore(), from, to, trusted, NotRecursive, [&](Store & store) {
+#if 0
+ /* Prevent users from doing something very dangerous. */
+ if (geteuid() == 0 &&
+ querySetting("build-users-group", "") == "")
+ throw Error("if you run 'nix-daemon' as root, then you MUST set 'build-users-group'!");
+#endif
+ store.createUser(user, peer.uid);
+ });
+
+ exit(0);
+ }, options);
+
+ } catch (Interrupted & e) {
+ return;
+ } catch (Error & error) {
+ ErrorInfo ei = error.info();
+ // FIXME: add to trace?
+ ei.msg = hintfmt("error processing connection: %1%", ei.msg.str());
+ logError(ei);
+ }
+ }
+}
+
+static void runDaemon(bool stdio)
+{
+ if (stdio) {
+ if (auto store = openUncachedStore().dynamic_pointer_cast<RemoteStore>()) {
+ auto conn = store->openConnectionWrapper();
+ int from = conn->from.fd;
+ int to = conn->to.fd;
+
+ auto nfds = std::max(from, STDIN_FILENO) + 1;
+ while (true) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(from, &fds);
+ FD_SET(STDIN_FILENO, &fds);
+ if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
+ throw SysError("waiting for data from client or server");
+ if (FD_ISSET(from, &fds)) {
+ auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from daemon socket to stdout");
+ else if (res == 0)
+ throw EndOfFile("unexpected EOF from daemon socket");
+ }
+ if (FD_ISSET(STDIN_FILENO, &fds)) {
+ auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from stdin to daemon socket");
+ else if (res == 0)
+ return;
+ }
+ }
+ } else {
+ FdSource from(STDIN_FILENO);
+ FdSink to(STDOUT_FILENO);
+ /* Auth hook is empty because in this mode we blindly trust the
+ standard streams. Limiting access to those is explicitly
+ not `nix-daemon`'s responsibility. */
+ processConnection(openUncachedStore(), from, to, Trusted, NotRecursive, [&](Store & _){});
+ }
+ } else
+ daemonLoop();
+}
+
+static int main_nix_daemon(int argc, char * * argv)
+{
+ {
+ auto stdio = false;
+
+ parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+ if (*arg == "--daemon")
+ ; // ignored for backwards compatibility
+ else if (*arg == "--help")
+ showManPage("nix-daemon");
+ else if (*arg == "--version")
+ printVersion("nix-daemon");
+ else if (*arg == "--stdio")
+ stdio = true;
+ else return false;
+ return true;
+ });
+
+ runDaemon(stdio);
+
+ return 0;
+ }
+}
+
+static RegisterLegacyCommand r_nix_daemon("nix-daemon", main_nix_daemon);
+
+struct CmdDaemon : StoreCommand
+{
+ std::string description() override
+ {
+ return "daemon to perform store operations on behalf of non-root clients";
+ }
+
+ Category category() override { return catUtility; }
+
+ std::string doc() override
+ {
+ return
+ #include "daemon.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ runDaemon(false);
+ }
+};
+
+static auto rCmdDaemon = registerCommand2<CmdDaemon>({"daemon"});
diff --git a/src/nix/daemon.md b/src/nix/daemon.md
new file mode 100644
index 000000000..e97016a94
--- /dev/null
+++ b/src/nix/daemon.md
@@ -0,0 +1,21 @@
+R""(
+
+# Example
+
+* Run the daemon in the foreground:
+
+ ```console
+ # nix daemon
+ ```
+
+# Description
+
+This command runs the Nix daemon, which is a required component in
+multi-user Nix installations. It performs build actions and other
+operations on the Nix store on behalf of non-root users. Usually you
+don't run the daemon directly; instead it's managed by a service
+management framework such as `systemd`.
+
+Note that this daemon does not fork into the background.
+
+)""
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 9372f43de..d0b140570 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -11,6 +11,19 @@
using namespace nix;
+struct DevelopSettings : Config
+{
+ Setting<std::string> bashPrompt{this, "", "bash-prompt",
+ "The bash prompt (`PS1`) in `nix develop` shells."};
+
+ Setting<std::string> bashPromptSuffix{this, "", "bash-prompt-suffix",
+ "Suffix appended to the `PS1` environment variable in `nix develop` shells."};
+};
+
+static DevelopSettings developSettings;
+
+static GlobalConfig::Register rDevelopSettings(&developSettings);
+
struct Var
{
bool exported = true;
@@ -39,21 +52,24 @@ BuildEnvironment readEnvironment(const Path & path)
static std::string varNameRegex =
R"re((?:[a-zA-Z_][a-zA-Z0-9_]*))re";
- static std::regex declareRegex(
- "^declare -x (" + varNameRegex + ")" +
- R"re((?:="((?:[^"\\]|\\.)*)")?\n)re");
-
static std::string simpleStringRegex =
R"re((?:[a-zA-Z0-9_/:\.\-\+=]*))re";
- static std::string quotedStringRegex =
- R"re((?:\$?'(?:[^'\\]|\\[abeEfnrtv\\'"?])*'))re";
+ static std::string dquotedStringRegex =
+ R"re((?:\$?"(?:[^"\\]|\\[$`"\\\n])*"))re";
+
+ static std::string squotedStringRegex =
+ R"re((?:\$?(?:'(?:[^'\\]|\\[abeEfnrtv\\'"?])*'|\\')+))re";
static std::string indexedArrayRegex =
R"re((?:\(( *\[[0-9]+\]="(?:[^"\\]|\\.)*")*\)))re";
+ static std::regex declareRegex(
+ "^declare -a?x (" + varNameRegex + ")(=(" +
+ dquotedStringRegex + "|" + indexedArrayRegex + "))?\n");
+
static std::regex varRegex(
- "^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + quotedStringRegex + "|" + indexedArrayRegex + ")\n");
+ "^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + squotedStringRegex + "|" + indexedArrayRegex + ")\n");
/* Note: we distinguish between an indexed and associative array
using the space before the closing parenthesis. Will
@@ -182,7 +198,22 @@ struct Common : InstallableCommand, MixProfile
"UID",
};
+ std::vector<std::pair<std::string, std::string>> redirects;
+
+ Common()
+ {
+ addFlag({
+ .longName = "redirect",
+ .description = "Redirect a store path to a mutable location.",
+ .labels = {"installable", "outputs-dir"},
+ .handler = {[&](std::string installable, std::string outputsDir) {
+ redirects.push_back({installable, outputsDir});
+ }}
+ });
+ }
+
std::string makeRcScript(
+ ref<Store> store,
const BuildEnvironment & buildEnvironment,
const Path & outputsDir = absPath(".") + "/outputs")
{
@@ -208,12 +239,14 @@ struct Common : InstallableCommand, MixProfile
out << buildEnvironment.bashFunctions << "\n";
- out << "export NIX_BUILD_TOP=\"$(mktemp -d --tmpdir nix-shell.XXXXXX)\"\n";
+ out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
out << "eval \"$shellHook\"\n";
+ auto script = out.str();
+
/* Substitute occurrences of output paths. */
auto outputs = buildEnvironment.env.find("outputs");
assert(outputs != buildEnvironment.env.end());
@@ -227,7 +260,33 @@ struct Common : InstallableCommand, MixProfile
rewrites.insert({from->second.quoted, outputsDir + "/" + outputName});
}
- return rewriteStrings(out.str(), rewrites);
+ /* Substitute redirects. */
+ for (auto & [installable_, dir_] : redirects) {
+ auto dir = absPath(dir_);
+ auto installable = parseInstallable(store, installable_);
+ auto buildable = installable->toBuildable();
+ auto doRedirect = [&](const StorePath & path)
+ {
+ auto from = store->printStorePath(path);
+ if (script.find(from) == std::string::npos)
+ warn("'%s' (path '%s') is not used by this build environment", installable->what(), from);
+ else {
+ printInfo("redirecting '%s' to '%s'", from, dir);
+ rewrites.insert({from, dir});
+ }
+ };
+ std::visit(overloaded {
+ [&](const BuildableOpaque & bo) {
+ doRedirect(bo.path);
+ },
+ [&](const BuildableFromDrv & bfd) {
+ for (auto & [outputName, path] : bfd.outputs)
+ if (path) doRedirect(*path);
+ },
+ }, buildable);
+ }
+
+ return rewriteStrings(script, rewrites);
}
Strings getDefaultFlakeAttrPaths() override
@@ -275,7 +334,7 @@ struct CmdDevelop : Common, MixEnvironment
addFlag({
.longName = "command",
.shortName = 'c',
- .description = "command and arguments to be executed instead of an interactive shell",
+ .description = "Instead of starting an interactive shell, start the specified command and arguments.",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
@@ -285,38 +344,38 @@ struct CmdDevelop : Common, MixEnvironment
addFlag({
.longName = "phase",
- .description = "phase to run (e.g. `build` or `configure`)",
+ .description = "The stdenv phase to run (e.g. `build` or `configure`).",
.labels = {"phase-name"},
.handler = {&phase},
});
addFlag({
.longName = "configure",
- .description = "run the configure phase",
+ .description = "Run the `configure` phase.",
.handler = {&phase, {"configure"}},
});
addFlag({
.longName = "build",
- .description = "run the build phase",
+ .description = "Run the `build` phase.",
.handler = {&phase, {"build"}},
});
addFlag({
.longName = "check",
- .description = "run the check phase",
+ .description = "Run the `check` phase.",
.handler = {&phase, {"check"}},
});
addFlag({
.longName = "install",
- .description = "run the install phase",
+ .description = "Run the `install` phase.",
.handler = {&phase, {"install"}},
});
addFlag({
.longName = "installcheck",
- .description = "run the installcheck phase",
+ .description = "Run the `installcheck` phase.",
.handler = {&phase, {"installCheck"}},
});
}
@@ -326,26 +385,11 @@ struct CmdDevelop : Common, MixEnvironment
return "run a bash shell that provides the build environment of a derivation";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To get the build environment of GNU 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"
- },
- Example{
- "To use a build environment previously recorded in a profile:",
- "nix develop /tmp/my-shell"
- },
- };
+ return
+ #include "develop.md"
+ ;
}
void run(ref<Store> store) override
@@ -354,7 +398,7 @@ struct CmdDevelop : Common, MixEnvironment
auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
- auto script = makeRcScript(buildEnvironment);
+ auto script = makeRcScript(store, buildEnvironment);
if (verbosity >= lvlDebug)
script += "set -x\n";
@@ -368,7 +412,6 @@ struct CmdDevelop : Common, MixEnvironment
// 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()) {
@@ -380,6 +423,10 @@ struct CmdDevelop : Common, MixEnvironment
else {
script += "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n";
+ if (developSettings.bashPrompt != "")
+ script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n", shellEscape(developSettings.bashPrompt));
+ if (developSettings.bashPromptSuffix != "")
+ script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n", shellEscape(developSettings.bashPromptSuffix));
}
writeFull(rcFileFd.get(), script);
@@ -396,6 +443,7 @@ struct CmdDevelop : Common, MixEnvironment
auto state = getEvalState();
auto bashInstallable = std::make_shared<InstallableFlake>(
+ this,
state,
installable->nixpkgsFlakeRef(),
Strings{"bashInteractive"},
@@ -408,7 +456,10 @@ struct CmdDevelop : Common, MixEnvironment
ignoreException();
}
- auto args = Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
+ // If running a phase or single command, don't want an interactive shell running after
+ // Ctrl-C, so don't pass --rcfile
+ auto args = phase || !command.empty() ? Strings{std::string(baseNameOf(shell)), rcFilePath}
+ : Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
restoreAffinity();
restoreSignals();
@@ -426,14 +477,11 @@ struct CmdPrintDevEnv : Common
return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To apply the build environment of GNU hello to the current shell:",
- ". <(nix print-dev-env nixpkgs#hello)"
- },
- };
+ return
+ #include "print-dev-env.md"
+ ;
}
Category category() override { return catUtility; }
@@ -444,7 +492,7 @@ struct CmdPrintDevEnv : Common
stopProgressBar();
- std::cout << makeRcScript(buildEnvironment);
+ std::cout << makeRcScript(store, buildEnvironment);
}
};
diff --git a/src/nix/develop.md b/src/nix/develop.md
new file mode 100644
index 000000000..e71d9f8aa
--- /dev/null
+++ b/src/nix/develop.md
@@ -0,0 +1,94 @@
+R""(
+
+# Examples
+
+* Start a shell with the build environment of the default package of
+ the flake in the current directory:
+
+ ```console
+ # nix develop
+ ```
+
+ Typical commands to run inside this shell are:
+
+ ```console
+ # configurePhase
+ # buildPhase
+ # installPhase
+ ```
+
+ Alternatively, you can run whatever build tools your project uses
+ directly, e.g. for a typical Unix project:
+
+ ```console
+ # ./configure --prefix=$out
+ # make
+ # make install
+ ```
+
+* Run a particular build phase directly:
+
+ ```console
+ # nix develop --configure
+ # nix develop --build
+ # nix develop --check
+ # nix develop --install
+ # nix develop --installcheck
+ ```
+
+* Start a shell with the build environment of GNU Hello:
+
+ ```console
+ # nix develop nixpkgs#hello
+ ```
+
+* Record a build environment in a profile:
+
+ ```console
+ # nix develop --profile /tmp/my-build-env nixpkgs#hello
+ ```
+
+* Use a build environment previously recorded in a profile:
+
+ ```console
+ # nix develop /tmp/my-build-env
+ ```
+
+* Replace all occurences of the store path corresponding to
+ `glibc.dev` with a writable directory:
+
+ ```console
+ # nix develop --redirect nixpkgs#glibc.dev ~/my-glibc/outputs/dev
+ ```
+
+ Note that this is useful if you're running a `nix develop` shell for
+ `nixpkgs#glibc` in `~/my-glibc` and want to compile another package
+ against it.
+
+# Description
+
+`nix develop` starts a `bash` shell that provides an interactive build
+environment nearly identical to what Nix would use to build
+*installable*. Inside this shell, environment variables and shell
+functions are set up so that you can interactively and incrementally
+build your package.
+
+Nix determines the build environment by building a modified version of
+the derivation *installable* that just records the environment
+initialised by `stdenv` and exits. This build environment can be
+recorded into a profile using `--profile`.
+
+The prompt used by the `bash` shell can be customised by setting the
+`bash-prompt` and `bash-prompt-suffix` settings in `nix.conf` or in
+the flake's `nixConfig` attribute.
+
+# Flake output attributes
+
+If no flake output attribute is given, `nix run` tries the following
+flake output attributes:
+
+* `devShell.<system>`
+
+* `defaultPackage.<system>`
+
+)""
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
index 30e7b20e1..0c7d531c1 100644
--- a/src/nix/diff-closures.cc
+++ b/src/nix/diff-closures.cc
@@ -121,16 +121,11 @@ struct CmdDiffClosures : SourceExprCommand
return "show what packages and versions were added and removed between two closures";
}
- Category category() override { return catSecondary; }
-
- Examples examples() override
+ std::string doc() 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",
- },
- };
+ return
+ #include "diff-closures.md"
+ ;
}
void run(ref<Store> store) override
@@ -143,4 +138,4 @@ struct CmdDiffClosures : SourceExprCommand
}
};
-static auto rCmdDiffClosures = registerCommand<CmdDiffClosures>("diff-closures");
+static auto rCmdDiffClosures = registerCommand2<CmdDiffClosures>({"store", "diff-closures"});
diff --git a/src/nix/diff-closures.md b/src/nix/diff-closures.md
new file mode 100644
index 000000000..0294c0d8d
--- /dev/null
+++ b/src/nix/diff-closures.md
@@ -0,0 +1,51 @@
+R""(
+
+# Examples
+
+* Show what got added and removed between two versions of the NixOS
+ system profile:
+
+ ```console
+ # nix store diff-closures /nix/var/nix/profiles/system-655-link /nix/var/nix/profiles/system-658-link
+ acpi-call: 2020-04-07-5.8.16 → 2020-04-07-5.8.18
+ baloo-widgets: 20.08.1 → 20.08.2
+ bluez-qt: +12.6 KiB
+ dolphin: 20.08.1 → 20.08.2, +13.9 KiB
+ kdeconnect: 20.08.2 → ∅, -6597.8 KiB
+ kdeconnect-kde: ∅ → 20.08.2, +6599.7 KiB
+ …
+ ```
+
+# Description
+
+This command shows the differences between the two closures *before*
+and *after* with respect to the addition, removal, or version change
+of packages, as well as changes in store path sizes.
+
+For each package name in the two closures (where a package name is
+defined as the name component of a store path excluding the version),
+if there is a change in the set of versions of the package, or a
+change in the size of the store paths of more than 8 KiB, it prints a
+line like this:
+
+```console
+dolphin: 20.08.1 → 20.08.2, +13.9 KiB
+```
+
+No size change is shown if it's below the threshold. If the package
+does not exist in either the *before* or *after* closures, it is
+represented using `∅` (empty set) on the appropriate side of the
+arrow. If a package has an empty version string, the version is
+rendered as `ε` (epsilon).
+
+There may be multiple versions of a package in each closure. In that
+case, only the changed versions are shown. Thus,
+
+```console
+libfoo: 1.2, 1.3 → 1.4
+```
+
+leaves open the possibility that there are other versions (e.g. `1.1`)
+that exist in both closures.
+
+)""
diff --git a/src/nix/dump-path.cc b/src/nix/dump-path.cc
index 6fd197531..c4edc894b 100644
--- a/src/nix/dump-path.cc
+++ b/src/nix/dump-path.cc
@@ -1,5 +1,6 @@
#include "command.hh"
#include "store-api.hh"
+#include "archive.hh"
using namespace nix;
@@ -7,21 +8,16 @@ struct CmdDumpPath : StorePathCommand
{
std::string description() override
{
- return "dump a store path to stdout (in NAR format)";
+ return "serialise a store path to stdout in NAR format";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To get a NAR from the binary cache https://cache.nixos.org/:",
- "nix dump-path --store https://cache.nixos.org/ /nix/store/7crrmih8c52r8fbnqb933dxrsp44md93-glibc-2.25"
- },
- };
+ return
+ #include "store-dump-path.md"
+ ;
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store, const StorePath & storePath) override
{
FdSink sink(STDOUT_FILENO);
@@ -30,4 +26,39 @@ struct CmdDumpPath : StorePathCommand
}
};
-static auto rDumpPath = registerCommand<CmdDumpPath>("dump-path");
+static auto rDumpPath = registerCommand2<CmdDumpPath>({"store", "dump-path"});
+
+struct CmdDumpPath2 : Command
+{
+ Path path;
+
+ CmdDumpPath2()
+ {
+ expectArgs({
+ .label = "path",
+ .handler = {&path},
+ .completer = completePath
+ });
+ }
+
+ std::string description() override
+ {
+ return "serialise a path to stdout in NAR format";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "nar-dump-path.md"
+ ;
+ }
+
+ void run() override
+ {
+ FdSink sink(STDOUT_FILENO);
+ dumpPath(path, sink);
+ sink.flush();
+ }
+};
+
+static auto rDumpPath2 = registerCommand2<CmdDumpPath2>({"nar", "dump-path"});
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 51c16f5a9..6472dd27a 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -15,14 +15,11 @@ struct CmdEdit : InstallableCommand
return "open the Nix expression of a Nix package in $EDITOR";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To open the Nix expression of the GNU Hello package:",
- "nix edit nixpkgs#hello"
- },
- };
+ return
+ #include "edit.md"
+ ;
}
Category category() override { return catSecondary; }
diff --git a/src/nix/edit.md b/src/nix/edit.md
new file mode 100644
index 000000000..80563d06b
--- /dev/null
+++ b/src/nix/edit.md
@@ -0,0 +1,31 @@
+R""(
+
+# Examples
+
+* Open the Nix expression of the GNU Hello package:
+
+ ```console
+ # nix edit nixpkgs#hello
+ ```
+
+* Get the filename and line number used by `nix edit`:
+
+ ```console
+ # nix eval --raw nixpkgs#hello.meta.position
+ /nix/store/fvafw0gvwayzdan642wrv84pzm5bgpmy-source/pkgs/applications/misc/hello/default.nix:15
+ ```
+
+# Description
+
+This command opens the Nix expression of a derivation in an
+editor. The filename and line number of the derivation are taken from
+its `meta.position` attribute. Nixpkgs' `stdenv.mkDerivation` sets
+this attribute to the location of the definition of the
+`meta.description`, `version` or `name` derivation attributes.
+
+The editor to invoke is specified by the `EDITOR` environment
+variable. It defaults to `cat`. If the editor is `emacs`, `nano` or
+`vim`, it is passed the line number of the derivation using the
+argument `+<lineno>`.
+
+)""
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 43ce46546..65d61e005 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -3,6 +3,7 @@
#include "shared.hh"
#include "store-api.hh"
#include "eval.hh"
+#include "eval-inline.hh"
#include "json.hh"
#include "value-to-json.hh"
#include "progress-bar.hh"
@@ -13,17 +14,29 @@ struct CmdEval : MixJSON, InstallableCommand
{
bool raw = false;
std::optional<std::string> apply;
+ std::optional<Path> writeTo;
CmdEval()
{
- mkFlag(0, "raw", "print strings unquoted", &raw);
+ addFlag({
+ .longName = "raw",
+ .description = "Print strings without quotes or escaping.",
+ .handler = {&raw, true},
+ });
addFlag({
.longName = "apply",
- .description = "apply a function to each argument",
+ .description = "Apply the function *expr* to each argument.",
.labels = {"expr"},
.handler = {&apply},
});
+
+ addFlag({
+ .longName = "write-to",
+ .description = "Write a string or attrset of strings to *path*.",
+ .labels = {"path"},
+ .handler = {&writeTo},
+ });
}
std::string description() override
@@ -31,30 +44,11 @@ struct CmdEval : MixJSON, InstallableCommand
return "evaluate a Nix expression";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- {
- "To evaluate a Nix expression given on the command line:",
- "nix eval --expr '1 + 2'"
- },
- {
- "To evaluate a Nix expression from a file or URI:",
- "nix eval -f ./my-nixpkgs hello.name"
- },
- {
- "To get the current version of Nixpkgs:",
- "nix eval --raw nixpkgs#lib.version"
- },
- {
- "To print the store path of the Hello package:",
- "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"
- },
- };
+ return
+ #include "eval.md"
+ ;
}
Category category() override { return catSecondary; }
@@ -66,7 +60,7 @@ struct CmdEval : MixJSON, InstallableCommand
auto state = getEvalState();
- auto v = installable->toValue(*state).first;
+ auto [v, pos] = installable->toValue(*state);
PathSet context;
if (apply) {
@@ -77,15 +71,53 @@ struct CmdEval : MixJSON, InstallableCommand
v = vRes;
}
- if (raw) {
+ if (writeTo) {
+ stopProgressBar();
+
+ if (pathExists(*writeTo))
+ throw Error("path '%s' already exists", *writeTo);
+
+ std::function<void(Value & v, const Pos & pos, const Path & path)> recurse;
+
+ recurse = [&](Value & v, const Pos & pos, const Path & path)
+ {
+ state->forceValue(v);
+ if (v.type() == nString)
+ // FIXME: disallow strings with contexts?
+ writeFile(path, v.string.s);
+ else if (v.type() == nAttrs) {
+ if (mkdir(path.c_str(), 0777) == -1)
+ throw SysError("creating directory '%s'", path);
+ for (auto & attr : *v.attrs)
+ try {
+ if (attr.name == "." || attr.name == "..")
+ throw Error("invalid file name '%s'", attr.name);
+ recurse(*attr.value, *attr.pos, path + "/" + std::string(attr.name));
+ } catch (Error & e) {
+ e.addTrace(*attr.pos, hintfmt("while evaluating the attribute '%s'", attr.name));
+ throw;
+ }
+ }
+ else
+ throw TypeError("value at '%s' is not a string or an attribute set", pos);
+ };
+
+ recurse(*v, pos, *writeTo);
+ }
+
+ else if (raw) {
stopProgressBar();
std::cout << state->coerceToString(noPos, *v, context);
- } else if (json) {
+ }
+
+ else if (json) {
JSONPlaceholder jsonOut(std::cout);
printValueAsJSON(*state, true, *v, jsonOut, context);
- } else {
+ }
+
+ else {
state->forceValueDeep(*v);
- logger->stdout("%s", *v);
+ logger->cout("%s", *v);
}
}
};
diff --git a/src/nix/eval.md b/src/nix/eval.md
new file mode 100644
index 000000000..61334cde1
--- /dev/null
+++ b/src/nix/eval.md
@@ -0,0 +1,74 @@
+R""(
+
+# Examples
+
+* Evaluate a Nix expression given on the command line:
+
+ ```console
+ # nix eval --expr '1 + 2'
+ ```
+
+* Evaluate a Nix expression to JSON:
+
+ ```console
+ # nix eval --json --expr '{ x = 1; }'
+ {"x":1}
+ ```
+
+* Evaluate a Nix expression from a file:
+
+ ```console
+ # nix eval -f ./my-nixpkgs hello.name
+ ```
+
+* Get the current version of the `nixpkgs` flake:
+
+ ```console
+ # nix eval --raw nixpkgs#lib.version
+ ```
+
+* Print the store path of the Hello package:
+
+ ```console
+ # nix eval --raw nixpkgs#hello
+ ```
+
+* Get a list of checks in the `nix` flake:
+
+ ```console
+ # nix eval nix#checks.x86_64-linux --apply builtins.attrNames
+ ```
+
+* Generate a directory with the specified contents:
+
+ ```console
+ # nix eval --write-to ./out --expr '{ foo = "bar"; subdir.bla = "123"; }'
+ # cat ./out/foo
+ bar
+ # cat ./out/subdir/bla
+ 123
+
+# Description
+
+This command evaluates the Nix expression *installable* and prints the
+result on standard output.
+
+# Output format
+
+`nix eval` can produce output in several formats:
+
+* By default, the evaluation result is printed as a Nix expression.
+
+* With `--json`, the evaluation result is printed in JSON format. Note
+ that this fails if the result contains values that are not
+ representable as JSON, such as functions.
+
+* With `--raw`, the evaluation result must be a string, which is
+ printed verbatim, without any quoting.
+
+* With `--write-to` *path*, the evaluation result must be a string or
+ a nested attribute set whose leaf values are strings. These strings
+ are written to files named *path*/*attrpath*. *path* must not
+ already exist.
+
+)""
diff --git a/src/nix/flake-archive.md b/src/nix/flake-archive.md
new file mode 100644
index 000000000..85bbeeb16
--- /dev/null
+++ b/src/nix/flake-archive.md
@@ -0,0 +1,29 @@
+R""(
+
+# Examples
+
+* Copy the `dwarffs` flake and its dependencies to a binary cache:
+
+ ```console
+ # nix flake archive --to file:///tmp/my-cache dwarffs
+ ```
+
+* Fetch the `dwarffs` flake and its dependencies to the local Nix
+ store:
+
+ ```console
+ # nix flake archive dwarffs
+ ```
+
+* Print the store paths of the flake sources of NixOps without
+ fetching them:
+
+ ```console
+ # nix flake archive --json --dry-run nixops
+ ```
+
+# Description
+
+FIXME
+
+)""
diff --git a/src/nix/flake-check.md b/src/nix/flake-check.md
new file mode 100644
index 000000000..dc079ba0c
--- /dev/null
+++ b/src/nix/flake-check.md
@@ -0,0 +1,68 @@
+R""(
+
+# Examples
+
+* Evaluate the flake in the current directory, and build its checks:
+
+ ```console
+ # nix flake check
+ ```
+
+* Verify that the `patchelf` flake evaluates, but don't build its
+ checks:
+
+ ```console
+ # nix flake check --no-build github:NixOS/patchelf
+ ```
+
+# Description
+
+This command verifies that the flake specified by flake reference
+*flake-url* can be evaluated successfully (as detailed below), and
+that the derivations specified by the flake's `checks` output can be
+built successfully.
+
+# Evaluation checks
+
+This following flake output attributes must be derivations:
+
+* `checks.`*system*`.`*name*
+* `defaultPackage.`*system*`
+* `devShell.`*system*`
+* `nixosConfigurations.`*name*`.config.system.build.toplevel
+* `packages.`*system*`.`*name*
+
+The following flake output attributes must be [app
+definitions](./nix3-run.md):
+
+* `apps.`*system*`.`*name*
+* `defaultApp.`*system*`
+
+The following flake output attributes must be [template
+definitions](./nix3-flake-init.md):
+
+* `defaultTemplate`
+* `templates`.`*name*
+
+The following flake output attributes must be *Nixpkgs overlays*:
+
+* `overlay`
+* `overlays`.`*name*
+
+The following flake output attributes must be *NixOS modules*:
+
+* `nixosModule`
+* `nixosModules`.`*name*
+
+The following flake output attributes must be
+[bundlers](./nix3-bundle.md):
+
+* `bundlers`.`*name*
+* `defaultBundler`
+
+In addition, the `hydraJobs` output is evaluated in the same way as
+Hydra's `hydra-eval-jobs` (i.e. as a arbitrarily deeply nested
+attribute set of derivations). Similarly, the
+`legacyPackages`.*system* output is evaluated like `nix-env -qa`.
+
+)""
diff --git a/src/nix/flake-clone.md b/src/nix/flake-clone.md
new file mode 100644
index 000000000..36cb96051
--- /dev/null
+++ b/src/nix/flake-clone.md
@@ -0,0 +1,18 @@
+R""(
+
+# Examples
+
+* Check out the source code of the `dwarffs` flake and build it:
+
+ ```console
+ # nix flake clone dwarffs --dest dwarffs
+ # cd dwarffs
+ # nix build
+ ```
+
+# Description
+
+This command performs a Git or Mercurial clone of the repository
+containing the source code of the flake *flake-url*.
+
+)""
diff --git a/src/nix/flake-info.md b/src/nix/flake-info.md
new file mode 100644
index 000000000..fda3171db
--- /dev/null
+++ b/src/nix/flake-info.md
@@ -0,0 +1,99 @@
+R""(
+
+# Examples
+
+* Show what `nixpkgs` resolves to:
+
+ ```console
+ # nix flake info nixpkgs
+ Resolved URL: github:NixOS/nixpkgs
+ Locked URL: github:NixOS/nixpkgs/b67ba0bfcc714453cdeb8d713e35751eb8b4c8f4
+ Description: A collection of packages for the Nix package manager
+ Path: /nix/store/23qapccs6cfmwwrlq8kr41vz5vdmns3r-source
+ Revision: b67ba0bfcc714453cdeb8d713e35751eb8b4c8f4
+ Last modified: 2020-12-23 12:36:12
+ ```
+
+* Show information about `dwarffs` in JSON format:
+
+ ```console
+ # nix flake info dwarffs --json | jq .
+ {
+ "description": "A filesystem that fetches DWARF debug info from the Internet on demand",
+ "lastModified": 1597153508,
+ "locked": {
+ "lastModified": 1597153508,
+ "narHash": "sha256-VHg3MYVgQ12LeRSU2PSoDeKlSPD8PYYEFxxwkVVDRd0=",
+ "owner": "edolstra",
+ "repo": "dwarffs",
+ "rev": "d181d714fd36eb06f4992a1997cd5601e26db8f5",
+ "type": "github"
+ },
+ "original": {
+ "id": "dwarffs",
+ "type": "indirect"
+ },
+ "originalUrl": "flake:dwarffs",
+ "path": "/nix/store/hang3792qwdmm2n0d9nsrs5n6bsws6kv-source",
+ "resolved": {
+ "owner": "edolstra",
+ "repo": "dwarffs",
+ "type": "github"
+ },
+ "resolvedUrl": "github:edolstra/dwarffs",
+ "revision": "d181d714fd36eb06f4992a1997cd5601e26db8f5",
+ "url": "github:edolstra/dwarffs/d181d714fd36eb06f4992a1997cd5601e26db8f5"
+ }
+ ```
+
+# Description
+
+This command shows information about the flake specified by the flake
+reference *flake-url*. It resolves the flake reference using the
+[flake registry](./nix3-registry.md), fetches it, and prints some meta
+data. This includes:
+
+* `Resolved URL`: If *flake-url* is a flake identifier, then this is
+ the flake reference that specifies its actual location, looked up in
+ the flake registry.
+
+* `Locked URL`: A flake reference that contains a commit or content
+ hash and thus uniquely identifies a specific flake version.
+
+* `Description`: A one-line description of the flake, taken from the
+ `description` field in `flake.nix`.
+
+* `Path`: The store path containing the source code of the flake.
+
+* `Revision`: The Git or Mercurial commit hash of the locked flake.
+
+* `Revisions`: The number of ancestors of the Git or Mercurial commit
+ of the locked flake. Note that this is not available for `github`
+ flakes.
+
+* `Last modified`: For Git or Mercurial flakes, this is the commit
+ time of the commit of the locked flake; for tarball flakes, it's the
+ most recent timestamp of any file inside the tarball.
+
+With `--json`, the output is a JSON object with the following fields:
+
+* `original` and `originalUrl`: The flake reference specified by the
+ user (*flake-url*) in attribute set and URL representation.
+
+* `resolved` and `resolvedUrl`: The resolved flake reference (see
+ above) in attribute set and URL representation.
+
+* `locked` and `lockedUrl`: The locked flake reference (see above) in
+ attribute set and URL representation.
+
+* `description`: See `Description` above.
+
+* `path`: See `Path` above.
+
+* `revision`: See `Revision` above.
+
+* `revCount`: See `Revisions` above.
+
+* `lastModified`: See `Last modified` above.
+
+)""
diff --git a/src/nix/flake-init.md b/src/nix/flake-init.md
new file mode 100644
index 000000000..c66154ad5
--- /dev/null
+++ b/src/nix/flake-init.md
@@ -0,0 +1,54 @@
+R""(
+
+# Examples
+
+* Create a flake using the default template:
+
+ ```console
+ # nix flake init
+ ```
+
+* List available templates:
+
+ ```console
+ # nix flake show templates
+ ```
+
+* Create a flake from a specific template:
+
+ ```console
+ # nix flake init -t templates#simpleContainer
+ ```
+
+# Description
+
+This command creates a flake in the current directory by copying the
+files of a template. It will not overwrite existing files. The default
+template is `templates#defaultTemplate`, but this can be overriden
+using `-t`.
+
+# Template definitions
+
+A flake can declare templates through its `templates` and
+`defaultTemplate` output attributes. A template has two attributes:
+
+* `description`: A one-line description of the template, in CommonMark
+ syntax.
+
+* `path`: The path of the directory to be copied.
+
+Here is an example:
+
+```
+outputs = { self }: {
+
+ templates.rust = {
+ path = ./rust;
+ description = "A simple Rust/Cargo project";
+ };
+
+ templates.defaultTemplate = self.templates.rust;
+}
+```
+
+)""
diff --git a/src/nix/flake-list-inputs.md b/src/nix/flake-list-inputs.md
new file mode 100644
index 000000000..250e13be0
--- /dev/null
+++ b/src/nix/flake-list-inputs.md
@@ -0,0 +1,23 @@
+R""(
+
+# Examples
+
+* Show the inputs of the `hydra` flake:
+
+ ```console
+ # nix flake list-inputs github:NixOS/hydra
+ github:NixOS/hydra/bde8d81876dfc02143e5070e42c78d8f0d83d6f7
+ ├───nix: github:NixOS/nix/79aa7d95183cbe6c0d786965f0dbff414fd1aa67
+ │ ├───lowdown-src: github:kristapsdz/lowdown/1705b4a26fbf065d9574dce47a94e8c7c79e052f
+ │ └───nixpkgs: github:NixOS/nixpkgs/ad0d20345219790533ebe06571f82ed6b034db31
+ └───nixpkgs follows input 'nix/nixpkgs'
+ ```
+
+# Description
+
+This command shows the inputs of the flake specified by the flake
+referenced *flake-url*. Since it prints the locked inputs that result
+from generating or updating the lock file, this command essentially
+displays the contents of the flake's lock file in human-readable form.
+
+)""
diff --git a/src/nix/flake-new.md b/src/nix/flake-new.md
new file mode 100644
index 000000000..725695c01
--- /dev/null
+++ b/src/nix/flake-new.md
@@ -0,0 +1,34 @@
+R""(
+
+# Examples
+
+* Create a flake using the default template in the directory `hello`:
+
+ ```console
+ # nix flake new hello
+ ```
+
+* List available templates:
+
+ ```console
+ # nix flake show templates
+ ```
+
+* Create a flake from a specific template in the directory `hello`:
+
+ ```console
+ # nix flake new hello -t templates#trivial
+ ```
+
+# Description
+
+This command creates a flake in the directory `dest-dir`, which must
+not already exist. It's equivalent to:
+
+```console
+# mkdir dest-dir
+# cd dest-dir
+# nix flake init
+```
+
+)""
diff --git a/src/nix/flake-prefetch.md b/src/nix/flake-prefetch.md
new file mode 100644
index 000000000..a1cf0289a
--- /dev/null
+++ b/src/nix/flake-prefetch.md
@@ -0,0 +1,28 @@
+R""(
+
+# Examples
+
+* Download a tarball and unpack it:
+
+ ```console
+ # nix flake prefetch https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.5.tar.xz
+ Downloaded 'https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.5.tar.xz?narHash=sha256-3XYHZANT6AFBV0BqegkAZHbba6oeDkIUCDwbATLMhAY='
+ to '/nix/store/sl5vvk8mb4ma1sjyy03kwpvkz50hd22d-source' (hash
+ 'sha256-3XYHZANT6AFBV0BqegkAZHbba6oeDkIUCDwbATLMhAY=').
+ ```
+
+* Download the `dwarffs` flake (looked up in the flake registry):
+
+ ```console
+ # nix flake prefetch dwarffs --json
+ {"hash":"sha256-VHg3MYVgQ12LeRSU2PSoDeKlSPD8PYYEFxxwkVVDRd0="
+ ,"storePath":"/nix/store/hang3792qwdmm2n0d9nsrs5n6bsws6kv-source"}
+ ```
+
+# Description
+
+This command downloads the source tree denoted by flake reference
+*flake-url*. Note that this does not need to be a flake (i.e. it does
+not have to contain a `flake.nix` file).
+
+)""
diff --git a/src/nix/flake-show.md b/src/nix/flake-show.md
new file mode 100644
index 000000000..1a42c44a0
--- /dev/null
+++ b/src/nix/flake-show.md
@@ -0,0 +1,38 @@
+R""(
+
+# Examples
+
+* Show the output attributes provided by the `patchelf` flake:
+
+ ```console
+ github:NixOS/patchelf/f34751b88bd07d7f44f5cd3200fb4122bf916c7e
+ ├───checks
+ │ ├───aarch64-linux
+ │ │ └───build: derivation 'patchelf-0.12.20201207.f34751b'
+ │ ├───i686-linux
+ │ │ └───build: derivation 'patchelf-0.12.20201207.f34751b'
+ │ └───x86_64-linux
+ │ └───build: derivation 'patchelf-0.12.20201207.f34751b'
+ ├───defaultPackage
+ │ ├───aarch64-linux: package 'patchelf-0.12.20201207.f34751b'
+ │ ├───i686-linux: package 'patchelf-0.12.20201207.f34751b'
+ │ └───x86_64-linux: package 'patchelf-0.12.20201207.f34751b'
+ ├───hydraJobs
+ │ ├───build
+ │ │ ├───aarch64-linux: derivation 'patchelf-0.12.20201207.f34751b'
+ │ │ ├───i686-linux: derivation 'patchelf-0.12.20201207.f34751b'
+ │ │ └───x86_64-linux: derivation 'patchelf-0.12.20201207.f34751b'
+ │ ├───coverage: derivation 'patchelf-coverage-0.12.20201207.f34751b'
+ │ ├───release: derivation 'patchelf-0.12.20201207.f34751b'
+ │ └───tarball: derivation 'patchelf-tarball-0.12.20201207.f34751b'
+ └───overlay: Nixpkgs overlay
+ ```
+
+# Description
+
+This command shows the output attributes provided by the flake
+specified by flake reference *flake-url*. These are the top-level
+attributes in the `outputs` of the flake, as well as lower-level
+attributes for some standard outputs (e.g. `packages` or `checks`).
+
+)""
diff --git a/src/nix/flake-update.md b/src/nix/flake-update.md
new file mode 100644
index 000000000..a2ffedd2a
--- /dev/null
+++ b/src/nix/flake-update.md
@@ -0,0 +1,53 @@
+R""(
+
+# Examples
+
+* Update the `nixpkgs` and `nix` inputs of the flake in the current
+ directory:
+
+ ```console
+ # nix flake update --update-input nixpkgs --update-input nix
+ * Updated 'nix': 'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' -> 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c'
+ * Updated 'nixpkgs': 'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' -> 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293'
+ ```
+
+* Recreate the lock file (i.e. update all inputs) and commit the new
+ lock file:
+
+ ```console
+ # nix flake update --recreate-lock-file --commit-lock-file
+ …
+ warning: committed new revision '158bcbd9d6cc08ab859c0810186c1beebc982aad'
+ ```
+
+# Description
+
+This command updates the lock file of a flake (`flake.lock`) so that
+it contains a lock for every flake input specified in
+`flake.nix`. Note that every command that operates on a flake will
+also update the lock file if needed, and supports the same
+flags. Therefore,
+
+```console
+# nix flake update --update-input nixpkgs
+# nix build
+```
+
+is equivalent to:
+
+```console
+# nix build --update-input nixpkgs
+```
+
+Thus, this command is only useful if you want to update the lock file
+separately from any other action such as building.
+
+> **Note**
+>
+> This command does *not* update locks that are already present unless
+> you explicitly ask for it using `--update-input` or
+> `--recreate-lock-file`. Thus, if the lock file already has locks for
+> every input, then `nix flake update` (without arguments) does
+> nothing.
+
+)""
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index d45f13029..b9cde5d6d 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -62,31 +62,31 @@ public:
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());
+ logger->cout("Resolved URL: %s", flake.resolvedRef.to_string());
+ logger->cout("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));
+ logger->cout("Description: %s", *flake.description);
+ logger->cout("Path: %s", store.printStorePath(flake.sourceInfo->storePath));
if (auto rev = flake.lockedRef.input.getRev())
- logger->stdout("Revision: %s", rev->to_string(Base16, false));
+ logger->cout("Revision: %s", rev->to_string(Base16, false));
if (auto revCount = flake.lockedRef.input.getRevCount())
- logger->stdout("Revisions: %s", *revCount);
+ logger->cout("Revisions: %s", *revCount);
if (auto lastModified = flake.lockedRef.input.getLastModified())
- logger->stdout("Last modified: %s",
+ logger->cout("Last modified: %s",
std::put_time(std::localtime(&*lastModified), "%F %T"));
}
-static nlohmann::json flakeToJson(const Store & store, const Flake & flake)
+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["original"] = fetchers::attrsToJSON(flake.originalRef.toAttrs());
j["resolvedUrl"] = flake.resolvedRef.to_string();
- j["resolved"] = attrsToJson(flake.resolvedRef.toAttrs());
+ j["resolved"] = fetchers::attrsToJSON(flake.resolvedRef.toAttrs());
j["url"] = flake.lockedRef.to_string(); // FIXME: rename to lockedUrl
- j["locked"] = attrsToJson(flake.lockedRef.toAttrs());
+ j["locked"] = fetchers::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())
@@ -104,6 +104,13 @@ struct CmdFlakeUpdate : FlakeCommand
return "update flake lock file";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-update.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
/* Use --refresh by default for 'nix flake update'. */
@@ -134,13 +141,20 @@ struct CmdFlakeInfo : FlakeCommand, MixJSON
return "list info about a given flake";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-info.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
auto flake = getFlake();
if (json) {
- auto json = flakeToJson(*store, flake);
- logger->stdout("%s", json.dump());
+ auto json = flakeToJSON(*store, flake);
+ logger->cout("%s", json.dump());
} else
printFlakeInfo(*store, flake);
}
@@ -153,14 +167,21 @@ struct CmdFlakeListInputs : FlakeCommand, MixJSON
return "list flake inputs";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-list-inputs.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
auto flake = lockFlake();
if (json)
- logger->stdout("%s", flake.lockFile.toJson());
+ logger->cout("%s", flake.lockFile.toJSON());
else {
- logger->stdout("%s", flake.flake.lockedRef);
+ logger->cout("%s", flake.flake.lockedRef);
std::unordered_set<std::shared_ptr<Node>> visited;
@@ -172,7 +193,7 @@ struct CmdFlakeListInputs : FlakeCommand, MixJSON
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",
+ logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
prefix + (last ? treeLast : treeConn), input.first,
*lockedNode ? (*lockedNode)->lockedRef : flake.flake.lockedRef);
@@ -180,7 +201,7 @@ struct CmdFlakeListInputs : FlakeCommand, MixJSON
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'",
+ logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL " follows input '%s'",
prefix + (last ? treeLast : treeConn), input.first,
printInputPath(*follows));
}
@@ -201,7 +222,7 @@ struct CmdFlakeCheck : FlakeCommand
{
addFlag({
.longName = "no-build",
- .description = "do not build checks",
+ .description = "Do not build checks.",
.handler = {&build, false}
});
}
@@ -211,6 +232,13 @@ struct CmdFlakeCheck : FlakeCommand
return "check whether the flake evaluates and run its tests";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-check.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
settings.readOnlyMode = !build;
@@ -260,7 +288,7 @@ struct CmdFlakeCheck : FlakeCommand
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")
+ if (!v.isLambda() || 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")
@@ -276,10 +304,10 @@ struct CmdFlakeCheck : FlakeCommand
auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) {
try {
state->forceValue(v, pos);
- if (v.type == tLambda) {
+ if (v.isLambda()) {
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) {
+ } else if (v.type() == nAttrs) {
for (auto & attr : *v.attrs)
try {
state->forceValue(*attr.value, *attr.pos);
@@ -371,7 +399,7 @@ struct CmdFlakeCheck : FlakeCommand
auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) {
try {
state->forceValue(v, pos);
- if (v.type != tLambda)
+ if (!v.isLambda())
throw Error("bundler must be a function");
if (!v.lambda.fun->formals ||
v.lambda.fun->formals->argNames.find(state->symbols.create("program")) == v.lambda.fun->formals->argNames.end() ||
@@ -545,7 +573,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
addFlag({
.longName = "template",
.shortName = 't',
- .description = "the template to use",
+ .description = "The template to use.",
.labels = {"template"},
.handler = {&templateUrl},
.completer = {[&](size_t, std::string_view prefix) {
@@ -567,7 +595,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
- auto installable = InstallableFlake(
+ auto installable = InstallableFlake(nullptr,
evalState, std::move(templateFlakeRef),
Strings{templateName == "" ? "defaultTemplate" : templateName},
Strings(attrsPathPrefixes), lockFlags);
@@ -631,22 +659,11 @@ struct CmdFlakeInit : CmdFlakeInitCommon
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"
- },
- };
+ std::string doc() override
+ {
+ return
+ #include "flake-init.md"
+ ;
}
CmdFlakeInit()
@@ -662,6 +679,13 @@ struct CmdFlakeNew : CmdFlakeInitCommon
return "create a flake in the specified directory from a template";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-new.md"
+ ;
+ }
+
CmdFlakeNew()
{
expectArgs({
@@ -681,12 +705,19 @@ struct CmdFlakeClone : FlakeCommand
return "clone flake repository";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-clone.md"
+ ;
+ }
+
CmdFlakeClone()
{
addFlag({
.longName = "dest",
.shortName = 'f',
- .description = "destination path",
+ .description = "Clone the flake to path *dest*.",
.labels = {"path"},
.handler = {&destDir}
});
@@ -720,22 +751,11 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
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"
- },
- };
+ std::string doc() override
+ {
+ return
+ #include "flake-archive.md"
+ ;
}
void run(nix::ref<nix::Store> store) override
@@ -787,7 +807,7 @@ struct CmdFlakeShow : FlakeCommand
{
addFlag({
.longName = "legacy",
- .description = "show the contents of the 'legacyPackages' output",
+ .description = "Show the contents of the `legacyPackages` output.",
.handler = {&showLegacy, true}
});
}
@@ -797,6 +817,13 @@ struct CmdFlakeShow : FlakeCommand
return "show the outputs provided by a flake";
}
+ std::string doc() override
+ {
+ return
+ #include "flake-show.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
auto state = getEvalState();
@@ -811,7 +838,7 @@ struct CmdFlakeShow : FlakeCommand
try {
auto recurse = [&]()
{
- logger->stdout("%s", headerPrefix);
+ logger->cout("%s", headerPrefix);
auto attrs = visitor.getAttrs();
for (const auto & [i, attr] : enumerate(attrs)) {
bool last = i + 1 == attrs.size();
@@ -837,7 +864,7 @@ struct CmdFlakeShow : FlakeCommand
}
*/
- logger->stdout("%s: %s '%s'",
+ logger->cout("%s: %s '%s'",
headerPrefix,
attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" :
attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" :
@@ -853,7 +880,8 @@ struct CmdFlakeShow : FlakeCommand
|| attrPath[0] == "nixosConfigurations"
|| attrPath[0] == "nixosModules"
|| attrPath[0] == "defaultApp"
- || attrPath[0] == "templates"))
+ || attrPath[0] == "templates"
+ || attrPath[0] == "overlays"))
|| ((attrPath.size() == 1 || attrPath.size() == 2)
&& (attrPath[0] == "checks"
|| attrPath[0] == "packages"
@@ -885,7 +913,7 @@ struct CmdFlakeShow : FlakeCommand
if (attrPath.size() == 1)
recurse();
else if (!showLegacy)
- logger->stdout("%s: " ANSI_YELLOW "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix);
+ logger->cout("%s: " ANSI_YELLOW "omitted" ANSI_NORMAL " (use '--legacy' to show)", headerPrefix);
else {
if (visitor.isDerivation())
showDerivation();
@@ -902,7 +930,7 @@ struct CmdFlakeShow : FlakeCommand
auto aType = visitor.maybeGetAttr("type");
if (!aType || aType->getString() != "app")
throw EvalError("not an app definition");
- logger->stdout("%s: app", headerPrefix);
+ logger->cout("%s: app", headerPrefix);
}
else if (
@@ -910,13 +938,14 @@ struct CmdFlakeShow : FlakeCommand
(attrPath.size() == 2 && attrPath[0] == "templates"))
{
auto description = visitor.getAttr("description")->getString();
- logger->stdout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
+ logger->cout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
}
else {
- logger->stdout("%s: %s",
+ logger->cout("%s: %s",
headerPrefix,
- attrPath.size() == 1 && attrPath[0] == "overlay" ? "Nixpkgs overlay" :
+ (attrPath.size() == 1 && attrPath[0] == "overlay")
+ || (attrPath.size() == 2 && attrPath[0] == "overlays") ? "Nixpkgs overlay" :
attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? "NixOS configuration" :
attrPath.size() == 2 && attrPath[0] == "nixosModules" ? "NixOS module" :
ANSI_YELLOW "unknown" ANSI_NORMAL);
@@ -933,6 +962,45 @@ struct CmdFlakeShow : FlakeCommand
}
};
+struct CmdFlakePrefetch : FlakeCommand, MixJSON
+{
+ CmdFlakePrefetch()
+ {
+ }
+
+ std::string description() override
+ {
+ return "download the source tree denoted by a flake reference into the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "flake-prefetch.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto originalRef = getFlakeRef();
+ auto resolvedRef = originalRef.resolve(store);
+ auto [tree, lockedRef] = resolvedRef.fetchTree(store);
+ auto hash = store->queryPathInfo(tree.storePath)->narHash;
+
+ if (json) {
+ auto res = nlohmann::json::object();
+ res["storePath"] = store->printStorePath(tree.storePath);
+ res["hash"] = hash.to_string(SRI, true);
+ logger->cout(res.dump());
+ } else {
+ notice("Downloaded '%s' to '%s' (hash '%s').",
+ lockedRef.to_string(),
+ store->printStorePath(tree.storePath),
+ hash.to_string(SRI, true));
+ }
+ }
+};
+
struct CmdFlake : NixMultiCommand
{
CmdFlake()
@@ -946,6 +1014,7 @@ struct CmdFlake : NixMultiCommand
{"clone", []() { return make_ref<CmdFlakeClone>(); }},
{"archive", []() { return make_ref<CmdFlakeArchive>(); }},
{"show", []() { return make_ref<CmdFlakeShow>(); }},
+ {"prefetch", []() { return make_ref<CmdFlakePrefetch>(); }},
})
{
}
@@ -955,6 +1024,13 @@ struct CmdFlake : NixMultiCommand
return "manage Nix flakes";
}
+ std::string doc() override
+ {
+ return
+ #include "flake.md"
+ ;
+ }
+
void run() override
{
if (!command)
diff --git a/src/nix/flake.md b/src/nix/flake.md
new file mode 100644
index 000000000..440c45dd1
--- /dev/null
+++ b/src/nix/flake.md
@@ -0,0 +1,566 @@
+R""(
+
+# Description
+
+`nix flake` provides subcommands for creating, modifying and querying
+*Nix flakes*. Flakes are the unit for packaging Nix code in a
+reproducible and discoverable way. They can have dependencies on other
+flakes, making it possible to have multi-repository Nix projects.
+
+A flake is a filesystem tree (typically fetched from a Git repository
+or a tarball) that contains a file named `flake.nix` in the root
+directory. `flake.nix` specifies some metadata about the flake such as
+dependencies (called *inputs*), as well as its *outputs* (the Nix
+values such as packages or NixOS modules provided by the flake).
+
+# Flake references
+
+Flake references (*flakerefs*) are a way to specify the location of a
+flake. These have two different forms:
+
+* An attribute set representation, e.g.
+
+ ```nix
+ {
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+ }
+ ```
+
+ The only required attribute is `type`. The supported types are
+ listed below.
+
+* A URL-like syntax, e.g.
+
+ ```
+ github:NixOS/nixpkgs
+ ```
+
+ These are used on the command line as a more convenient alternative
+ to the attribute set representation. For instance, in the command
+
+ ```console
+ # nix build github:NixOS/nixpkgs#hello
+ ```
+
+ `github:NixOS/nixpkgs` is a flake reference (while `hello` is an
+ output attribute). They are also allowed in the `inputs` attribute
+ of a flake, e.g.
+
+ ```nix
+ inputs.nixpkgs.url = github:NixOS/nixpkgs;
+ ```
+
+ is equivalent to
+
+ ```nix
+ inputs.nixpkgs = {
+ type = "github";
+ owner = "NixOS";
+ repo = "nixpkgs";
+ };
+ ```
+
+## Examples
+
+Here are some examples of flake references in their URL-like representation:
+
+* `.`: The flake in the current directory.
+* `/home/alice/src/patchelf`: A flake in some other directory.
+* `nixpkgs`: The `nixpkgs` entry in the flake registry.
+* `nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293`: The `nixpkgs`
+ entry in the flake registry, with its Git revision overriden to a
+ specific value.
+* `github:NixOS/nixpkgs`: The `master` branch of the `NixOS/nixpkgs`
+ repository on GitHub.
+* `github:NixOS/nixpkgs/nixos-20.09`: The `nixos-20.09` branch of the
+ `nixpkgs` repository.
+* `github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293`: A
+ specific revision of the `nixpkgs` repository.
+* `github:edolstra/nix-warez?dir=blender`: A flake in a subdirectory
+ of a GitHub repository.
+* `git+https://github.com/NixOS/patchelf`: A Git repository.
+* `git+https://github.com/NixOS/patchelf?ref=master`: A specific
+ branch of a Git repository.
+* `git+https://github.com/NixOS/patchelf?ref=master&rev=f34751b88bd07d7f44f5cd3200fb4122bf916c7e`:
+ A specific branch *and* revision of a Git repository.
+* `https://github.com/NixOS/patchelf/archive/master.tar.gz`: A tarball
+ flake.
+
+## Flake reference attributes
+
+The following generic flake reference attributes are supported:
+
+* `dir`: The subdirectory of the flake in which `flake.nix` is
+ located. This parameter enables having multiple flakes in a
+ repository or tarball. The default is the root directory of the
+ flake.
+
+* `narHash`: The hash of the NAR serialisation (in SRI format) of the
+ contents of the flake. This is useful for flake types such as
+ tarballs that lack a unique content identifier such as a Git commit
+ hash.
+
+In addition, the following attributes are common to several flake
+reference types:
+
+* `rev`: A Git or Mercurial commit hash.
+
+* `ref`: A Git or Mercurial branch or tag name.
+
+Finally, some attribute are typically not specified by the user, but
+can occur in *locked* flake references and are available to Nix code:
+
+* `revCount`: The number of ancestors of the commit `rev`.
+
+* `lastModified`: The timestamp (in seconds since the Unix epoch) of
+ the last modification of this version of the flake. For
+ Git/Mercurial flakes, this is the commit time of commit *rev*, while
+ for tarball flakes, it's the most recent timestamp of any file
+ inside the tarball.
+
+## Types
+
+Currently the `type` attribute can be one of the following:
+
+* `path`: arbitrary local directories, or local Git trees. The
+ required attribute `path` specifies the path of the flake. The URL
+ form is
+
+ ```
+ [path:]<path>(\?<params)?
+ ```
+
+ where *path* is an absolute path.
+
+ *path* must be a directory in the file system containing a file
+ named `flake.nix`.
+
+ If the directory or any of its parents is a Git repository, then
+ this is essentially equivalent to `git+file://<path>` (see below),
+ except that the `dir` parameter is derived automatically. For
+ example, if `/foo/bar` is a Git repository, then the flake reference
+ `/foo/bar/flake` is equivalent to `/foo/bar?dir=flake`.
+
+ If the directory is not inside a Git repository, then the flake
+ contents is the entire contents of *path*.
+
+ *path* generally must be an absolute path. However, on the command
+ line, it can be a relative path (e.g. `.` or `./foo`) which is
+ interpreted as relative to the current directory. In this case, it
+ must start with `.` to avoid ambiguity with registry lookups
+ (e.g. `nixpkgs` is a registry lookup; `./nixpkgs` is a relative
+ path).
+
+* `git`: Git repositories. The location of the repository is specified
+ by the attribute `url`.
+
+ They have the URL form
+
+ ```
+ git(+http|+https|+ssh|+git|+file|):(//<server>)?<path>(\?<params>)?
+ ```
+
+ The `ref` attribute defaults to `master`.
+
+ The `rev` attribute must denote a commit that exists in the branch
+ or tag specified by the `ref` attribute, since Nix doesn't do a full
+ clone of the remote repository by default (and the Git protocol
+ doesn't allow fetching a `rev` without a known `ref`). The default
+ is the commit currently pointed to by `ref`.
+
+ For example, the following are valid Git flake references:
+
+ * `git+https://example.org/my/repo`
+ * `git+https://example.org/my/repo?dir=flake1`
+ * `git+ssh://git@github.com/NixOS/nix?ref=v1.2.3`
+ * `git://github.com/edolstra/dwarffs?ref=unstable&rev=e486d8d40e626a20e06d792db8cc5ac5aba9a5b4`
+ * `git+file:///home/my-user/some-repo/some-repo`
+
+* `mercurial`: Mercurial repositories. The URL form is similar to the
+ `git` type, except that the URL schema must be one of `hg+http`,
+ `hg+https`, `hg+ssh` or `hg+file`.
+
+* `tarball`: Tarballs. The location of the tarball is specified by the
+ attribute `url`.
+
+ In URL form, the schema must be `http://`, `https://` or `file://`
+ URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`
+ or `.tar.bz2`.
+
+* `github`: A more efficient way to fetch repositories from
+ GitHub. The following attributes are required:
+
+ * `owner`: The owner of the repository.
+
+ * `repo`: The name of the repository.
+
+ These are downloaded as tarball archives, rather than
+ through Git. This is often much faster and uses less disk space
+ since it doesn't require fetching the entire history of the
+ repository. On the other hand, it doesn't allow incremental fetching
+ (but full downloads are often faster than incremental fetches!).
+
+ The URL syntax for `github` flakes is:
+
+ ```
+ github:<owner>/<repo>(/<rev-or-ref>)?(\?<params>)?
+ ```
+
+ `<rev-or-ref>` specifies the name of a branch or tag (`ref`), or a
+ commit hash (`rev`). Note that unlike Git, GitHub allows fetching by
+ commit hash without specifying a branch or tag.
+
+ Some examples:
+
+ * `github:edolstra/dwarffs`
+ * `github:edolstra/dwarffs/unstable`
+ * `github:edolstra/dwarffs/d3f2baba8f425779026c6ec04021b2e927f61e31`
+
+* `indirect`: Indirections through the flake registry. These have the
+ form
+
+ ```
+ [flake:]<flake-id>(/<rev-or-ref>(/rev)?)?
+ ```
+
+ These perform a lookup of `<flake-id>` in the flake registry. or
+ example, `nixpkgs` and `nixpkgs/release-20.09` are indirect flake
+ references. The specified `rev` and/or `ref` are merged with the
+ entry in the registry; see [nix registry](./nix3-registry.md) for
+ details.
+
+# Flake format
+
+As an example, here is a simple `flake.nix` that depends on the
+Nixpkgs flake and provides a single package (i.e. an installable
+derivation):
+
+```nix
+{
+ description = "A flake for building Hello World";
+
+ inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-20.03;
+
+ outputs = { self, nixpkgs }: {
+
+ defaultPackage.x86_64-linux =
+ # Notice the reference to nixpkgs here.
+ with import nixpkgs { system = "x86_64-linux"; };
+ stdenv.mkDerivation {
+ name = "hello";
+ src = self;
+ buildPhase = "gcc -o hello ./hello.c";
+ installPhase = "mkdir -p $out/bin; install -t $out/bin hello";
+ };
+
+ };
+}
+```
+
+The following attributes are supported in `flake.nix`:
+
+* `description`: A short, one-line description of the flake.
+
+* `inputs`: An attrset specifying the dependencies of the flake
+ (described below).
+
+* `outputs`: A function that, given an attribute set containing the
+ outputs of each of the input flakes keyed by their identifier,
+ yields the Nix values provided by this flake. Thus, in the example
+ above, `inputs.nixpkgs` contains the result of the call to the
+ `outputs` function of the `nixpkgs` flake.
+
+ In addition to the outputs of each input, each input in `inputs`
+ also contains some metadata about the inputs. These are:
+
+ * `outPath`: The path in the Nix store of the flake's source tree.
+
+ * `rev`: The commit hash of the flake's repository, if applicable.
+
+ * `revCount`: The number of ancestors of the revision `rev`. This is
+ not available for `github` repositories, since they're fetched as
+ tarballs rather than as Git repositories.
+
+ * `lastModifiedDate`: The commit time of the revision `rev`, in the
+ format `%Y%m%d%H%M%S` (e.g. `20181231100934`). Unlike `revCount`,
+ this is available for both Git and GitHub repositories, so it's
+ useful for generating (hopefully) monotonically increasing version
+ strings.
+
+ * `lastModified`: The commit time of the revision `rev` as an integer
+ denoting the number of seconds since 1970.
+
+ * `narHash`: The SHA-256 (in SRI format) of the NAR serialization of
+ the flake's source tree.
+
+ The value returned by the `outputs` function must be an attribute
+ set. The attributes can have arbitrary values; however, various
+ `nix` subcommands require specific attributes to have a specific
+ value (e.g. `packages.x86_64-linux` must be an attribute set of
+ derivations built for the `x86_64-linux` platform).
+
+## Flake inputs
+
+The attribute `inputs` specifies the dependencies of a flake, as an
+attrset mapping input names to flake references. For example, the
+following specifies a dependency on the `nixpkgs` and `import-cargo`
+repositories:
+
+```nix
+# A GitHub repository.
+inputs.import-cargo = {
+ type = "github";
+ owner = "edolstra";
+ repo = "import-cargo";
+};
+
+# An indirection through the flake registry.
+inputs.nixpkgs = {
+ type = "indirect";
+ id = "nixpkgs";
+};
+```
+
+Alternatively, you can use the URL-like syntax:
+
+```nix
+inputs.import-cargo.url = github:edolstra/import-cargo;
+inputs.nixpkgs.url = "nixpkgs";
+```
+
+Each input is fetched, evaluated and passed to the `outputs` function
+as a set of attributes with the same name as the corresponding
+input. The special input named `self` refers to the outputs and source
+tree of *this* flake. Thus, a typical `outputs` function looks like
+this:
+
+```nix
+outputs = { self, nixpkgs, import-cargo }: {
+ ... outputs ...
+};
+```
+
+It is also possible to omit an input entirely and *only* list it as
+expected function argument to `outputs`. Thus,
+
+```nix
+outputs = { self, nixpkgs }: ...;
+```
+
+without an `inputs.nixpkgs` attribute is equivalent to
+
+```nix
+inputs.nixpkgs = {
+ type = "indirect";
+ id = "nixpkgs";
+};
+```
+
+Repositories that don't contain a `flake.nix` can also be used as
+inputs, by setting the input's `flake` attribute to `false`:
+
+```nix
+inputs.grcov = {
+ type = "github";
+ owner = "mozilla";
+ repo = "grcov";
+ flake = false;
+};
+
+outputs = { self, nixpkgs, grcov }: {
+ packages.x86_64-linux.grcov = stdenv.mkDerivation {
+ src = grcov;
+ ...
+ };
+};
+```
+
+Transitive inputs can be overriden from a `flake.nix` file. For
+example, the following overrides the `nixpkgs` input of the `nixops`
+input:
+
+```nix
+inputs.nixops.inputs.nixpkgs = {
+ type = "github";
+ owner = "my-org";
+ repo = "nixpkgs";
+};
+```
+
+It is also possible to "inherit" an input from another input. This is
+useful to minimize flake dependencies. For example, the following sets
+the `nixpkgs` input of the top-level flake to be equal to the
+`nixpkgs` input of the `dwarffs` input of the top-level flake:
+
+```nix
+inputs.nixops.follows = "dwarffs/nixpkgs";
+```
+
+The value of the `follows` attribute is a `/`-separated sequence of
+input names denoting the path of inputs to be followed from the root
+flake.
+
+Overrides and `follows` can be combined, e.g.
+
+```nix
+inputs.nixops.inputs.nixpkgs.follows = "dwarffs/nixpkgs";
+```
+
+sets the `nixpkgs` input of `nixops` to be the same as the `nixpkgs`
+input of `dwarffs`. It is worth noting, however, that it is generally
+not useful to eliminate transitive `nixpkgs` flake inputs in this
+way. Most flakes provide their functionality through Nixpkgs overlays
+or NixOS modules, which are composed into the top-level flake's
+`nixpkgs` input; so their own `nixpkgs` input is usually irrelevant.
+
+# Lock files
+
+Inputs specified in `flake.nix` are typically "unlocked" in the sense
+that they don't specify an exact revision. To ensure reproducibility,
+Nix will automatically generate and use a *lock file* called
+`flake.lock` in the flake's directory. The lock file contains a graph
+structure isomorphic to the graph of dependencies of the root
+flake. Each node in the graph (except the root node) maps the
+(usually) unlocked input specifications in `flake.nix` to locked input
+specifications. Each node also contains some metadata, such as the
+dependencies (outgoing edges) of the node.
+
+For example, if `flake.nix` has the inputs in the example above, then
+the resulting lock file might be:
+
+```json
+{
+ "version": 7,
+ "root": "n1",
+ "nodes": {
+ "n1": {
+ "inputs": {
+ "nixpkgs": "n2",
+ "import-cargo": "n3",
+ "grcov": "n4"
+ }
+ },
+ "n2": {
+ "inputs": {},
+ "locked": {
+ "owner": "edolstra",
+ "repo": "nixpkgs",
+ "rev": "7f8d4b088e2df7fdb6b513bc2d6941f1d422a013",
+ "type": "github",
+ "lastModified": 1580555482,
+ "narHash": "sha256-OnpEWzNxF/AU4KlqBXM2s5PWvfI5/BS6xQrPvkF5tO8="
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "n3": {
+ "inputs": {},
+ "locked": {
+ "owner": "edolstra",
+ "repo": "import-cargo",
+ "rev": "8abf7b3a8cbe1c8a885391f826357a74d382a422",
+ "type": "github",
+ "lastModified": 1567183309,
+ "narHash": "sha256-wIXWOpX9rRjK5NDsL6WzuuBJl2R0kUCnlpZUrASykSc="
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "import-cargo",
+ "type": "github"
+ }
+ },
+ "n4": {
+ "inputs": {},
+ "locked": {
+ "owner": "mozilla",
+ "repo": "grcov",
+ "rev": "989a84bb29e95e392589c4e73c29189fd69a1d4e",
+ "type": "github",
+ "lastModified": 1580729070,
+ "narHash": "sha256-235uMxYlHxJ5y92EXZWAYEsEb6mm+b069GAd+BOIOxI="
+ },
+ "original": {
+ "owner": "mozilla",
+ "repo": "grcov",
+ "type": "github"
+ },
+ "flake": false
+ }
+ }
+}
+```
+
+This graph has 4 nodes: the root flake, and its 3 dependencies. The
+nodes have arbitrary labels (e.g. `n1`). The label of the root node of
+the graph is specified by the `root` attribute. Nodes contain the
+following fields:
+
+* `inputs`: The dependencies of this node, as a mapping from input
+ names (e.g. `nixpkgs`) to node labels (e.g. `n2`).
+
+* `original`: The original input specification from `flake.lock`, as a
+ set of `builtins.fetchTree` arguments.
+
+* `locked`: The locked input specification, as a set of
+ `builtins.fetchTree` arguments. Thus, in the example above, when we
+ build this flake, the input `nixpkgs` is mapped to revision
+ `7f8d4b088e2df7fdb6b513bc2d6941f1d422a013` of the `edolstra/nixpkgs`
+ repository on GitHub.
+
+ It also includes the attribute `narHash`, specifying the expected
+ contents of the tree in the Nix store (as computed by `nix
+ hash-path`), and may include input-type-specific attributes such as
+ the `lastModified` or `revCount`. The main reason for these
+ attributes is to allow flake inputs to be substituted from a binary
+ cache: `narHash` allows the store path to be computed, while the
+ other attributes are necessary because they provide information not
+ stored in the store path.
+
+* `flake`: A Boolean denoting whether this is a flake or non-flake
+ dependency. Corresponds to the `flake` attribute in the `inputs`
+ attribute in `flake.nix`.
+
+The `original` and `locked` attributes are omitted for the root
+node. This is because we cannot record the commit hash or content hash
+of the root flake, since modifying `flake.lock` will invalidate these.
+
+The graph representation of lock files allows circular dependencies
+between flakes. For example, here are two flakes that reference each
+other:
+
+```nix
+{
+ inputs.b = ... location of flake B ...;
+ # Tell the 'b' flake not to fetch 'a' again, to ensure its 'a' is
+ # *this* 'a'.
+ inputs.b.inputs.a.follows = "";
+ outputs = { self, b }: {
+ foo = 123 + b.bar;
+ xyzzy = 1000;
+ };
+}
+```
+
+and
+
+```nix
+{
+ inputs.a = ... location of flake A ...;
+ inputs.a.inputs.b.follows = "";
+ outputs = { self, a }: {
+ bar = 456 + a.xyzzy;
+ };
+}
+```
+
+Lock files transitively lock direct as well as indirect
+dependencies. That is, if a lock file exists and is up to date, Nix
+will not look at the lock files of dependencies. However, lock file
+generation itself *does* use the lock files of dependencies by
+default.
+
+)""
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 1d23bb0e2..4535e4ab0 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -8,7 +8,7 @@
using namespace nix;
-struct CmdHash : Command
+struct CmdHashBase : Command
{
FileIngestionMethod mode;
Base base = SRI;
@@ -17,20 +17,43 @@ struct CmdHash : Command
std::vector<std::string> paths;
std::optional<std::string> modulus;
- CmdHash(FileIngestionMethod mode) : mode(mode)
+ CmdHashBase(FileIngestionMethod mode) : mode(mode)
{
- mkFlag(0, "sri", "print hash in SRI format", &base, SRI);
- mkFlag(0, "base64", "print hash in base-64", &base, Base64);
- mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base32);
- mkFlag(0, "base16", "print hash in base-16", &base, Base16);
+ addFlag({
+ .longName = "sri",
+ .description = "Print the hash in SRI format.",
+ .handler = {&base, SRI},
+ });
+
+ addFlag({
+ .longName = "base64",
+ .description = "Print the hash in base-64 format.",
+ .handler = {&base, Base64},
+ });
+
+ addFlag({
+ .longName = "base32",
+ .description = "Print the hash in base-32 (Nix-specific) format.",
+ .handler = {&base, Base32},
+ });
+
+ addFlag({
+ .longName = "base16",
+ .description = "Print the hash in base-16 format.",
+ .handler = {&base, Base16},
+ });
+
addFlag(Flag::mkHashTypeFlag("type", &ht));
+
#if 0
- mkFlag()
- .longName("modulo")
- .description("compute hash modulo specified string")
- .labels({"modulus"})
- .dest(&modulus);
- #endif
+ addFlag({
+ .longName = "modulo",
+ .description = "Compute the hash modulo the specified string.",
+ .labels = {"modulus"},
+ .handler = {&modulus},
+ });
+ #endif\
+
expectArgs({
.label = "paths",
.handler = {&paths},
@@ -40,19 +63,16 @@ struct CmdHash : Command
std::string description() override
{
- const char* d;
switch (mode) {
case FileIngestionMethod::Flat:
- d = "print cryptographic hash of a regular file";
- break;
+ return "print cryptographic hash of a regular file";
case FileIngestionMethod::Recursive:
- d = "print cryptographic hash of the NAR serialisation of a path";
+ return "print cryptographic hash of the NAR serialisation of a path";
+ default:
+ assert(false);
};
- return d;
}
- Category category() override { return catUtility; }
-
void run() override
{
for (auto path : paths) {
@@ -74,14 +94,11 @@ struct CmdHash : Command
Hash h = hashSink->finish().first;
if (truncate && h.hashSize > 20) h = compressHash(h, 20);
- logger->stdout(h.to_string(base, base == SRI));
+ logger->cout(h.to_string(base, base == SRI));
}
}
};
-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
{
Base base;
@@ -103,19 +120,43 @@ struct CmdToBase : Command
"SRI");
}
+ void run() override
+ {
+ for (auto s : args)
+ logger->cout(Hash::parseAny(s, ht).to_string(base, base == SRI));
+ }
+};
+
+struct CmdHash : NixMultiCommand
+{
+ CmdHash()
+ : MultiCommand({
+ {"file", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Flat);; }},
+ {"path", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Recursive); }},
+ {"to-base16", []() { return make_ref<CmdToBase>(Base16); }},
+ {"to-base32", []() { return make_ref<CmdToBase>(Base32); }},
+ {"to-base64", []() { return make_ref<CmdToBase>(Base64); }},
+ {"to-sri", []() { return make_ref<CmdToBase>(SRI); }},
+ })
+ { }
+
+ std::string description() override
+ {
+ return "compute and convert cryptographic hashes";
+ }
+
Category category() override { return catUtility; }
void run() override
{
- for (auto s : args)
- logger->stdout(Hash::parseAny(s, ht).to_string(base, base == SRI));
+ if (!command)
+ throw UsageError("'nix hash' requires a sub-command.");
+ command->second->prepare();
+ command->second->run();
}
};
-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); });
+static auto rCmdHash = registerCommand<CmdHash>("hash");
/* Legacy nix-hash command. */
static int compatNixHash(int argc, char * * argv)
@@ -149,7 +190,7 @@ static int compatNixHash(int argc, char * * argv)
});
if (op == opHash) {
- CmdHash cmd(flat ? FileIngestionMethod::Flat : FileIngestionMethod::Recursive);
+ CmdHashBase cmd(flat ? FileIngestionMethod::Flat : FileIngestionMethod::Recursive);
cmd.ht = ht;
cmd.base = base32 ? Base32 : Base16;
cmd.truncate = truncate;
diff --git a/src/nix/help.md b/src/nix/help.md
new file mode 100644
index 000000000..734f35028
--- /dev/null
+++ b/src/nix/help.md
@@ -0,0 +1,17 @@
+R""(
+
+# Examples
+
+* Show help about `nix` in general:
+
+ ```console
+ # nix help
+ ```
+
+* Show help about a particular subcommand:
+
+ ```console
+ # nix help flake info
+ ```
+
+)""
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
deleted file mode 100644
index 7473c9758..000000000
--- a/src/nix/installables.cc
+++ /dev/null
@@ -1,781 +0,0 @@
-#include "installables.hh"
-#include "command.hh"
-#include "attr-path.hh"
-#include "common-eval-args.hh"
-#include "derivations.hh"
-#include "eval-inline.hh"
-#include "eval.hh"
-#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->add(input.first);
-}
-
-MixFlakeOptions::MixFlakeOptions()
-{
- addFlag({
- .longName = "recreate-lock-file",
- .description = "recreate lock file from scratch",
- .handler = {&lockFlags.recreateLockFile, true}
- });
-
- addFlag({
- .longName = "no-update-lock-file",
- .description = "do not allow any updates to the lock file",
- .handler = {&lockFlags.updateLockFile, false}
- });
-
- 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}
- });
-
- addFlag({
- .longName = "commit-lock-file",
- .description = "commit changes to the lock file",
- .handler = {&lockFlags.commitLockFile, true}
- });
-
- 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);
- }}
- });
-
- 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(".")));
- }}
- });
-
- 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);
- }}
- });
-}
-
-SourceExprCommand::SourceExprCommand()
-{
- addFlag({
- .longName = "file",
- .shortName = 'f',
- .description = "evaluate *file* rather than the default",
- .labels = {"file"},
- .handler = {&file},
- .completer = completePath
- });
-
- addFlag({
- .longName ="expr",
- .description = "evaluate attributes from *expr*",
- .labels = {"expr"},
- .handler = {&expr}
- });
-
- addFlag({
- .longName ="derivation",
- .description = "operate on the store derivation rather than its outputs",
- .handler = {&operateOn, OperateOn::Derivation},
- });
-}
-
-Strings SourceExprCommand::getDefaultFlakeAttrPaths()
-{
- return {"defaultPackage." + settings.thisSystem.get()};
-}
-
-Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
-{
- return {
- // As a convenience, look for the attribute in
- // 'outputs.packages'.
- "packages." + settings.thisSystem.get() + ".",
- // As a temporary hack until Nixpkgs is properly converted
- // to provide a clean 'packages' set, look in 'legacyPackages'.
- "legacyPackages." + settings.thisSystem.get() + "."
- };
-}
-
-void SourceExprCommand::completeInstallable(std::string_view prefix)
-{
- if (file) return; // FIXME
-
- completeFlakeRefWithFragment(
- getEvalState(),
- lockFlags,
- getDefaultFlakeAttrPathPrefixes(),
- getDefaultFlakeAttrPaths(),
- prefix);
-}
-
-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->add(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->add(flakeRefS + "#");
- }
- }
- }
- } catch (Error & e) {
- warn(e.msg());
- }
-
- completeFlakeRef(evalState->store, prefix);
-}
-
-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->add(".");
-
- 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->add(from2);
- } else {
- if (hasPrefix(from, prefix))
- completions->add(from);
- }
- }
- }
-}
-
-Buildable Installable::toBuildable()
-{
- auto buildables = toBuildables();
- if (buildables.size() != 1)
- throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
- 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, StorePath && storePath)
- : store(store), storePath(std::move(storePath)) { }
-
- std::string what() override { return store->printStorePath(storePath); }
-
- Buildables toBuildables() override
- {
- 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
- {
- return storePath;
- }
-};
-
-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;
-
- InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
- : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
- { }
-
- std::string what() override { return attrPath; }
-
- std::pair<Value *, Pos> toValue(EvalState & state) override
- {
- auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
- state.forceValue(*vRes);
- return {vRes, pos};
- }
-
- virtual std::vector<InstallableValue::DerivationInfo> toDerivations() override;
-};
-
-std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations()
-{
- auto v = toValue(*state).first;
-
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
-
- DrvInfos drvInfos;
- getDerivations(*state, *v, "", autoArgs, drvInfos, false);
-
- std::vector<DerivationInfo> res;
- for (auto & drvInfo : drvInfos) {
- res.push_back({
- state->store->parseStorePath(drvInfo.queryDrvPath()),
- state->store->parseStorePath(drvInfo.queryOutPath()),
- drvInfo.queryOutputName()
- });
- }
-
- return res;
-}
-
-std::vector<std::string> InstallableFlake::getActualAttrPaths()
-{
- std::vector<std::string> res;
-
- for (auto & prefix : prefixes)
- res.push_back(prefix + *attrPaths.begin());
-
- for (auto & s : attrPaths)
- res.push_back(s);
-
- return res;
-}
-
-Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::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;
- });
-}
-
-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;
-}
-
-std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
-{
- auto lockedFlake = getLockedFlake();
-
- auto cache = openEvalCache(*state, lockedFlake);
- auto root = cache->getRoot();
-
- for (auto & attrPath : getActualAttrPaths()) {
- auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
- if (!attr) continue;
-
- 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)};
- }
-
- throw Error("flake '%s' does not provide attribute %s",
- flakeRef, showAttrPaths(getActualAttrPaths()));
-}
-
-std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
-{
- std::vector<DerivationInfo> res;
- res.push_back(std::get<2>(toDerivation()));
- return res;
-}
-
-std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
-{
- auto lockedFlake = getLockedFlake();
-
- auto vOutputs = getFlakeOutputs(state, *lockedFlake);
-
- auto emptyArgs = state.allocBindings(0);
-
- for (auto & attrPath : getActualAttrPaths()) {
- try {
- auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
- state.forceValue(*v);
- return {v, pos};
- } catch (AttrPathNotFound & e) {
- }
- }
-
- throw Error("flake '%s' does not provide attribute %s",
- flakeRef, 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)));
-
- auto root = evalCache->getRoot();
-
- std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res;
-
- for (auto & attrPath : getActualAttrPaths()) {
- auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
- if (attr) res.push_back({attr, attrPath});
- }
-
- return res;
-}
-
-std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
-{
- if (!_lockedFlake)
- _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags));
- return _lockedFlake;
-}
-
-FlakeRef InstallableFlake::nixpkgsFlakeRef() const
-{
- auto lockedFlake = getLockedFlake();
-
- 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);
- }
- }
-
- return Installable::nixpkgsFlakeRef();
-}
-
-std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
- ref<Store> store, std::vector<std::string> ss)
-{
- std::vector<std::shared_ptr<Installable>> result;
-
- if (file || expr) {
- if (file && expr)
- throw UsageError("'--file' and '--expr' are exclusive");
-
- // FIXME: backward compatibility hack
- if (file) evalSettings.pureEval = false;
-
- 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);
- }
-
- 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();
- }
- }
-
- 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> SourceExprCommand::parseInstallable(
- ref<Store> store, const std::string & installable)
-{
- auto installables = parseInstallables(store, {installable});
- assert(installables.size() == 1);
- return installables.front();
-}
-
-Buildables build(ref<Store> store, Realise mode,
- std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode)
-{
- if (mode == Realise::Nothing)
- settings.readOnlyMode = true;
-
- Buildables buildables;
-
- std::vector<StorePathWithOutputs> pathsToBuild;
-
- for (auto & i : installables) {
- for (auto & b : i->toBuildables()) {
- 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 == Realise::Nothing)
- printMissing(store, pathsToBuild, lvlError);
- else if (mode == Realise::Outputs)
- store->buildPaths(pathsToBuild, bMode);
-
- return buildables;
-}
-
-StorePathSet toStorePaths(ref<Store> store,
- Realise mode, OperateOn operateOn,
- std::vector<std::shared_ptr<Installable>> installables)
-{
- StorePathSet outPaths;
-
- 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,
- Realise mode, OperateOn operateOn,
- std::shared_ptr<Installable> installable)
-{
- auto paths = toStorePaths(store, mode, operateOn, {installable});
-
- if (paths.size() != 1)
- throw Error("argument '%s' should evaluate to one store path", installable->what());
-
- return *paths.begin();
-}
-
-StorePathSet toDerivations(ref<Store> store,
- std::vector<std::shared_ptr<Installable>> installables, bool useDeriver)
-{
- StorePathSet drvPaths;
-
- for (auto & i : installables)
- 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());
- },
- [&](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()
-{
- 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(getStore(), _installable);
-}
-
-}
diff --git a/src/nix/installables.hh b/src/nix/installables.hh
deleted file mode 100644
index c7c2f8981..000000000
--- a/src/nix/installables.hh
+++ /dev/null
@@ -1,132 +0,0 @@
-#pragma once
-
-#include "util.hh"
-#include "path.hh"
-#include "eval.hh"
-#include "flake/flake.hh"
-
-#include <optional>
-
-namespace nix {
-
-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() = 0;
-
- Buildable toBuildable();
-
- App toApp(EvalState & state);
-
- virtual std::pair<Value *, Pos> toValue(EvalState & state)
- {
- throw Error("argument '%s' cannot be evaluated", what());
- }
-
- /* Return a value only if this installable is a store path or a
- symlink to it. */
- virtual std::optional<StorePath> getStorePath()
- {
- 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/key-convert-secret-to-public.md b/src/nix/key-convert-secret-to-public.md
new file mode 100644
index 000000000..3adc18502
--- /dev/null
+++ b/src/nix/key-convert-secret-to-public.md
@@ -0,0 +1,19 @@
+R""(
+
+# Examples
+
+* Convert a secret key to a public key:
+
+ ```console
+ # echo cache.example.org-0:E7lAO+MsPwTFfPXsdPtW8GKui/5ho4KQHVcAGnX+Tti1V4dUxoVoqLyWJ4YESuZJwQ67GVIksDt47og+tPVUZw== \
+ | nix key convert-secret-to-public
+ cache.example.org-0:tVeHVMaFaKi8lieGBErmScEOuxlSJLA7eO6IPrT1VGc=
+ ```
+
+# Description
+
+This command reads a Ed25519 secret key from standard input, and
+writes the corresponding public key to standard output. For more
+details, see [nix key generate-secret](./nix3-key-generate-secret.md).
+
+)""
diff --git a/src/nix/key-generate-secret.md b/src/nix/key-generate-secret.md
new file mode 100644
index 000000000..4938f637c
--- /dev/null
+++ b/src/nix/key-generate-secret.md
@@ -0,0 +1,48 @@
+R""(
+
+# Examples
+
+* Generate a new secret key:
+
+ ```console
+ # nix key generate-secret --key-name cache.example.org-1 > ./secret-key
+ ```
+
+ We can then use this key to sign the closure of the Hello package:
+
+ ```console
+ # nix build nixpkgs#hello
+ # nix store sign --key-file ./secret-key --recursive ./result
+ ```
+
+ Finally, we can verify the store paths using the corresponding
+ public key:
+
+ ```
+ # nix store verify --trusted-public-keys $(nix key convert-secret-to-public < ./secret-key) ./result
+ ```
+
+# Description
+
+This command generates a new Ed25519 secret key for signing store
+paths and prints it on standard output. Use `nix key
+convert-secret-to-public` to get the corresponding public key for
+verifying signed store paths.
+
+The mandatory argument `--key-name` specifies a key name (such as
+`cache.example.org-1). It is used to look up keys on the client when
+it verifies signatures. It can be anything, but it’s suggested to use
+the host name of your cache (e.g. `cache.example.org`) with a suffix
+denoting the number of the key (to be incremented every time you need
+to revoke a key).
+
+# Format
+
+Both secret and public keys are represented as the key name followed
+by a base-64 encoding of the Ed25519 key data, e.g.
+
+```
+cache.example.org-0:E7lAO+MsPwTFfPXsdPtW8GKui/5ho4KQHVcAGnX+Tti1V4dUxoVoqLyWJ4YESuZJwQ67GVIksDt47og+tPVUZw==
+```
+
+)""
diff --git a/src/nix/legacy.cc b/src/nix/legacy.cc
deleted file mode 100644
index 6df09ee37..000000000
--- a/src/nix/legacy.cc
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "legacy.hh"
-
-namespace nix {
-
-RegisterLegacyCommand::Commands * RegisterLegacyCommand::commands = 0;
-
-}
diff --git a/src/nix/legacy.hh b/src/nix/legacy.hh
deleted file mode 100644
index f503b0da3..000000000
--- a/src/nix/legacy.hh
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include <functional>
-#include <map>
-#include <string>
-
-namespace nix {
-
-typedef std::function<void(int, char * *)> MainFunction;
-
-struct RegisterLegacyCommand
-{
- typedef std::map<std::string, MainFunction> Commands;
- static Commands * commands;
-
- RegisterLegacyCommand(const std::string & name, MainFunction fun)
- {
- if (!commands) commands = new Commands;
- (*commands)[name] = fun;
- }
-};
-
-}
diff --git a/src/nix/local.mk b/src/nix/local.mk
index f37b73384..83b6dd08b 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -12,12 +12,11 @@ nix_SOURCES := \
$(wildcard src/nix-daemon/*.cc) \
$(wildcard src/nix-env/*.cc) \
$(wildcard src/nix-instantiate/*.cc) \
- $(wildcard src/nix-prefetch-url/*.cc) \
$(wildcard src/nix-store/*.cc) \
-nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain
+nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain -I src/libcmd
-nix_LIBS = libexpr libmain libfetchers libstore libutil
+nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd
nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -llowdown
diff --git a/src/nix/log.cc b/src/nix/log.cc
index 33a3053f5..67d3742d6 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -13,22 +13,11 @@ struct CmdLog : InstallableCommand
return "show the build log of the specified packages or paths, if available";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To get the build log of GNU Hello:",
- "nix log nixpkgs#hello"
- },
- Example{
- "To get the build log of a specific path:",
- "nix log /nix/store/lmngj4wcm9rkv3w4dfhzhcyij3195hiq-thunderbird-52.2.1"
- },
- Example{
- "To get a build log from a specific binary cache:",
- "nix log --store https://cache.nixos.org nixpkgs#hello"
- },
- };
+ return
+ #include "log.md"
+ ;
}
Category category() override { return catSecondary; }
diff --git a/src/nix/log.md b/src/nix/log.md
new file mode 100644
index 000000000..1c76226a3
--- /dev/null
+++ b/src/nix/log.md
@@ -0,0 +1,40 @@
+R""(
+
+# Examples
+
+* Get the build log of GNU Hello:
+
+ ```console
+ # nix log nixpkgs#hello
+ ```
+
+* Get the build log of a specific store path:
+
+ ```console
+ # nix log /nix/store/lmngj4wcm9rkv3w4dfhzhcyij3195hiq-thunderbird-52.2.1
+ ```
+
+* Get a build log from a specific binary cache:
+
+ ```console
+ # nix log --store https://cache.nixos.org nixpkgs#hello
+ ```
+
+# Description
+
+This command prints the log of a previous build of the derivation
+*installable* on standard output.
+
+Nix looks for build logs in two places:
+
+* In the directory `/nix/var/log/nix/drvs`, which contains logs for
+ locally built derivations.
+
+* In the binary caches listed in the `substituters` setting. Logs
+ should be named `<cache>/log/<base-name-of-store-path>`, where
+ `store-path` is a derivation,
+ e.g. `https://cache.nixos.org/log/dvmig8jgrdapvbyxb1rprckdmdqx08kv-hello-2.10.drv`.
+ For non-derivation store paths, Nix will first try to determine the
+ deriver by fetching the `.narinfo` file for this store path.
+
+)""
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index baca54431..c1dc9a95b 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -17,9 +17,26 @@ struct MixLs : virtual Args, MixJSON
MixLs()
{
- mkFlag('R', "recursive", "list subdirectories recursively", &recursive);
- mkFlag('l', "long", "show more file information", &verbose);
- mkFlag('d', "directory", "show directories rather than their contents", &showDirectory);
+ addFlag({
+ .longName = "recursive",
+ .shortName = 'R',
+ .description = "List subdirectories recursively.",
+ .handler = {&recursive, true},
+ });
+
+ addFlag({
+ .longName = "long",
+ .shortName = 'l',
+ .description = "Show detailed file information.",
+ .handler = {&verbose, true},
+ });
+
+ addFlag({
+ .longName = "directory",
+ .shortName = 'd',
+ .description = "Show directories rather than their contents.",
+ .handler = {&showDirectory, true},
+ });
}
void listText(ref<FSAccessor> accessor)
@@ -37,11 +54,11 @@ struct MixLs : virtual Args, MixJSON
auto line = fmt("%s %20d %s", tp, st.fileSize, relPath);
if (st.type == FSAccessor::Type::tSymlink)
line += " -> " + accessor->readLink(curPath);
- logger->stdout(line);
+ logger->cout(line);
if (recursive && st.type == FSAccessor::Type::tDirectory)
doPath(st, curPath, relPath, false);
} else {
- logger->stdout(relPath);
+ logger->cout(relPath);
if (recursive) {
auto st = accessor->stat(curPath);
if (st.type == FSAccessor::Type::tDirectory)
@@ -75,6 +92,8 @@ struct MixLs : virtual Args, MixJSON
if (json) {
JSONPlaceholder jsonRoot(std::cout);
+ if (showDirectory)
+ throw UsageError("'--directory' is useless with '--json'");
listNar(jsonRoot, accessor, path, recursive);
} else
listText(accessor);
@@ -92,22 +111,17 @@ struct CmdLsStore : StoreCommand, MixLs
});
}
- Examples examples() override
- {
- return {
- Example{
- "To list the contents of a store path in a binary cache:",
- "nix ls-store --store https://cache.nixos.org/ -lR /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10"
- },
- };
- }
-
std::string description() override
{
return "show information about a path in the Nix store";
}
- Category category() override { return catUtility; }
+ std::string doc() override
+ {
+ return
+ #include "store-ls.md"
+ ;
+ }
void run(ref<Store> store) override
{
@@ -129,14 +143,11 @@ struct CmdLsNar : Command, MixLs
expectArg("path", &path);
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To list a specific file in a NAR:",
- "nix ls-nar -l hello.nar /bin/hello"
- },
- };
+ return
+ #include "nar-ls.md"
+ ;
}
std::string description() override
@@ -144,13 +155,11 @@ struct CmdLsNar : Command, MixLs
return "show information about a path inside a NAR file";
}
- Category category() override { return catUtility; }
-
void run() override
{
list(makeNarAccessor(make_ref<std::string>(readFile(narPath))));
}
};
-static auto rCmdLsStore = registerCommand<CmdLsStore>("ls-store");
-static auto rCmdLsNar = registerCommand<CmdLsNar>("ls-nar");
+static auto rCmdLsStore = registerCommand2<CmdLsStore>({"store", "ls"});
+static auto rCmdLsNar = registerCommand2<CmdLsNar>({"nar", "ls"});
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 5056ceb78..06e221682 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -17,6 +17,10 @@
#include <netdb.h>
#include <netinet/in.h>
+#if __linux__
+#include <sys/resource.h>
+#endif
+
#include <nlohmann/json.hpp>
extern std::string chrootHelperName;
@@ -52,14 +56,18 @@ static bool haveInternet()
}
std::string programPath;
+char * * savedArgv;
+
+struct HelpRequested { };
struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{
bool printBuildLogs = false;
bool useNet = true;
bool refresh = false;
+ bool showVersion = false;
- NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
+ NixArgs() : MultiCommand(RegisterCommand::getCommandsFor({})), MixCommonArgs("nix")
{
categories.clear();
categories[Command::catDefault] = "Main commands";
@@ -69,88 +77,137 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
- .description = "show usage information",
- .handler = {[&]() { if (!completions) showHelpAndExit(); }},
- });
-
- addFlag({
- .longName = "help-config",
- .description = "show configuration options",
- .handler = {[&]() {
- std::cout << "The following configuration options are available:\n\n";
- Table2 tbl;
- std::map<std::string, Config::SettingInfo> settings;
- globalConfig.getSettings(settings);
- for (const auto & s : settings)
- tbl.emplace_back(s.first, s.second.description);
- printTable(std::cout, tbl);
- throw Exit();
- }},
+ .description = "Show usage information.",
+ .handler = {[&]() { throw HelpRequested(); }},
});
addFlag({
.longName = "print-build-logs",
.shortName = 'L',
- .description = "print full build logs on stderr",
+ .description = "Print full build logs on standard error.",
+ .category = loggingCategory,
.handler = {[&]() {setLogFormat(LogFormat::barWithLogs); }},
});
addFlag({
.longName = "version",
- .description = "show version information",
- .handler = {[&]() { if (!completions) printVersion(programName); }},
+ .description = "Show version information.",
+ .handler = {[&]() { showVersion = true; }},
});
addFlag({
- .longName = "no-net",
- .description = "disable substituters and consider all previously downloaded files up-to-date",
+ .longName = "offline",
+ .aliases = {"no-net"}, // FIXME: remove
+ .description = "Disable substituters and consider all previously downloaded files up-to-date.",
.handler = {[&]() { useNet = false; }},
});
addFlag({
.longName = "refresh",
- .description = "consider all previously downloaded files out-of-date",
+ .description = "Consider all previously downloaded files out-of-date.",
.handler = {[&]() { refresh = true; }},
});
-
- deprecatedAliases.insert({"dev-shell", "develop"});
}
- void printFlags(std::ostream & out) override
+ std::map<std::string, std::vector<std::string>> aliases = {
+ {"add-to-store", {"store", "add-path"}},
+ {"cat-nar", {"nar", "cat"}},
+ {"cat-store", {"store", "cat"}},
+ {"copy-sigs", {"store", "copy-sigs"}},
+ {"dev-shell", {"develop"}},
+ {"diff-closures", {"store", "diff-closures"}},
+ {"dump-path", {"store", "dump-path"}},
+ {"hash-file", {"hash", "file"}},
+ {"hash-path", {"hash", "path"}},
+ {"ls-nar", {"nar", "ls"}},
+ {"ls-store", {"store", "ls"}},
+ {"make-content-addressable", {"store", "make-content-addressable"}},
+ {"optimise-store", {"store", "optimise"}},
+ {"ping-store", {"store", "ping"}},
+ {"sign-paths", {"store", "sign"}},
+ {"to-base16", {"hash", "to-base16"}},
+ {"to-base32", {"hash", "to-base32"}},
+ {"to-base64", {"hash", "to-base64"}},
+ {"verify", {"store", "verify"}},
+ };
+
+ bool aliasUsed = false;
+
+ Strings::iterator rewriteArgs(Strings & args, Strings::iterator pos) override
{
- Args::printFlags(out);
- std::cout <<
- "\n"
- "In addition, most configuration settings can be overriden using '--" ANSI_ITALIC "name value" ANSI_NORMAL "'.\n"
- "Boolean settings can be overriden using '--" ANSI_ITALIC "name" ANSI_NORMAL "' or '--no-" ANSI_ITALIC "name" ANSI_NORMAL "'. See 'nix\n"
- "--help-config' for a list of configuration settings.\n";
+ if (aliasUsed || command || pos == args.end()) return pos;
+ auto arg = *pos;
+ auto i = aliases.find(arg);
+ if (i == aliases.end()) return pos;
+ warn("'%s' is a deprecated alias for '%s'",
+ arg, concatStringsSep(" ", i->second));
+ pos = args.erase(pos);
+ for (auto j = i->second.rbegin(); j != i->second.rend(); ++j)
+ pos = args.insert(pos, *j);
+ aliasUsed = true;
+ return pos;
}
- void printHelp(const string & programName, std::ostream & out) override
+ std::string description() override
{
- MultiCommand::printHelp(programName, out);
+ return "a tool for reproducible and declarative configuration management";
+ }
-#if 0
- out << "\nFor full documentation, run 'man " << programName << "' or 'man " << programName << "-" ANSI_ITALIC "COMMAND" ANSI_NORMAL "'.\n";
-#endif
+ std::string doc() override
+ {
+ return
+ #include "nix.md"
+ ;
+ }
- std::cout << "\nNote: this program is " ANSI_RED "EXPERIMENTAL" ANSI_NORMAL " and subject to change.\n";
+ // Plugins may add new subcommands.
+ void pluginsInited() override
+ {
+ commands = RegisterCommand::getCommandsFor({});
}
+};
- void showHelpAndExit()
+static void showHelp(std::vector<std::string> subcommand)
+{
+ showManPage(subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand)));
+}
+
+struct CmdHelp : Command
+{
+ std::vector<std::string> subcommand;
+
+ CmdHelp()
{
- printHelp(programName, std::cout);
- throw Exit();
+ expectArgs({
+ .label = "subcommand",
+ .handler = {&subcommand},
+ });
}
std::string description() override
{
- return "a tool for reproducible and declarative configuration management";
+ return "show help about `nix` or a particular subcommand";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "help.md"
+ ;
+ }
+
+ void run() override
+ {
+ showHelp(subcommand);
}
};
+static auto rCmdHelp = registerCommand<CmdHelp>("help");
+
void mainWrapped(int argc, char * * argv)
{
+ savedArgv = argv;
+
/* The chroot helper needs to be run before any threads have been
started. */
if (argc > 0 && argv[0] == chrootHelperName) {
@@ -169,7 +226,7 @@ void mainWrapped(int argc, char * * argv)
if (legacy) return legacy(argc, argv);
}
- verbosity = lvlWarn;
+ verbosity = lvlNotice;
settings.verboseBuild = false;
evalSettings.pureEval = true;
@@ -191,7 +248,7 @@ void mainWrapped(int argc, char * * argv)
auto builtins = state.baseEnv.values[0]->attrs;
for (auto & builtin : *builtins) {
auto b = nlohmann::json::object();
- if (builtin.value->type != tPrimOp) continue;
+ if (!builtin.value->isPrimOp()) continue;
auto primOp = builtin.value->primOp;
if (!primOp->doc) continue;
b["arity"] = primOp->arity;
@@ -214,15 +271,31 @@ void mainWrapped(int argc, char * * argv)
try {
args.parseCmdline(argvToStrings(argc, argv));
+ } catch (HelpRequested &) {
+ std::vector<std::string> subcommand;
+ MultiCommand * command = &args;
+ while (command) {
+ if (command && command->command) {
+ subcommand.push_back(command->command->first);
+ command = dynamic_cast<MultiCommand *>(&*command->command->second);
+ } else
+ break;
+ }
+ showHelp(subcommand);
+ return;
} catch (UsageError &) {
if (!completions) throw;
}
if (completions) return;
- initPlugins();
+ if (args.showVersion) {
+ printVersion(programName);
+ return;
+ }
- if (!args.command) args.showHelpAndExit();
+ if (!args.command)
+ throw UsageError("no subcommand specified");
if (args.command->first != "repl"
&& args.command->first != "doctor"
@@ -246,8 +319,11 @@ void mainWrapped(int argc, char * * argv)
fileTransferSettings.connectTimeout = 1;
}
- if (args.refresh)
+ if (args.refresh) {
settings.tarballTtl = 0;
+ settings.ttlNegativeNarInfoCache = 0;
+ settings.ttlPositiveNarInfoCache = 0;
+ }
args.command->second->prepare();
args.command->second->run();
@@ -257,6 +333,17 @@ void mainWrapped(int argc, char * * argv)
int main(int argc, char * * argv)
{
+ // Increase the default stack size for the evaluator and for
+ // libstdc++'s std::regex.
+ #if __linux__
+ rlim_t stackSize = 64 * 1024 * 1024;
+ struct rlimit limit;
+ if (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur < stackSize) {
+ limit.rlim_cur = stackSize;
+ setrlimit(RLIMIT_STACK, &limit);
+ }
+ #endif
+
return nix::handleExceptions(argv[0], [&]() {
nix::mainWrapped(argc, argv);
});
diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc
index 53ad39e02..29f855f95 100644
--- a/src/nix/make-content-addressable.cc
+++ b/src/nix/make-content-addressable.cc
@@ -15,25 +15,16 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
std::string description() override
{
- return "rewrite a path or closure to content-addressable form";
+ return "rewrite a path or closure to content-addressed form";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To create a content-addressable representation of GNU Hello (but not its dependencies):",
- "nix make-content-addressable nixpkgs#hello"
- },
- Example{
- "To compute a content-addressable representation of the current NixOS system closure:",
- "nix make-content-addressable -r /run/current-system"
- },
- };
+ return
+ #include "make-content-addressable.md"
+ ;
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store, StorePaths storePaths) override
{
auto paths = store->topoSortPaths(StorePathSet(storePaths.begin(), storePaths.end()));
@@ -69,7 +60,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
*sink.s = rewriteStrings(*sink.s, rewrites);
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
- hashModuloSink((unsigned char *) sink.s->data(), sink.s->size());
+ hashModuloSink(*sink.s);
auto narHash = hashModuloSink.finish().first;
@@ -90,11 +81,11 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
info.narSize = sink.s->size();
if (!json)
- printInfo("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
+ notice("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
auto source = sinkToSource([&](Sink & nextSink) {
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink);
- rsink2((unsigned char *) sink.s->data(), sink.s->size());
+ rsink2(*sink.s);
rsink2.flush();
});
@@ -108,4 +99,4 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
}
};
-static auto rCmdMakeContentAddressable = registerCommand<CmdMakeContentAddressable>("make-content-addressable");
+static auto rCmdMakeContentAddressable = registerCommand2<CmdMakeContentAddressable>({"store", "make-content-addressable"});
diff --git a/src/nix/make-content-addressable.md b/src/nix/make-content-addressable.md
new file mode 100644
index 000000000..3dd847edc
--- /dev/null
+++ b/src/nix/make-content-addressable.md
@@ -0,0 +1,59 @@
+R""(
+
+# Examples
+
+* Create a content-addressed representation of the closure of GNU Hello:
+
+ ```console
+ # nix store make-content-addressable -r nixpkgs#hello
+ …
+ rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10'
+ ```
+
+ Since the resulting paths are content-addressed, they are always
+ trusted and don't need signatures to copied to another store:
+
+ ```console
+ # nix copy --to /tmp/nix --trusted-public-keys '' /nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10
+ ```
+
+ By contrast, the original closure is input-addressed, so it does
+ need signatures to be trusted:
+
+ ```console
+ # nix copy --to /tmp/nix --trusted-public-keys '' nixpkgs#hello
+ cannot add path '/nix/store/zy9wbxwcygrwnh8n2w9qbbcr6zk87m26-libunistring-0.9.10' because it lacks a valid signature
+ ```
+
+* Create a content-addressed representation of the current NixOS
+ system closure:
+
+ ```console
+ # nix store make-content-addressable -r /run/current-system
+ ```
+
+# Description
+
+This command converts the closure of the store paths specified by
+*installables* to content-addressed form. Nix store paths are usually
+*input-addressed*, meaning that the hash part of the store path is
+computed from the contents of the derivation (i.e., the build-time
+dependency graph). Input-addressed paths need to be signed by a
+trusted key if you want to import them into a store, because we need
+to trust that the contents of the path were actually built by the
+derivation.
+
+By contrast, in a *content-addressed* path, the hash part is computed
+from the contents of the path. This allows the contents of the path to
+be verified without any additional information such as
+signatures. This means that a command like
+
+```console
+# nix store build /nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10 \
+ --substituters https://my-cache.example.org
+```
+
+will succeed even if the binary cache `https://my-cache.example.org`
+doesn't present any signatures.
+
+)""
diff --git a/src/nix/markdown.cc b/src/nix/markdown.cc
deleted file mode 100644
index 40788a42f..000000000
--- a/src/nix/markdown.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-#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
deleted file mode 100644
index 78320fcf5..000000000
--- a/src/nix/markdown.hh
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "types.hh"
-
-namespace nix {
-
-std::string renderMarkdownToTerminal(std::string_view markdown);
-
-}
diff --git a/src/nix/nar-cat.md b/src/nix/nar-cat.md
new file mode 100644
index 000000000..55c481a28
--- /dev/null
+++ b/src/nix/nar-cat.md
@@ -0,0 +1,19 @@
+R""(
+
+# Examples
+
+* List a file in a NAR and pipe it through `gunzip`:
+
+ ```console
+ # nix nar cat ./hello.nar /share/man/man1/hello.1.gz | gunzip
+ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.46.4.
+ .TH HELLO "1" "November 2014" "hello 2.10" "User Commands"
+ …
+ ```
+
+# Description
+
+This command prints on standard output the contents of the regular
+file *path* inside the NAR file *nar*.
+
+)""
diff --git a/src/nix/nar-dump-path.md b/src/nix/nar-dump-path.md
new file mode 100644
index 000000000..26191ad25
--- /dev/null
+++ b/src/nix/nar-dump-path.md
@@ -0,0 +1,17 @@
+R""(
+
+# Examples
+
+* To serialise directory `foo` as a NAR:
+
+ ```console
+ # nix nar dump-path ./foo > foo.nar
+ ```
+
+# Description
+
+This command generates a NAR file containing the serialisation of
+*path*, which must contain only regular files, directories and
+symbolic links. The NAR is written to standard output.
+
+)""
diff --git a/src/nix/nar-ls.md b/src/nix/nar-ls.md
new file mode 100644
index 000000000..d373f9715
--- /dev/null
+++ b/src/nix/nar-ls.md
@@ -0,0 +1,24 @@
+R""(
+
+# Examples
+
+* To list a specific file in a NAR:
+
+ ```console
+ # nix nar ls -l ./hello.nar /bin/hello
+ -r-xr-xr-x 38184 hello
+ ```
+
+* To recursively list the contents of a directory inside a NAR, in JSON
+ format:
+
+ ```console
+ # nix nar ls --json -R ./hello.nar /bin
+ {"type":"directory","entries":{"hello":{"type":"regular","size":38184,"executable":true,"narOffset":400}}}
+ ```
+
+# Description
+
+This command shows information about a *path* inside NAR file *nar*.
+
+)""
diff --git a/src/nix/nar.cc b/src/nix/nar.cc
new file mode 100644
index 000000000..dbb043d9b
--- /dev/null
+++ b/src/nix/nar.cc
@@ -0,0 +1,33 @@
+#include "command.hh"
+
+using namespace nix;
+
+struct CmdNar : NixMultiCommand
+{
+ CmdNar() : MultiCommand(RegisterCommand::getCommandsFor({"nar"}))
+ { }
+
+ std::string description() override
+ {
+ return "create or inspect NAR files";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "nar.md"
+ ;
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix nar' requires a sub-command.");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdNar = registerCommand<CmdNar>("nar");
diff --git a/src/nix/nar.md b/src/nix/nar.md
new file mode 100644
index 000000000..a83b5c764
--- /dev/null
+++ b/src/nix/nar.md
@@ -0,0 +1,13 @@
+R""(
+
+# Description
+
+`nix nar` provides several subcommands for creating and inspecting
+*Nix Archives* (NARs).
+
+# File format
+
+For the definition of the NAR file format, see Figure 5.2 in
+https://edolstra.github.io/pubs/phd-thesis.pdf.
+
+)""
diff --git a/src/nix/nix.md b/src/nix/nix.md
new file mode 100644
index 000000000..d10de7c01
--- /dev/null
+++ b/src/nix/nix.md
@@ -0,0 +1,119 @@
+R""(
+
+# Examples
+
+* Create a new flake:
+
+ ```console
+ # nix flake new hello
+ # cd hello
+ ```
+
+* Build the flake in the current directory:
+
+ ```console
+ # nix build
+ # ./result/bin/hello
+ Hello, world!
+ ```
+
+* Run the flake in the current directory:
+
+ ```console
+ # nix run
+ Hello, world!
+ ```
+
+* Start a development shell for hacking on this flake:
+
+ ```console
+ # nix develop
+ # unpackPhase
+ # cd hello-*
+ # configurePhase
+ # buildPhase
+ # ./hello
+ Hello, world!
+ # installPhase
+ # ../outputs/out/bin/hello
+ Hello, world!
+ ```
+
+# Description
+
+Nix is a tool for building software, configurations and other
+artifacts in a reproducible and declarative way. For more information,
+see the [Nix homepage](https://nixos.org/) or the [Nix
+manual](https://nixos.org/manual/nix/stable/).
+
+# Installables
+
+Many `nix` subcommands operate on one or more *installables*. These are
+command line arguments that represent something that can be built in
+the Nix store. Here are the recognised types of installables:
+
+* **Flake output attributes**: `nixpkgs#hello`
+
+ These have the form *flakeref*[`#`*attrpath*], where *flakeref* is a
+ flake reference and *attrpath* is an optional attribute path. For
+ more information on flakes, see [the `nix flake` manual
+ page](./nix3-flake.md). Flake references are most commonly a flake
+ identifier in the flake registry (e.g. `nixpkgs`) or a path
+ (e.g. `/path/to/my-flake` or `.`).
+
+ If *attrpath* is omitted, Nix tries some default values; for most
+ subcommands, the default is `defaultPackage.`*system*
+ (e.g. `defaultPackage.x86_64-linux`), but some subcommands have
+ other defaults. If *attrpath* *is* specified, *attrpath* is
+ interpreted as relative to one or more prefixes; for most
+ subcommands, these are `packages.`*system*,
+ `legacyPackages.*system*` and the empty prefix. Thus, on
+ `x86_64-linux` `nix build nixpkgs#hello` will try to build the
+ attributes `packages.x86_64-linux.hello`,
+ `legacyPackages.x86_64-linux.hello` and `hello`.
+
+* **Store paths**: `/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10`
+
+ These are paths inside the Nix store, or symlinks that resolve to a
+ path in the Nix store.
+
+* **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv`
+
+ Store derivations are store paths with extension `.drv` and are a
+ low-level representation of a build-time dependency graph used
+ internally by Nix. By default, if you pass a store derivation to a
+ `nix` subcommand, it will operate on the *output paths* of the
+ derivation. For example, `nix path-info` prints information about
+ the output paths:
+
+ ```console
+ # nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv
+ [{"path":"/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10",…}]
+ ```
+
+ If you want to operate on the store derivation itself, pass the
+ `--derivation` flag.
+
+* **Nix attributes**: `--file /path/to/nixpkgs hello`
+
+ When the `-f` / `--file` *path* option is given, installables are
+ interpreted as attribute paths referencing a value returned by
+ evaluating the Nix file *path*.
+
+* **Nix expressions**: `--expr '(import <nixpkgs> {}).hello.overrideDerivation (prev: { name = "my-hello"; })'`.
+
+ When the `--expr` option is given, all installables are interpreted
+ as Nix expressions. You may need to specify `--impure` if the
+ expression references impure inputs (such as `<nixpkgs>`).
+
+For most commands, if no installable is specified, the default is `.`,
+i.e. Nix will operate on the default flake output attribute of the
+flake in the current directory.
+
+# Nix stores
+
+Most `nix` subcommands operate on a *Nix store*.
+
+TODO: list store types, options
+
+)""
diff --git a/src/nix/optimise-store.cc b/src/nix/optimise-store.cc
index 51a7a9756..985006e5a 100644
--- a/src/nix/optimise-store.cc
+++ b/src/nix/optimise-store.cc
@@ -13,22 +13,17 @@ struct CmdOptimiseStore : StoreCommand
return "replace identical files in the store by hard links";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To optimise the Nix store:",
- "nix optimise-store"
- },
- };
+ return
+ #include "optimise-store.md"
+ ;
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store) override
{
store->optimiseStore();
}
};
-static auto rCmdOptimiseStore = registerCommand<CmdOptimiseStore>("optimise-store");
+static auto rCmdOptimiseStore = registerCommand2<CmdOptimiseStore>({"store", "optimise"});
diff --git a/src/nix/optimise-store.md b/src/nix/optimise-store.md
new file mode 100644
index 000000000..f6fb66f97
--- /dev/null
+++ b/src/nix/optimise-store.md
@@ -0,0 +1,23 @@
+R""(
+
+# Examples
+
+* Optimise the Nix store:
+
+ ```console
+ nix store optimise
+ ```
+
+# Description
+
+This command deduplicates the Nix store: it scans the store for
+regular files with identical contents, and replaces them with hard
+links to a single instance.
+
+Note that you can also set `auto-optimise-store` to `true` in
+`nix.conf` to perform this optimisation incrementally whenever a new
+path is added to the Nix store. To make this efficient, Nix maintains
+a content-addressed index of all the files in the Nix store in the
+directory `/nix/store/.links/`.
+
+)""
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index 63cf885f9..518cd5568 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -18,10 +18,32 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
CmdPathInfo()
{
- mkFlag('s', "size", "print size of the NAR dump of each path", &showSize);
- mkFlag('S', "closure-size", "print sum size of the NAR dumps of the closure of each path", &showClosureSize);
- mkFlag('h', "human-readable", "with -s and -S, print sizes like 1K 234M 5.67G etc.", &humanReadable);
- mkFlag(0, "sigs", "show signatures", &showSigs);
+ addFlag({
+ .longName = "size",
+ .shortName = 's',
+ .description = "Print the size of the NAR serialisation of each path.",
+ .handler = {&showSize, true},
+ });
+
+ addFlag({
+ .longName = "closure-size",
+ .shortName = 'S',
+ .description = "Print the sum of the sizes of the NAR serialisations of the closure of each path.",
+ .handler = {&showClosureSize, true},
+ });
+
+ addFlag({
+ .longName = "human-readable",
+ .shortName = 'h',
+ .description = "With `-s` and `-S`, print sizes in a human-friendly format such as `5.67G`.",
+ .handler = {&humanReadable, true},
+ });
+
+ addFlag({
+ .longName = "sigs",
+ .description = "Show signatures.",
+ .handler = {&showSigs, true},
+ });
}
std::string description() override
@@ -29,38 +51,15 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
return "query information about store paths";
}
- Category category() override { return catSecondary; }
-
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To show the closure sizes of every path in the current NixOS system closure, sorted by size:",
- "nix path-info -rS /run/current-system | sort -nk2"
- },
- Example{
- "To show a package's closure size and all its dependencies with human readable sizes:",
- "nix path-info -rsSh nixpkgs#rust"
- },
- Example{
- "To check the existence of a path in a binary cache:",
- "nix path-info -r /nix/store/7qvk5c91...-geeqie-1.1 --store https://cache.nixos.org/"
- },
- Example{
- "To print the 10 most recently added paths (using --json and the jq(1) command):",
- "nix path-info --json --all | jq -r 'sort_by(.registrationTime)[-11:-1][].path'"
- },
- Example{
- "To show the size of the entire Nix store:",
- "nix path-info --json --all | jq 'map(.narSize) | add'"
- },
- Example{
- "To show every path whose closure is bigger than 1 GB, sorted by closure size:",
- "nix path-info --json --all -S | jq 'map(select(.closureSize > 1e9)) | sort_by(.closureSize) | map([.path, .closureSize])'"
- },
- };
+ return
+ #include "path-info.md"
+ ;
}
+ Category category() override { return catSecondary; }
+
void printSize(uint64_t value)
{
if (!humanReadable) {
diff --git a/src/nix/path-info.md b/src/nix/path-info.md
new file mode 100644
index 000000000..76a83e39d
--- /dev/null
+++ b/src/nix/path-info.md
@@ -0,0 +1,94 @@
+R""(
+
+# Examples
+
+* Print the store path produced by `nixpkgs#hello`:
+
+ ```console
+ # nix path-info nixpkgs#hello
+ /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
+ ```
+
+* Show the closure sizes of every path in the current NixOS system
+ closure, sorted by size:
+
+ ```console
+ # nix path-info -rS /run/current-system | sort -nk2
+ /nix/store/hl5xwp9kdrd1zkm0idm3kkby9q66z404-empty 96
+ /nix/store/27324qvqhnxj3rncazmxc4mwy79kz8ha-nameservers 112
+ …
+ /nix/store/539jkw9a8dyry7clcv60gk6na816j7y8-etc 5783255504
+ /nix/store/zqamz3cz4dbzfihki2mk7a63mbkxz9xq-nixos-system-machine-20.09.20201112.3090c65 5887562256
+ ```
+
+* Show a package's closure size and all its dependencies with human
+ readable sizes:
+
+ ```console
+ # nix path-info -rsSh nixpkgs#rustc
+ /nix/store/01rrgsg5zk3cds0xgdsq40zpk6g51dz9-ncurses-6.2-dev 386.7K 69.1M
+ /nix/store/0q783wnvixpqz6dxjp16nw296avgczam-libpfm-4.11.0 5.9M 37.4M
+ …
+ ```
+
+* Check the existence of a path in a binary cache:
+
+ ```console
+ # nix path-info -r /nix/store/blzxgyvrk32ki6xga10phr4sby2xf25q-geeqie-1.5.1 --store https://cache.nixos.org/
+ path '/nix/store/blzxgyvrk32ki6xga10phr4sby2xf25q-geeqie-1.5.1' is not valid
+
+ ```
+
+* Print the 10 most recently added paths (using --json and the jq(1)
+ command):
+
+ ```console
+ # nix path-info --json --all | jq -r 'sort_by(.registrationTime)[-11:-1][].path'
+ ```
+
+* Show the size of the entire Nix store:
+
+ ```console
+ # nix path-info --json --all | jq 'map(.narSize) | add'
+ 49812020936
+ ```
+
+* Show every path whose closure is bigger than 1 GB, sorted by closure
+ size:
+
+ ```console
+ # nix path-info --json --all -S \
+ | jq 'map(select(.closureSize > 1e9)) | sort_by(.closureSize) | map([.path, .closureSize])'
+ [
+ …,
+ [
+ "/nix/store/zqamz3cz4dbzfihki2mk7a63mbkxz9xq-nixos-system-machine-20.09.20201112.3090c65",
+ 5887562256
+ ]
+ ]
+ ```
+
+* Print the path of the store derivation produced by `nixpkgs#hello`:
+
+ ```console
+ # nix path-info --derivation nixpkgs#hello
+ /nix/store/s6rn4jz1sin56rf4qj5b5v8jxjm32hlk-hello-2.10.drv
+ ```
+
+# Description
+
+This command shows information about the store paths produced by
+*installables*, or about all paths in the store if you pass `--all`.
+
+By default, this command only prints the store paths. You can get
+additional information by passing flags such as `--closure-size`,
+--size`, `--sigs` or `--json`.
+
+> **Warning**
+>
+> Note that `nix path-info` does not build or substitute the
+> *installables* you specify. Thus, if the corresponding store paths
+> don't already exist, this command will fail. You can use `nix build`
+> to ensure that they exist.
+
+)""
diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc
index 8db78d591..62b645b06 100644
--- a/src/nix/ping-store.cc
+++ b/src/nix/ping-store.cc
@@ -8,25 +8,20 @@ struct CmdPingStore : StoreCommand
{
std::string description() override
{
- return "test whether a store can be opened";
+ return "test whether a store can be accessed";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To test whether connecting to a remote Nix store via SSH works:",
- "nix ping-store --store ssh://mac1"
- },
- };
+ return
+ #include "ping-store.md"
+ ;
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store) override
{
store->connect();
}
};
-static auto rCmdPingStore = registerCommand<CmdPingStore>("ping-store");
+static auto rCmdPingStore = registerCommand2<CmdPingStore>({"store", "ping"});
diff --git a/src/nix/ping-store.md b/src/nix/ping-store.md
new file mode 100644
index 000000000..8c846791b
--- /dev/null
+++ b/src/nix/ping-store.md
@@ -0,0 +1,33 @@
+R""(
+
+# Examples
+
+* Test whether connecting to a remote Nix store via SSH works:
+
+ ```console
+ # nix store ping --store ssh://mac1
+ ```
+
+* Test whether a URL is a valid binary cache:
+
+ ```console
+ # nix store ping --store https://cache.nixos.org
+ ```
+
+* Test whether the Nix daemon is up and running:
+
+ ```console
+ # nix store ping --store daemon
+ ```
+
+# Description
+
+This command tests whether a particular Nix store (specified by the
+argument `--store` *url*) can be accessed. What this means is
+dependent on the type of the store. For instance, for an SSH store it
+means that Nix can connect to the specified machine.
+
+If the command succeeds, Nix returns a exit code of 0 and does not
+print any output.
+
+)""
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
new file mode 100644
index 000000000..07c41587e
--- /dev/null
+++ b/src/nix/prefetch.cc
@@ -0,0 +1,323 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "filetransfer.hh"
+#include "finally.hh"
+#include "progress-bar.hh"
+#include "tarfile.hh"
+#include "attr-path.hh"
+#include "eval-inline.hh"
+#include "legacy.hh"
+
+#include <nlohmann/json.hpp>
+
+using namespace nix;
+
+/* If ‘url’ starts with ‘mirror://’, then resolve it using the list of
+ mirrors defined in Nixpkgs. */
+string resolveMirrorUrl(EvalState & state, string url)
+{
+ if (url.substr(0, 9) != "mirror://") return url;
+
+ std::string s(url, 9);
+ auto p = s.find('/');
+ if (p == std::string::npos) throw Error("invalid mirror URL '%s'", url);
+ std::string mirrorName(s, 0, p);
+
+ Value vMirrors;
+ // FIXME: use nixpkgs flake
+ state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
+ state.forceAttrs(vMirrors);
+
+ auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
+ if (mirrorList == vMirrors.attrs->end())
+ throw Error("unknown mirror name '%s'", mirrorName);
+ state.forceList(*mirrorList->value);
+
+ if (mirrorList->value->listSize() < 1)
+ throw Error("mirror URL '%s' did not expand to anything", url);
+
+ auto mirror = state.forceString(*mirrorList->value->listElems()[0]);
+ return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
+}
+
+std::tuple<StorePath, Hash> prefetchFile(
+ ref<Store> store,
+ std::string_view url,
+ std::optional<std::string> name,
+ HashType hashType,
+ std::optional<Hash> expectedHash,
+ bool unpack,
+ bool executable)
+{
+ auto ingestionMethod = unpack || executable ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
+
+ /* Figure out a name in the Nix store. */
+ if (!name) {
+ name = baseNameOf(url);
+ if (name->empty())
+ throw Error("cannot figure out file name for '%s'", url);
+ }
+
+ std::optional<StorePath> storePath;
+ std::optional<Hash> hash;
+
+ /* If an expected hash is given, the file may already exist in
+ the store. */
+ if (expectedHash) {
+ hashType = expectedHash->type;
+ storePath = store->makeFixedOutputPath(*name, FixedOutputInfo {
+ {
+ .method = ingestionMethod,
+ .hash = *expectedHash,
+ },
+ {},
+ });
+ if (store->isValidPath(*storePath))
+ hash = expectedHash;
+ else
+ storePath.reset();
+ }
+
+ if (!storePath) {
+
+ AutoDelete tmpDir(createTempDir(), true);
+ Path tmpFile = (Path) tmpDir + "/tmp";
+
+ /* Download the file. */
+ {
+ auto mode = 0600;
+ if (executable)
+ mode = 0700;
+
+ AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (!fd) throw SysError("creating temporary file '%s'", tmpFile);
+
+ FdSink sink(fd.get());
+
+ FileTransferRequest req(url);
+ req.decompress = false;
+ getFileTransfer()->download(std::move(req), sink);
+ }
+
+ /* Optionally unpack the file. */
+ if (unpack) {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("unpacking '%s'", url));
+ Path unpacked = (Path) tmpDir + "/unpacked";
+ createDirs(unpacked);
+ unpackTarfile(tmpFile, unpacked);
+
+ /* If the archive unpacks to a single file/directory, then use
+ that as the top-level. */
+ auto entries = readDirectory(unpacked);
+ if (entries.size() == 1)
+ tmpFile = unpacked + "/" + entries[0].name;
+ else
+ tmpFile = unpacked;
+ }
+
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("adding '%s' to the store", url));
+
+ auto info = store->addToStoreSlow(*name, tmpFile, ingestionMethod, hashType, expectedHash);
+ storePath = info.path;
+ assert(info.ca);
+ hash = getContentAddressHash(*info.ca);
+ }
+
+ return {storePath.value(), hash.value()};
+}
+
+static int main_nix_prefetch_url(int argc, char * * argv)
+{
+ {
+ HashType ht = htSHA256;
+ std::vector<string> args;
+ bool printPath = getEnv("PRINT_PATH") == "1";
+ bool fromExpr = false;
+ string attrPath;
+ bool unpack = false;
+ bool executable = false;
+ std::optional<std::string> name;
+
+ struct MyArgs : LegacyArgs, MixEvalArgs
+ {
+ using LegacyArgs::LegacyArgs;
+ };
+
+ MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) {
+ if (*arg == "--help")
+ showManPage("nix-prefetch-url");
+ else if (*arg == "--version")
+ printVersion("nix-prefetch-url");
+ else if (*arg == "--type") {
+ string s = getArg(*arg, arg, end);
+ ht = parseHashType(s);
+ }
+ else if (*arg == "--print-path")
+ printPath = true;
+ else if (*arg == "--attr" || *arg == "-A") {
+ fromExpr = true;
+ attrPath = getArg(*arg, arg, end);
+ }
+ else if (*arg == "--unpack")
+ unpack = true;
+ else if (*arg == "--executable")
+ executable = true;
+ else if (*arg == "--name")
+ name = getArg(*arg, arg, end);
+ else if (*arg != "" && arg->at(0) == '-')
+ return false;
+ else
+ args.push_back(*arg);
+ return true;
+ });
+
+ myArgs.parseCmdline(argvToStrings(argc, argv));
+
+ if (args.size() > 2)
+ throw UsageError("too many arguments");
+
+ Finally f([]() { stopProgressBar(); });
+
+ if (isatty(STDERR_FILENO))
+ startProgressBar();
+
+ auto store = openStore();
+ auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
+
+ Bindings & autoArgs = *myArgs.getAutoArgs(*state);
+
+ /* If -A is given, get the URL from the specified Nix
+ expression. */
+ string url;
+ if (!fromExpr) {
+ if (args.empty())
+ throw UsageError("you must specify a URL");
+ url = args[0];
+ } else {
+ Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
+ Value vRoot;
+ state->evalFile(path, vRoot);
+ Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
+ state->forceAttrs(v);
+
+ /* Extract the URL. */
+ auto attr = v.attrs->find(state->symbols.create("urls"));
+ if (attr == v.attrs->end())
+ throw Error("attribute set does not contain a 'urls' attribute");
+ state->forceList(*attr->value);
+ if (attr->value->listSize() < 1)
+ throw Error("'urls' list is empty");
+ url = state->forceString(*attr->value->listElems()[0]);
+
+ /* Extract the hash mode. */
+ attr = v.attrs->find(state->symbols.create("outputHashMode"));
+ if (attr == v.attrs->end())
+ printInfo("warning: this does not look like a fetchurl call");
+ else
+ unpack = state->forceString(*attr->value) == "recursive";
+
+ /* Extract the name. */
+ if (!name) {
+ attr = v.attrs->find(state->symbols.create("name"));
+ if (attr != v.attrs->end())
+ name = state->forceString(*attr->value);
+ }
+ }
+
+ std::optional<Hash> expectedHash;
+ if (args.size() == 2)
+ expectedHash = Hash::parseAny(args[1], ht);
+
+ auto [storePath, hash] = prefetchFile(
+ store, resolveMirrorUrl(*state, url), name, ht, expectedHash, unpack, executable);
+
+ stopProgressBar();
+
+ if (!printPath)
+ printInfo("path is '%s'", store->printStorePath(storePath));
+
+ std::cout << printHash16or32(hash) << std::endl;
+ if (printPath)
+ std::cout << store->printStorePath(storePath) << std::endl;
+
+ return 0;
+ }
+}
+
+static RegisterLegacyCommand r_nix_prefetch_url("nix-prefetch-url", main_nix_prefetch_url);
+
+struct CmdStorePrefetchFile : StoreCommand, MixJSON
+{
+ std::string url;
+ bool executable = false;
+ std::optional<std::string> name;
+ HashType hashType = htSHA256;
+ std::optional<Hash> expectedHash;
+
+ CmdStorePrefetchFile()
+ {
+ addFlag({
+ .longName = "name",
+ .description = "Override the name component of the resulting store path. It defaults to the base name of *url*.",
+ .labels = {"name"},
+ .handler = {&name}
+ });
+
+ addFlag({
+ .longName = "expected-hash",
+ .description = "The expected hash of the file.",
+ .labels = {"hash"},
+ .handler = {[&](std::string s) {
+ expectedHash = Hash::parseAny(s, hashType);
+ }}
+ });
+
+ addFlag(Flag::mkHashTypeFlag("hash-type", &hashType));
+
+ addFlag({
+ .longName = "executable",
+ .description =
+ "Make the resulting file executable. Note that this causes the "
+ "resulting hash to be a NAR hash rather than a flat file hash.",
+ .handler = {&executable, true},
+ });
+
+ expectArg("url", &url);
+ }
+
+ Category category() override { return catUtility; }
+
+ std::string description() override
+ {
+ return "download a file into the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-prefetch-file.md"
+ ;
+ }
+ void run(ref<Store> store) override
+ {
+ auto [storePath, hash] = prefetchFile(store, url, name, hashType, expectedHash, false, executable);
+
+ if (json) {
+ auto res = nlohmann::json::object();
+ res["storePath"] = store->printStorePath(storePath);
+ res["hash"] = hash.to_string(SRI, true);
+ logger->cout(res.dump());
+ } else {
+ notice("Downloaded '%s' to '%s' (hash '%s').",
+ url,
+ store->printStorePath(storePath),
+ hash.to_string(SRI, true));
+ }
+ }
+};
+
+static auto rCmdStorePrefetchFile = registerCommand2<CmdStorePrefetchFile>({"store", "prefetch-file"});
diff --git a/src/nix/print-dev-env.md b/src/nix/print-dev-env.md
new file mode 100644
index 000000000..b80252acf
--- /dev/null
+++ b/src/nix/print-dev-env.md
@@ -0,0 +1,19 @@
+R""(
+
+# Examples
+
+* Apply the build environment of GNU hello to the current shell:
+
+ ```console
+ # . <(nix print-dev-env nixpkgs#hello)
+ ```
+
+# Description
+
+This command prints a shell script that can be sourced by `b`ash and
+that sets the environment variables and shell functions defined by the
+build process of *installable*. This allows you to get a similar build
+environment in your current shell rather than in a subshell (as with
+`nix develop`).
+
+)""
diff --git a/src/nix/profile-diff-closures.md b/src/nix/profile-diff-closures.md
new file mode 100644
index 000000000..295d1252b
--- /dev/null
+++ b/src/nix/profile-diff-closures.md
@@ -0,0 +1,28 @@
+R""(
+
+# Examples
+
+* Show what changed between each version of the NixOS system
+ profile:
+
+ ```console
+ # nix profile diff-closures --profile /nix/var/nix/profiles/system
+ Version 13 -> 14:
+ acpi-call: 2020-04-07-5.8.13 → 2020-04-07-5.8.14
+ aws-sdk-cpp: -6723.1 KiB
+ …
+
+ Version 14 -> 15:
+ acpi-call: 2020-04-07-5.8.14 → 2020-04-07-5.8.16
+ attica: -996.2 KiB
+ breeze-icons: -78713.5 KiB
+ brotli: 1.0.7 → 1.0.9, +44.2 KiB
+ ```
+
+# Description
+
+This command shows the difference between the closures of subsequent
+versions of a profile. See [`nix store
+diff-closures`](nix3-store-diff-closures.md) for details.
+
+)""
diff --git a/src/nix/profile-history.md b/src/nix/profile-history.md
new file mode 100644
index 000000000..d0fe40c82
--- /dev/null
+++ b/src/nix/profile-history.md
@@ -0,0 +1,26 @@
+R""(
+
+# Examples
+
+* Show the changes between each version of your default profile:
+
+ ```console
+ # nix profile history
+ Version 508 -> 509:
+ flake:nixpkgs#legacyPackages.x86_64-linux.awscli: ∅ -> 1.17.13
+
+ Version 509 -> 510:
+ flake:nixpkgs#legacyPackages.x86_64-linux.awscli: 1.17.13 -> 1.18.211
+ ```
+
+# Description
+
+This command shows what packages were added, removed or upgraded
+between subsequent versions of a profile. It only shows top-level
+packages, not dependencies; for that, use [`nix profile
+diff-closures`](./nix3-profile-diff-closures.md).
+
+The addition of a package to a profile is denoted by the string `∅ ->`
+*version*, whereas the removal is denoted by *version* `-> ∅`.
+
+)""
diff --git a/src/nix/profile-install.md b/src/nix/profile-install.md
new file mode 100644
index 000000000..e3009491e
--- /dev/null
+++ b/src/nix/profile-install.md
@@ -0,0 +1,27 @@
+R""(
+
+# Examples
+
+* Install a package from Nixpkgs:
+
+ ```console
+ # nix profile install nixpkgs#hello
+ ```
+
+* Install a package from a specific branch of Nixpkgs:
+
+ ```console
+ # nix profile install nixpkgs/release-20.09#hello
+ ```
+
+* Install a package from a specific revision of Nixpkgs:
+
+ ```console
+ # nix profile install nixpkgs/d73407e8e6002646acfdef0e39ace088bacc83da#hello
+ ```
+
+# Description
+
+This command adds *installables* to a Nix profile.
+
+)""
diff --git a/src/nix/profile-list.md b/src/nix/profile-list.md
new file mode 100644
index 000000000..5c29c0b02
--- /dev/null
+++ b/src/nix/profile-list.md
@@ -0,0 +1,31 @@
+R""(
+
+# Examples
+
+* Show what packages are installed in the default profile:
+
+ ```console
+ # nix profile list
+ 0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.spotify /nix/store/akpdsid105phbbvknjsdh7hl4v3fhjkr-spotify-1.1.46.916.g416cacf1
+ 1 flake:nixpkgs#legacyPackages.x86_64-linux.zoom-us github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.zoom-us /nix/store/89pmjmbih5qpi7accgacd17ybpgp4xfm-zoom-us-5.4.53350.1027
+ 2 flake:blender-bin#defaultPackage.x86_64-linux github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#defaultPackage.x86_64-linux /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0
+ ```
+
+# Description
+
+This command shows what packages are currently installed in a
+profile. The output consists of one line per package, with the
+following fields:
+
+* An integer that can be used to unambiguously identify the package in
+ invocations of `nix profile remove` and `nix profile upgrade`.
+
+* The original ("mutable") flake reference and output attribute path
+ used at installation time.
+
+* The immutable flake reference to which the mutable flake reference
+ was resolved.
+
+* The store path(s) of the package.
+
+)""
diff --git a/src/nix/profile-remove.md b/src/nix/profile-remove.md
new file mode 100644
index 000000000..dcf825da9
--- /dev/null
+++ b/src/nix/profile-remove.md
@@ -0,0 +1,32 @@
+R""(
+
+# Examples
+
+* Remove a package by position:
+
+ ```console
+ # nix profile remove 3
+ ```
+
+* Remove a package by attribute path:
+
+ ```console
+ # nix profile remove packages.x86_64-linux.hello
+ ```
+
+* Remove all packages:
+ ```console
+ # nix profile remove '.*'
+ ```
+
+* Remove a package by store path:
+
+ ```console
+ # nix profile remove /nix/store/rr3y0c6zyk7kjjl8y19s4lsrhn4aiq1z-hello-2.10
+ ```
+
+# Description
+
+This command removes a package from a profile.
+
+)""
diff --git a/src/nix/profile-upgrade.md b/src/nix/profile-upgrade.md
new file mode 100644
index 000000000..2bd5d256d
--- /dev/null
+++ b/src/nix/profile-upgrade.md
@@ -0,0 +1,41 @@
+R""(
+
+# Examples
+
+* Upgrade all packages that were installed using a mutable flake
+ reference:
+
+ ```console
+ # nix profile upgrade '.*'
+ ```
+
+* Upgrade a specific package:
+
+ ```console
+ # nix profile upgrade packages.x86_64-linux.hello
+ ```
+
+* Upgrade a specific profile element by number:
+
+ ```console
+ # nix profile info
+ 0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify …
+
+ # nix profile upgrade 0
+ ```
+
+# Description
+
+This command upgrades a previously installed package in a Nix profile,
+by fetching and evaluating the latest version of the flake from which
+the package was installed.
+
+> **Warning**
+>
+> This only works if you used a *mutable* flake reference at
+> installation time, e.g. `nixpkgs#hello`. It does not work if you
+> used an *immutable* flake reference
+> (e.g. `github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a#hello`),
+> since in that case the "latest version" is always the same.
+
+)""
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index b9ad31cf4..bd1be44e6 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -8,6 +8,7 @@
#include "flake/flakeref.hh"
#include "../nix-env/user-env.hh"
#include "profiles.hh"
+#include "names.hh"
#include <nlohmann/json.hpp>
#include <regex>
@@ -21,6 +22,13 @@ struct ProfileElementSource
FlakeRef resolvedRef;
std::string attrPath;
// FIXME: output names
+
+ bool operator < (const ProfileElementSource & other) const
+ {
+ return
+ std::pair(originalRef.to_string(), attrPath) <
+ std::pair(other.originalRef.to_string(), other.attrPath);
+ }
};
struct ProfileElement
@@ -29,6 +37,29 @@ struct ProfileElement
std::optional<ProfileElementSource> source;
bool active = true;
// FIXME: priority
+
+ std::string describe() const
+ {
+ if (source)
+ return fmt("%s#%s", source->originalRef, source->attrPath);
+ StringSet names;
+ for (auto & path : storePaths)
+ names.insert(DrvName(path.name()).name);
+ return concatStringsSep(", ", names);
+ }
+
+ std::string versions() const
+ {
+ StringSet versions;
+ for (auto & path : storePaths)
+ versions.insert(DrvName(path.name()).version);
+ return showVersions(versions);
+ }
+
+ bool operator < (const ProfileElement & other) const
+ {
+ return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
+ }
};
struct ProfileManifest
@@ -151,6 +182,46 @@ struct ProfileManifest
return std::move(info.path);
}
+
+ static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent)
+ {
+ auto prevElems = prev.elements;
+ std::sort(prevElems.begin(), prevElems.end());
+
+ auto curElems = cur.elements;
+ std::sort(curElems.begin(), curElems.end());
+
+ auto i = prevElems.begin();
+ auto j = curElems.begin();
+
+ bool changes = false;
+
+ while (i != prevElems.end() || j != curElems.end()) {
+ if (j != curElems.end() && (i == prevElems.end() || i->describe() > j->describe())) {
+ std::cout << fmt("%s%s: ∅ -> %s\n", indent, j->describe(), j->versions());
+ changes = true;
+ ++j;
+ }
+ else if (i != prevElems.end() && (j == curElems.end() || i->describe() < j->describe())) {
+ std::cout << fmt("%s%s: %s -> ∅\n", indent, i->describe(), i->versions());
+ changes = true;
+ ++i;
+ }
+ else {
+ auto v1 = i->versions();
+ auto v2 = j->versions();
+ if (v1 != v2) {
+ std::cout << fmt("%s%s: %s -> %s\n", indent, i->describe(), v1, v2);
+ changes = true;
+ }
+ ++i;
+ ++j;
+ }
+ }
+
+ if (!changes)
+ std::cout << fmt("%sNo changes.\n", indent);
+ }
};
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
@@ -160,22 +231,11 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
return "install a package into a profile";
}
- Examples examples() override
+ std::string doc() 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"
- },
- };
+ return
+ #include "profile-install.md"
+ ;
}
void run(ref<Store> store) override
@@ -198,11 +258,31 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
attrPath,
};
- pathsToBuild.push_back({drv.drvPath, StringSet{"out"}}); // FIXME
+ pathsToBuild.push_back({drv.drvPath, StringSet{drv.outputName}});
manifest.elements.emplace_back(std::move(element));
- } else
- throw UnimplementedError("'nix profile install' does not support argument '%s'", installable->what());
+ } else {
+ auto buildables = build(store, Realise::Outputs, {installable}, bmNormal);
+
+ for (auto & buildable : buildables) {
+ ProfileElement element;
+
+ std::visit(overloaded {
+ [&](BuildableOpaque bo) {
+ pathsToBuild.push_back({bo.path, {}});
+ element.storePaths.insert(bo.path);
+ },
+ [&](BuildableFromDrv bfd) {
+ for (auto & output : store->queryDerivationOutputMap(bfd.drvPath)) {
+ pathsToBuild.push_back({bfd.drvPath, {output.first}});
+ element.storePaths.insert(output.second);
+ }
+ },
+ }, buildable);
+
+ manifest.elements.emplace_back(std::move(element));
+ }
+ }
}
store->buildPaths(pathsToBuild);
@@ -229,9 +309,8 @@ public:
std::vector<Matcher> res;
for (auto & s : _matchers) {
- size_t n;
- if (string2Int(s, n))
- res.push_back(n);
+ if (auto n = string2Int<size_t>(s))
+ res.push_back(*n);
else if (store->isStorePath(s))
res.push_back(s);
else
@@ -266,26 +345,11 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
return "remove packages from a profile";
}
- Examples examples() override
+ std::string doc() 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"
- },
- };
+ return
+ #include "profile-remove.md"
+ ;
}
void run(ref<Store> store) override
@@ -319,18 +383,11 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
return "upgrade packages using their most recent flake";
}
- Examples examples() override
+ std::string doc() 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"
- },
- };
+ return
+ #include "profile-upgrade.md"
+ ;
}
void run(ref<Store> store) override
@@ -351,7 +408,13 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking '%s' for updates", element.source->attrPath));
- InstallableFlake installable(getEvalState(), FlakeRef(element.source->originalRef), {element.source->attrPath}, {}, lockFlags);
+ InstallableFlake installable(
+ this,
+ getEvalState(),
+ FlakeRef(element.source->originalRef),
+ {element.source->attrPath},
+ {},
+ lockFlags);
auto [attrPath, resolvedRef, drv] = installable.toDerivation();
@@ -379,21 +442,18 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
}
};
-struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
+struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
{
std::string description() override
{
return "list installed packages";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To show what packages are installed in the default profile:",
- "nix profile info"
- },
- };
+ return
+ #include "profile-list.md"
+ ;
}
void run(ref<Store> store) override
@@ -402,7 +462,7 @@ struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
- logger->stdout("%d %s %s %s", i,
+ logger->cout("%d %s %s %s", i,
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-",
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
@@ -414,17 +474,14 @@ struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
{
std::string description() override
{
- return "show the closure difference between each generation of a profile";
+ return "show the closure difference between each version of a profile";
}
- Examples examples() override
+ std::string doc() 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"
- },
- };
+ return
+ #include "profile-diff-closures.md"
+ ;
}
void run(ref<Store> store) override
@@ -438,7 +495,7 @@ struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
if (prevGen) {
if (!first) std::cout << "\n";
first = false;
- std::cout << fmt("Generation %d -> %d:\n", prevGen->number, gen.number);
+ std::cout << fmt("Version %d -> %d:\n", prevGen->number, gen.number);
printClosureDiff(store,
store->followLinksToStorePath(prevGen->path),
store->followLinksToStorePath(gen.path),
@@ -450,6 +507,48 @@ struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
}
};
+struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
+{
+ std::string description() override
+ {
+ return "show all versions of a profile";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "profile-history.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto [gens, curGen] = findGenerations(*profile);
+
+ std::optional<std::pair<Generation, ProfileManifest>> prevGen;
+ bool first = true;
+
+ for (auto & gen : gens) {
+ ProfileManifest manifest(*getEvalState(), gen.path);
+
+ if (!first) std::cout << "\n";
+ first = false;
+
+ if (prevGen)
+ std::cout << fmt("Version %d -> %d:\n", prevGen->first.number, gen.number);
+ else
+ std::cout << fmt("Version %d:\n", gen.number);
+
+ ProfileManifest::printDiff(
+ prevGen ? prevGen->second : ProfileManifest(),
+ manifest,
+ " ");
+
+ prevGen = {gen, std::move(manifest)};
+ }
+ }
+};
+
struct CmdProfile : NixMultiCommand
{
CmdProfile()
@@ -457,8 +556,9 @@ struct CmdProfile : NixMultiCommand
{"install", []() { return make_ref<CmdProfileInstall>(); }},
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
- {"info", []() { return make_ref<CmdProfileInfo>(); }},
+ {"list", []() { return make_ref<CmdProfileList>(); }},
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
+ {"history", []() { return make_ref<CmdProfileHistory>(); }},
})
{ }
@@ -467,6 +567,13 @@ struct CmdProfile : NixMultiCommand
return "manage Nix profiles";
}
+ std::string doc() override
+ {
+ return
+ #include "profile.md"
+ ;
+ }
+
void run() override
{
if (!command)
diff --git a/src/nix/profile.md b/src/nix/profile.md
new file mode 100644
index 000000000..d3ddcd3d1
--- /dev/null
+++ b/src/nix/profile.md
@@ -0,0 +1,107 @@
+R""(
+
+# Description
+
+`nix profile` allows you to create and manage *Nix profiles*. A Nix
+profile is a set of packages that can be installed and upgraded
+independently from each other. Nix profiles are versioned, allowing
+them to be rolled back easily.
+
+# Default profile
+
+The default profile used by `nix profile` is `$HOME/.nix-profile`,
+which, if it does not exist, is created as a symlink to
+`/nix/var/nix/profiles/per-user/default` if Nix is invoked by the
+`root` user, or `/nix/var/nix/profiles/per-user/`*username* otherwise.
+
+You can specify another profile location using `--profile` *path*.
+
+# Filesystem layout
+
+Profiles are versioned as follows. When using profile *path*, *path*
+is a symlink to *path*`-`*N*, where *N* is the current *version* of
+the profile. In turn, *path*`-`*N* is a symlink to a path in the Nix
+store. For example:
+
+```console
+$ ls -l /nix/var/nix/profiles/per-user/alice/profile*
+lrwxrwxrwx 1 alice users 14 Nov 25 14:35 /nix/var/nix/profiles/per-user/alice/profile -> profile-7-link
+lrwxrwxrwx 1 alice users 51 Oct 28 16:18 /nix/var/nix/profiles/per-user/alice/profile-5-link -> /nix/store/q69xad13ghpf7ir87h0b2gd28lafjj1j-profile
+lrwxrwxrwx 1 alice users 51 Oct 29 13:20 /nix/var/nix/profiles/per-user/alice/profile-6-link -> /nix/store/6bvhpysd7vwz7k3b0pndn7ifi5xr32dg-profile
+lrwxrwxrwx 1 alice users 51 Nov 25 14:35 /nix/var/nix/profiles/per-user/alice/profile-7-link -> /nix/store/mp0x6xnsg0b8qhswy6riqvimai4gm677-profile
+```
+
+Each of these symlinks is a root for the Nix garbage collector.
+
+The contents of the store path corresponding to each version of the
+profile is a tree of symlinks to the files of the installed packages,
+e.g.
+
+```console
+$ ll -R /nix/var/nix/profiles/per-user/eelco/profile-7-link/
+/nix/var/nix/profiles/per-user/eelco/profile-7-link/:
+total 20
+dr-xr-xr-x 2 root root 4096 Jan 1 1970 bin
+-r--r--r-- 2 root root 1402 Jan 1 1970 manifest.json
+dr-xr-xr-x 4 root root 4096 Jan 1 1970 share
+
+/nix/var/nix/profiles/per-user/eelco/profile-7-link/bin:
+total 20
+lrwxrwxrwx 5 root root 79 Jan 1 1970 chromium -> /nix/store/ijm5k0zqisvkdwjkc77mb9qzb35xfi4m-chromium-86.0.4240.111/bin/chromium
+lrwxrwxrwx 7 root root 87 Jan 1 1970 spotify -> /nix/store/w9182874m1bl56smps3m5zjj36jhp3rn-spotify-1.1.26.501.gbe11e53b-15/bin/spotify
+lrwxrwxrwx 3 root root 79 Jan 1 1970 zoom-us -> /nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927/bin/zoom-us
+
+/nix/var/nix/profiles/per-user/eelco/profile-7-link/share/applications:
+total 12
+lrwxrwxrwx 4 root root 120 Jan 1 1970 chromium-browser.desktop -> /nix/store/4cf803y4vzfm3gyk3vzhzb2327v0kl8a-chromium-unwrapped-86.0.4240.111/share/applications/chromium-browser.desktop
+lrwxrwxrwx 7 root root 110 Jan 1 1970 spotify.desktop -> /nix/store/w9182874m1bl56smps3m5zjj36jhp3rn-spotify-1.1.26.501.gbe11e53b-15/share/applications/spotify.desktop
+lrwxrwxrwx 3 root root 107 Jan 1 1970 us.zoom.Zoom.desktop -> /nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927/share/applications/us.zoom.Zoom.desktop
+
+…
+```
+
+The file `manifest.json` records the provenance of the packages that
+are installed in this version of the profile. It looks like this:
+
+```json
+{
+ "version": 1,
+ "elements": [
+ {
+ "active": true,
+ "attrPath": "legacyPackages.x86_64-linux.zoom-us",
+ "originalUri": "flake:nixpkgs",
+ "storePaths": [
+ "/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927"
+ ],
+ "uri": "github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a"
+ },
+ …
+ ]
+}
+```
+
+Each object in the array `elements` denotes an installed package and
+has the following fields:
+
+* `originalUri`: The [flake reference](./nix3-flake.md) specified by
+ the user at the time of installation (e.g. `nixpkgs`). This is also
+ the flake reference that will be used by `nix profile upgrade`.
+
+* `uri`: The immutable flake reference to which `originalUri`
+ resolved.
+
+* `attrPath`: The flake output attribute that provided this
+ package. Note that this is not necessarily the attribute that the
+ user specified, but the one resulting from applying the default
+ attribute paths and prefixes; for instance, `hello` might resolve to
+ `packages.x86_64-linux.hello` and the empty string to
+ `defaultPackage.x86_64-linux`.
+
+* `storePath`: The paths in the Nix store containing the package.
+
+* `active`: Whether the profile contains symlinks to the files of this
+ package. If set to false, the package is kept in the Nix store, but
+ is not "visible" in the profile's symlink tree.
+
+)""
diff --git a/src/nix/registry-add.md b/src/nix/registry-add.md
new file mode 100644
index 000000000..80a31996a
--- /dev/null
+++ b/src/nix/registry-add.md
@@ -0,0 +1,33 @@
+R""(
+
+# Examples
+
+* Set the `nixpkgs` flake identifier to a specific branch of Nixpkgs:
+
+ ```console
+ # nix registry add nixpkgs github:NixOS/nixpkgs/nixos-20.03
+ ```
+
+* Pin `nixpkgs` to a specific revision:
+
+ ```console
+ # nix registry add nixpkgs github:NixOS/nixpkgs/925b70cd964ceaedee26fde9b19cc4c4f081196a
+ ```
+
+* Add an entry that redirects a specific branch of `nixpkgs` to
+ another fork:
+
+ ```console
+ # nix registry add nixpkgs/nixos-20.03 ~/Dev/nixpkgs
+ ```
+
+# Description
+
+This command adds an entry to the user registry that maps flake
+reference *from-url* to flake reference *to-url*. If an entry for
+*from-url* already exists, it is overwritten.
+
+Entries can be removed using [`nix registry
+remove`](./nix3-registry-remove.md).
+
+)""
diff --git a/src/nix/registry-list.md b/src/nix/registry-list.md
new file mode 100644
index 000000000..30b6e29d8
--- /dev/null
+++ b/src/nix/registry-list.md
@@ -0,0 +1,29 @@
+R""(
+
+# Examples
+
+* Show the contents of all registries:
+
+ ```console
+ # nix registry list
+ user flake:dwarffs github:edolstra/dwarffs/d181d714fd36eb06f4992a1997cd5601e26db8f5
+ system flake:nixpkgs path:/nix/store/fxl9mrm5xvzam0lxi9ygdmksskx4qq8s-source?lastModified=1605220118&narHash=sha256-Und10ixH1WuW0XHYMxxuHRohKYb45R%2fT8CwZuLd2D2Q=&rev=3090c65041104931adda7625d37fa874b2b5c124
+ global flake:blender-bin github:edolstra/nix-warez?dir=blender
+ global flake:dwarffs github:edolstra/dwarffs
+ …
+ ```
+
+# Description
+
+This command displays the contents of all registries on standard
+output. Each line represents one registry entry in the format *type*
+*from* *to*, where *type* denotes the registry containing the entry:
+
+* `flags`: entries specified on the command line using `--override-flake`.
+* `user`: the user registry.
+* `system`: the system registry.
+* `global`: the global registry.
+
+See the [`nix registry` manual page](./nix3-registry.md) for more details.
+
+)""
diff --git a/src/nix/registry-pin.md b/src/nix/registry-pin.md
new file mode 100644
index 000000000..6e97e003e
--- /dev/null
+++ b/src/nix/registry-pin.md
@@ -0,0 +1,38 @@
+R""(
+
+# Examples
+
+* Pin `nixpkgs` to its most recent Git revision:
+
+ ```console
+ # nix registry pin nixpkgs
+ ```
+
+ Afterwards the user registry will have an entry like this:
+
+ ```console
+ nix registry list | grep '^user '
+ user flake:nixpkgs github:NixOS/nixpkgs/925b70cd964ceaedee26fde9b19cc4c4f081196a
+ ```
+
+ and `nix flake info` will say:
+
+ ```console
+ # nix flake info nixpkgs
+ Resolved URL: github:NixOS/nixpkgs/925b70cd964ceaedee26fde9b19cc4c4f081196a
+ Locked URL: github:NixOS/nixpkgs/925b70cd964ceaedee26fde9b19cc4c4f081196a
+ …
+ ```
+
+# Description
+
+This command adds an entry to the user registry that maps flake
+reference *url* to the corresponding *locked* flake reference, that
+is, a flake reference that specifies an exact revision or content
+hash. This ensures that until this registry entry is removed, all uses
+of *url* will resolve to exactly the same flake.
+
+Entries can be removed using [`nix registry
+remove`](./nix3-registry-remove.md).
+
+)""
diff --git a/src/nix/registry-remove.md b/src/nix/registry-remove.md
new file mode 100644
index 000000000..4c0eb4947
--- /dev/null
+++ b/src/nix/registry-remove.md
@@ -0,0 +1,16 @@
+R""(
+
+# Examples
+
+* Remove the entry `nixpkgs` from the user registry:
+
+ ```console
+ # nix registry remove nixpkgs
+ ```
+
+# Description
+
+This command removes from the user registry any entry for flake
+reference *url*.
+
+)""
diff --git a/src/nix/registry.cc b/src/nix/registry.cc
index 8e8983ad0..f9719600f 100644
--- a/src/nix/registry.cc
+++ b/src/nix/registry.cc
@@ -17,6 +17,13 @@ struct CmdRegistryList : StoreCommand
return "list available Nix flakes";
}
+ std::string doc() override
+ {
+ return
+ #include "registry-list.md"
+ ;
+ }
+
void run(nix::ref<nix::Store> store) override
{
using namespace fetchers;
@@ -26,7 +33,7 @@ struct CmdRegistryList : StoreCommand
for (auto & registry : registries) {
for (auto & entry : registry->entries) {
// FIXME: format nicely
- logger->stdout("%s %s %s",
+ logger->cout("%s %s %s",
registry->type == Registry::Flag ? "flags " :
registry->type == Registry::User ? "user " :
registry->type == Registry::System ? "system" :
@@ -47,6 +54,13 @@ struct CmdRegistryAdd : MixEvalArgs, Command
return "add/replace flake in user flake registry";
}
+ std::string doc() override
+ {
+ return
+ #include "registry-add.md"
+ ;
+ }
+
CmdRegistryAdd()
{
expectArg("from-url", &fromUrl);
@@ -75,6 +89,13 @@ struct CmdRegistryRemove : virtual Args, MixEvalArgs, Command
return "remove flake from user flake registry";
}
+ std::string doc() override
+ {
+ return
+ #include "registry-remove.md"
+ ;
+ }
+
CmdRegistryRemove()
{
expectArg("url", &url);
@@ -97,6 +118,13 @@ struct CmdRegistryPin : virtual Args, EvalCommand
return "pin a flake to its current version in user flake registry";
}
+ std::string doc() override
+ {
+ return
+ #include "registry-pin.md"
+ ;
+ }
+
CmdRegistryPin()
{
expectArg("url", &url);
@@ -132,6 +160,13 @@ struct CmdRegistry : virtual NixMultiCommand
return "manage the flake registry";
}
+ std::string doc() override
+ {
+ return
+ #include "registry.md"
+ ;
+ }
+
Category category() override { return catSecondary; }
void run() override
diff --git a/src/nix/registry.md b/src/nix/registry.md
new file mode 100644
index 000000000..557e5795b
--- /dev/null
+++ b/src/nix/registry.md
@@ -0,0 +1,98 @@
+R""(
+
+# Description
+
+`nix flake` provides subcommands for managing *flake
+registries*. Flake registries are a convenience feature that allows
+you to refer to flakes using symbolic identifiers such as `nixpkgs`,
+rather than full URLs such as `git://github.com/NixOS/nixpkgs`. You
+can use these identifiers on the command line (e.g. when you do `nix
+run nixpkgs#hello`) or in flake input specifications in `flake.nix`
+files. The latter are automatically resolved to full URLs and recorded
+in the flake's `flake.lock` file.
+
+In addition, the flake registry allows you to redirect arbitrary flake
+references (e.g. `github:NixOS/patchelf`) to another location, such as
+a local fork.
+
+There are multiple registries. These are, in order from lowest to
+highest precedence:
+
+* The global registry, which is a file downloaded from the URL
+ specified by the setting `flake-registry`. It is cached locally and
+ updated automatically when it's older than `tarball-ttl`
+ seconds. The default global registry is kept in [a GitHub
+ repository](https://github.com/NixOS/flake-registry).
+
+* The system registry, which is shared by all users. The default
+ location is `/etc/nix/registry.json`. On NixOS, the system registry
+ can be specified using the NixOS option `nix.registry`.
+
+* The user registry `~/.config/nix/registry.json`. This registry can
+ be modified by commands such as `nix flake pin`.
+
+* Overrides specified on the command line using the option
+ `--override-flake`.
+
+# Registry format
+
+A registry is a JSON file with the following format:
+
+```json
+{
+ "version": 2,
+ [
+ {
+ "from": {
+ "type": "indirect",
+ "id": "nixpkgs"
+ },
+ "to": {
+ "type": "github",
+ "owner": "NixOS",
+ "repo": "nixpkgs"
+ }
+ },
+ ...
+ ]
+}
+```
+
+That is, it contains a list of objects with attributes `from` and
+`to`, both of which contain a flake reference in attribute
+representation. (For example, `{"type": "indirect", "id": "nixpkgs"}`
+is the attribute representation of `nixpkgs`, while `{"type":
+"github", "owner": "NixOS", "repo": "nixpkgs"}` is the attribute
+representation of `github:NixOS/nixpkgs`.)
+
+Given some flake reference *R*, a registry entry is used if its
+`from` flake reference *matches* *R*. *R* is then replaced by the
+*unification* of the `to` flake reference with *R*.
+
+# Matching
+
+The `from` flake reference in a registry entry *matches* some flake
+reference *R* if the attributes in `from` are the same as the
+attributes in `R`. For example:
+
+* `nixpkgs` matches with `nixpkgs`.
+
+* `nixpkgs` matches with `nixpkgs/nixos-20.09`.
+
+* `nixpkgs/nixos-20.09` does not match with `nixpkgs`.
+
+* `nixpkgs` does not match with `git://github.com/NixOS/patchelf`.
+
+# Unification
+
+The `to` flake reference in a registry entry is *unified* with some flake
+reference *R* by taking `to` and applying the `rev` and `ref`
+attributes from *R*, if specified. For example:
+
+* `github:NixOS/nixpkgs` unified with `nixpkgs` produces `github:NixOS/nixpkgs`.
+
+* `github:NixOS/nixpkgs` unified with `nixpkgs/nixos-20.09` produces `github:NixOS/nixpkgs/nixos-20.09`.
+
+* `github:NixOS/nixpkgs/master` unified with `nixpkgs/nixos-20.09` produces `github:NixOS/nixpkgs/nixos-20.09`.
+
+)""
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 9ff386b1d..bce8d31dc 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -212,7 +212,7 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
try {
if (!removeWhitespace(input).empty() && !processLine(input)) return;
} catch (ParseError & e) {
- if (e.msg().find("unexpected $end") != std::string::npos) {
+ if (e.msg().find("unexpected end of file") != std::string::npos) {
// For parse errors on incomplete input, we continue waiting for the next line of
// input without clearing the input so far.
continue;
@@ -220,9 +220,9 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
printMsg(lvlError, e.msg());
}
} catch (Error & e) {
- printMsg(lvlError, e.msg());
+ printMsg(lvlError, e.msg());
} catch (Interrupted & e) {
- printMsg(lvlError, e.msg());
+ printMsg(lvlError, e.msg());
}
// We handled the current input fully, so we should clear it
@@ -405,6 +405,7 @@ bool NixRepl::processLine(string line)
}
if (command == ":?" || command == ":help") {
+ // FIXME: convert to Markdown, include in the 'nix repl' manpage.
std::cout
<< "The following commands are available:\n"
<< "\n"
@@ -446,11 +447,11 @@ bool NixRepl::processLine(string line)
Pos pos;
- if (v.type == tPath || v.type == tString) {
+ if (v.type() == nPath || v.type() == nString) {
PathSet context;
auto filename = state->coerceToString(noPos, v, context);
pos.file = state->symbols.create(filename);
- } else if (v.type == tLambda) {
+ } else if (v.isLambda()) {
pos = v.lambda.fun->pos;
} else {
// assume it's a derivation
@@ -551,9 +552,7 @@ bool NixRepl::processLine(string line)
{
Expr * e = parseString(string(line, p + 1));
Value & v(*state->allocValue());
- v.type = tThunk;
- v.thunk.env = env;
- v.thunk.expr = e;
+ v.mkThunk(env, e);
addVarToScope(state->symbols.create(name), v);
} else {
Value v;
@@ -669,31 +668,31 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
state->forceValue(v);
- switch (v.type) {
+ switch (v.type()) {
- case tInt:
+ case nInt:
str << ANSI_CYAN << v.integer << ANSI_NORMAL;
break;
- case tBool:
+ case nBool:
str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
break;
- case tString:
+ case nString:
str << ANSI_YELLOW;
printStringValue(str, v.string.s);
str << ANSI_NORMAL;
break;
- case tPath:
+ case nPath:
str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
break;
- case tNull:
+ case nNull:
str << ANSI_CYAN "null" ANSI_NORMAL;
break;
- case tAttrs: {
+ case nAttrs: {
seen.insert(&v);
bool isDrv = state->isDerivation(v);
@@ -738,9 +737,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break;
}
- case tList1:
- case tList2:
- case tListN:
+ case nList:
seen.insert(&v);
str << "[ ";
@@ -761,22 +758,21 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
str << "]";
break;
- case tLambda: {
- std::ostringstream s;
- s << v.lambda.fun->pos;
- str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
- break;
- }
-
- case tPrimOp:
- str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
- break;
-
- case tPrimOpApp:
- str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
+ case nFunction:
+ if (v.isLambda()) {
+ std::ostringstream s;
+ s << v.lambda.fun->pos;
+ str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
+ } else if (v.isPrimOp()) {
+ str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
+ } else if (v.isPrimOpApp()) {
+ str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
+ } else {
+ abort();
+ }
break;
- case tFloat:
+ case nFloat:
str << v.fpoint;
break;
@@ -806,14 +802,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs
return "start an interactive environment for evaluating Nix expressions";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "Display all special commands within the REPL:",
- "nix repl\nnix-repl> :?"
- }
- };
+ return
+ #include "repl.md"
+ ;
}
void run(ref<Store> store) override
diff --git a/src/nix/repl.md b/src/nix/repl.md
new file mode 100644
index 000000000..bba60f871
--- /dev/null
+++ b/src/nix/repl.md
@@ -0,0 +1,57 @@
+R""(
+
+# Examples
+
+* Display all special commands within the REPL:
+
+ ```console
+ # nix repl
+ nix-repl> :?
+ ```
+
+* Evaluate some simple Nix expressions:
+
+ ```console
+ # nix repl
+
+ nix-repl> 1 + 2
+ 3
+
+ nix-repl> map (x: x * 2) [1 2 3]
+ [ 2 4 6 ]
+ ```
+
+* Interact with Nixpkgs in the REPL:
+
+ ```console
+ # nix repl '<nixpkgs>'
+
+ Loading '<nixpkgs>'...
+ Added 12428 variables.
+
+ nix-repl> emacs.name
+ "emacs-27.1"
+
+ nix-repl> emacs.drvPath
+ "/nix/store/lp0sjrhgg03y2n0l10n70rg0k7hhyz0l-emacs-27.1.drv"
+
+ nix-repl> drv = runCommand "hello" { buildInputs = [ hello ]; } "hello > $out"
+
+ nix-repl> :b x
+ this derivation produced the following outputs:
+ out -> /nix/store/0njwbgwmkwls0w5dv9mpc1pq5fj39q0l-hello
+
+ nix-repl> builtins.readFile drv
+ "Hello, world!\n"
+ ```
+
+# Description
+
+This command provides an interactive environment for evaluating Nix
+expressions. (REPL stands for 'read–eval–print loop'.)
+
+On startup, it loads the Nix expressions named *files* and adds them
+into the lexical scope. You can load addition files using the `:l
+<filename>` command, or reload all files using `:r`.
+
+)""
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 790784382..ec9388234 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -22,6 +22,9 @@ std::string chrootHelperName = "__run_in_chroot";
struct RunCommon : virtual Command
{
+
+ using Command::run;
+
void runProgram(ref<Store> store,
const std::string & program,
const Strings & args)
@@ -59,6 +62,9 @@ struct RunCommon : virtual Command
struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
{
+
+ using InstallablesCommand::run;
+
std::vector<std::string> command = { getEnv("SHELL").value_or("bash") };
CmdShell()
@@ -66,7 +72,7 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
addFlag({
.longName = "command",
.shortName = 'c',
- .description = "command and arguments to be executed; defaults to '$SHELL'",
+ .description = "Command and arguments to be executed, defaulting to `$SHELL`",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
@@ -80,26 +86,11 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
return "run a shell in which the specified packages are available";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "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"
- },
- Example{
- "To run GNU Hello:",
- "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"
- },
- };
+ return
+ #include "shell.md"
+ ;
}
void run(ref<Store> store) override
@@ -144,6 +135,8 @@ static auto rCmdShell = registerCommand<CmdShell>("shell");
struct CmdRun : InstallableCommand, RunCommon
{
+ using InstallableCommand::run;
+
std::vector<std::string> args;
CmdRun()
@@ -160,22 +153,11 @@ struct CmdRun : InstallableCommand, RunCommon
return "run a Nix application";
}
- Examples examples() override
+ std::string doc() 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"
- },
- };
+ return
+ #include "run.md"
+ ;
}
Strings getDefaultFlakeAttrPaths() override
@@ -250,14 +232,16 @@ void chrootHelper(int argc, char * * argv)
for (auto entry : readDirectory("/")) {
auto src = "/" + entry.name;
- auto st = lstat(src);
- if (!S_ISDIR(st.st_mode)) continue;
Path dst = tmpDir + "/" + entry.name;
if (pathExists(dst)) continue;
- if (mkdir(dst.c_str(), 0700) == -1)
- throw SysError("creating directory '%s'", dst);
- if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
- throw SysError("mounting '%s' on '%s'", src, dst);
+ auto st = lstat(src);
+ if (S_ISDIR(st.st_mode)) {
+ if (mkdir(dst.c_str(), 0700) == -1)
+ throw SysError("creating directory '%s'", dst);
+ if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
+ throw SysError("mounting '%s' on '%s'", src, dst);
+ } else if (S_ISLNK(st.st_mode))
+ createSymlink(readLink(src), dst);
}
char * cwd = getcwd(0, 0);
diff --git a/src/nix/run.md b/src/nix/run.md
new file mode 100644
index 000000000..a76750376
--- /dev/null
+++ b/src/nix/run.md
@@ -0,0 +1,88 @@
+R""(
+
+# Examples
+
+* Run the default app from the `blender-bin` flake:
+
+ ```console
+ # nix run blender-bin
+ ```
+
+* Run a non-default app from the `blender-bin` flake:
+
+ ```console
+ # nix run blender-bin#blender_2_83
+ ```
+
+ Tip: you can find apps provided by this flake by running `nix flake
+ show blender-bin`.
+
+* Run `vim` from the `nixpkgs` flake:
+
+ ```console
+ # nix run nixpkgs#vim
+ ```
+
+ Note that `vim` (as of the time of writing of this page) is not an
+ app but a package. Thus, Nix runs the eponymous file from the `vim`
+ package.
+
+* Run `vim` with arguments:
+
+ ```console
+ # nix run nixpkgs#vim -- --help
+ ```
+
+# Description
+
+`nix run` builds and runs *installable*, which must evaluate to an
+*app* or a regular Nix derivation.
+
+If *installable* evaluates to an *app* (see below), it executes the
+program specified by the app definition.
+
+If *installable* evaluates to a derivation, it will try to execute the
+program `<out>/bin/<name>`, where *out* is the primary output store
+path of the derivation and *name* is the `meta.mainProgram` attribute
+of the derivation if it exists, and otherwise the name part of the
+value of the `name` attribute of the derivation (e.g. if `name` is set
+to `hello-1.10`, it will run `$out/bin/hello`).
+
+# Flake output attributes
+
+If no flake output attribute is given, `nix run` tries the following
+flake output attributes:
+
+* `defaultApp.<system>`
+
+* `defaultPackage.<system>`
+
+If an attribute *name* is given, `nix run` tries the following flake
+output attributes:
+
+* `apps.<system>.<name>`
+
+* `packages.<system>.<name>`
+
+* `legacyPackages.<system>.<name>`
+
+# Apps
+
+An app is specified by a flake output attribute named
+`apps.<system>.<name>` or `defaultApp.<system>`. It looks like this:
+
+```nix
+apps.x86_64-linux.blender_2_79 = {
+ type = "app";
+ program = "${self.packages.x86_64-linux.blender_2_79}/bin/blender";
+};
+```
+
+The only supported attributes are:
+
+* `type` (required): Must be set to `app`.
+
+* `program` (required): The full path of the executable to run. It
+ must reside in the Nix store.
+
+)""
diff --git a/src/nix/search.cc b/src/nix/search.cc
index d4326dc84..c52a48d4e 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -41,29 +41,14 @@ struct CmdSearch : InstallableCommand, MixJSON
std::string description() override
{
- return "query available packages";
+ return "search for packages";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To show all packages in the flake in the current directory:",
- "nix search"
- },
- Example{
- "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 nixpkgs 'firefox|chromium'"
- },
- Example{
- "To search for packages containing 'git' and either 'frontend' or 'gui':",
- "nix search nixpkgs git 'frontend|gui'"
- }
- };
+ return
+ #include "search.md"
+ ;
}
Strings getDefaultFlakeAttrPaths() override
@@ -96,9 +81,9 @@ struct CmdSearch : InstallableCommand, MixJSON
uint64_t results = 0;
- std::function<void(eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath)> visit;
+ std::function<void(eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath, bool initialRecurse)> visit;
- visit = [&](eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath)
+ visit = [&](eval_cache::AttrCursor & cursor, const std::vector<Symbol> & attrPath, bool initialRecurse)
{
Activity act(*logger, lvlInfo, actUnknown,
fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
@@ -109,7 +94,7 @@ struct CmdSearch : InstallableCommand, MixJSON
auto cursor2 = cursor.getAttr(attr);
auto attrPath2(attrPath);
attrPath2.push_back(attr);
- visit(*cursor2, attrPath2);
+ visit(*cursor2, attrPath2, false);
}
};
@@ -147,13 +132,13 @@ struct CmdSearch : InstallableCommand, MixJSON
jsonElem.attr("description", description);
} else {
auto name2 = hilite(name.name, nameMatch, "\e[0;2m");
- if (results > 1) logger->stdout("");
- logger->stdout(
+ if (results > 1) logger->cout("");
+ logger->cout(
"* %s%s",
wrap("\e[0;1m", hilite(attrPath2, attrPathMatch, "\e[0;1m")),
name.version != "" ? " (" + name.version + ")" : "");
if (description != "")
- logger->stdout(
+ logger->cout(
" %s", hilite(description, descriptionMatch, ANSI_NORMAL));
}
}
@@ -165,6 +150,9 @@ struct CmdSearch : InstallableCommand, MixJSON
|| (attrPath[0] == "packages" && attrPath.size() <= 2))
recurse();
+ else if (initialRecurse)
+ recurse();
+
else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) {
auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations);
if (attr && attr->getBool())
@@ -178,7 +166,7 @@ struct CmdSearch : InstallableCommand, MixJSON
};
for (auto & [cursor, prefix] : installable->getCursors(*state))
- visit(*cursor, parseAttrPath(*state, prefix));
+ visit(*cursor, parseAttrPath(*state, prefix), true);
if (!json && !results)
throw Error("no results for the given search term(s)!");
diff --git a/src/nix/search.md b/src/nix/search.md
new file mode 100644
index 000000000..d182788a6
--- /dev/null
+++ b/src/nix/search.md
@@ -0,0 +1,72 @@
+R""(
+
+# Examples
+
+* Show all packages in the `nixpkgs` flake:
+
+ ```console
+ # nix search nixpkgs
+ * legacyPackages.x86_64-linux.AMB-plugins (0.8.1)
+ A set of ambisonics ladspa plugins
+
+ * legacyPackages.x86_64-linux.ArchiSteamFarm (4.3.1.0)
+ Application with primary purpose of idling Steam cards from multiple accounts simultaneously
+ …
+ ```
+
+* Show packages in the `nixpkgs` flake containing `blender` in its
+ name or description:
+
+ ```console
+ # nix search nixpkgs blender
+ * legacyPackages.x86_64-linux.blender (2.91.0)
+ 3D Creation/Animation/Publishing System
+ ```
+
+* Search for packages underneath the attribute `gnome3` in Nixpkgs:
+
+ ```console
+ # nix search nixpkgs#gnome3 vala
+ * legacyPackages.x86_64-linux.gnome3.vala (0.48.9)
+ Compiler for GObject type system
+ ```
+
+* Show all packages in the flake in the current directory:
+
+ ```console
+ # nix search
+ ```
+
+* Search for Firefox or Chromium:
+
+ ```console
+ # nix search nixpkgs 'firefox|chromium'
+ ```
+
+* Search for packages containing `git'`and either `frontend` or `gui`:
+
+ ```console
+ # nix search nixpkgs git 'frontend|gui'
+ ```
+
+# Description
+
+`nix search` searches *installable* (which must be evaluatable, e.g. a
+flake) for packages whose name or description matches all of the
+regular expressions *regex*. For each matching package, It prints the
+full attribute name (from the root of the installable), the version
+and the `meta.description` field, highlighting the substrings that
+were matched by the regular expressions. If no regular expressions are
+specified, all packages are shown.
+
+# Flake output attributes
+
+If no flake output attribute is given, `nix search` searches for
+packages:
+
+* Directly underneath `packages.<system>`.
+
+* Underneath `legacyPackages.<system>`, recursing into attribute sets
+ that contain an attribute `recurseForDerivations = true`.
+
+)""
diff --git a/src/nix/shell.md b/src/nix/shell.md
new file mode 100644
index 000000000..2a379e03f
--- /dev/null
+++ b/src/nix/shell.md
@@ -0,0 +1,48 @@
+R""(
+
+# Examples
+
+* Start a shell providing `youtube-dl` from the `nixpkgs` flake:
+
+ ```console
+ # nix shell nixpkgs#youtube-dl
+ # youtube-dl --version
+ 2020.11.01.1
+ ```
+
+* Start a shell providing GNU Hello from NixOS 20.03:
+
+ ```console
+ # nix shell nixpkgs/nixos-20.03#hello
+ ```
+
+* Run GNU Hello:
+
+ ```console
+ # nix shell nixpkgs#hello -c hello --greeting 'Hi everybody!'
+ Hi everybody!
+ ```
+
+* Run GNU Hello in a chroot store:
+
+ ```console
+ # nix shell --store ~/my-nix nixpkgs#hello -c hello
+ ```
+
+* Start a shell providing GNU Hello in a chroot store:
+
+ ```console
+ # nix shell --store ~/my-nix nixpkgs#hello nixpkgs#bashInteractive -c bash
+ ```
+
+ Note that it's necessary to specify `bash` explicitly because your
+ default shell (e.g. `/bin/bash`) generally will not exist in the
+ chroot.
+
+# Description
+
+`nix shell` runs a command in an environment in which the `$PATH`
+variable provides the specified *installables*. If not command is
+specified, it starts the default shell of your user account.
+
+)""
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index 1ef54a33a..91721219b 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -20,12 +20,12 @@ struct CmdShowConfig : Command, MixJSON
{
if (json) {
// FIXME: use appropriate JSON types (bool, ints, etc).
- logger->stdout("%s", globalConfig.toJSON().dump());
+ logger->cout("%s", globalConfig.toJSON().dump());
} else {
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (auto & s : settings)
- logger->stdout("%s = %s", s.first, s.second.value);
+ logger->cout("%s = %s", s.first, s.second.value);
}
}
};
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index 0799f21eb..d13960280 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -1,4 +1,5 @@
// FIXME: integrate this with nix path-info?
+// FIXME: rename to 'nix store show-derivation' or 'nix debug show-derivation'?
#include "command.hh"
#include "common-args.hh"
@@ -18,7 +19,7 @@ struct CmdShowDerivation : InstallablesCommand
addFlag({
.longName = "recursive",
.shortName = 'r',
- .description = "include the dependencies of the specified derivations",
+ .description = "Include the dependencies of the specified derivations.",
.handler = {&recursive, true}
});
}
@@ -28,18 +29,11 @@ struct CmdShowDerivation : InstallablesCommand
return "show the contents of a store derivation";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To show the store derivation that results from evaluating the Hello package:",
- "nix show-derivation nixpkgs#hello"
- },
- Example{
- "To show the full derivation graph (if available) that produced your NixOS system:",
- "nix show-derivation -r /run/current-system"
- },
- };
+ return
+ #include "show-derivation.md"
+ ;
}
Category category() override { return catUtility; }
@@ -83,6 +77,7 @@ struct CmdShowDerivation : InstallablesCommand
[&](DerivationOutputCAFloating dof) {
outputObj.attr("hashAlgo", makeContentAddressingPrefix(dof.method) + printHashType(dof.hashType));
},
+ [&](DerivationOutputDeferred) {},
}, output.output);
}
}
@@ -102,7 +97,7 @@ struct CmdShowDerivation : InstallablesCommand
}
}
- drvObj.attr("platform", drv.platform);
+ drvObj.attr("system", drv.platform);
drvObj.attr("builder", drv.builder);
{
diff --git a/src/nix/show-derivation.md b/src/nix/show-derivation.md
new file mode 100644
index 000000000..aa863899c
--- /dev/null
+++ b/src/nix/show-derivation.md
@@ -0,0 +1,103 @@
+R""(
+
+# Examples
+
+* Show the store derivation that results from evaluating the Hello
+ package:
+
+ ```console
+ # nix show-derivation nixpkgs#hello
+ {
+ "/nix/store/s6rn4jz1sin56rf4qj5b5v8jxjm32hlk-hello-2.10.drv": {
+ …
+ }
+ }
+ ```
+
+* Show the full derivation graph (if available) that produced your
+ NixOS system:
+
+ ```console
+ # nix show-derivation -r /run/current-system
+ ```
+
+* Print all files fetched using `fetchurl` by Firefox's dependency
+ graph:
+
+ ```console
+ # nix show-derivation -r nixpkgs#firefox \
+ | jq -r '.[] | select(.outputs.out.hash and .env.urls) | .env.urls' \
+ | uniq | sort
+ ```
+
+ Note that `.outputs.out.hash` selects *fixed-output derivations*
+ (derivations that produce output with a specified content hash),
+ while `.env.urls` selects derivations with a `urls` attribute.
+
+# Description
+
+This command prints on standard output a JSON representation of the
+store derivations to which *installables* evaluate. Store derivations
+are used internally by Nix. They are store paths with extension `.drv`
+that represent the build-time dependency graph to which a Nix
+expression evaluates.
+
+By default, this command only shows top-level derivations, but with
+`--recursive`, it also shows their dependencies.
+
+The JSON output is a JSON object whose keys are the store paths of the
+derivations, and whose values are a JSON object with the following
+fields:
+
+* `outputs`: Information about the output paths of the
+ derivation. This is a JSON object with one member per output, where
+ the key is the output name and the value is a JSON object with these
+ fields:
+
+ * `path`: The output path.
+ * `hashAlgo`: For fixed-output derivations, the hashing algorithm
+ (e.g. `sha256`), optionally prefixed by `r:` if `hash` denotes a
+ NAR hash rather than a flat file hash.
+ * `hash`: For fixed-output derivations, the expected content hash in
+ base-16.
+
+ Example:
+
+ ```json
+ "outputs": {
+ "out": {
+ "path": "/nix/store/2543j7c6jn75blc3drf4g5vhb1rhdq29-source",
+ "hashAlgo": "r:sha256",
+ "hash": "6fc80dcc62179dbc12fc0b5881275898f93444833d21b89dfe5f7fbcbb1d0d62"
+ }
+ }
+ ```
+
+* `inputSrcs`: A list of store paths on which this derivation depends.
+
+* `inputDrvs`: A JSON object specifying the derivations on which this
+ derivation depends, and what outputs of those derivations. For
+ example,
+
+ ```json
+ "inputDrvs": {
+ "/nix/store/6lkh5yi7nlb7l6dr8fljlli5zfd9hq58-curl-7.73.0.drv": ["dev"],
+ "/nix/store/fn3kgnfzl5dzym26j8g907gq3kbm8bfh-unzip-6.0.drv": ["out"]
+ }
+ ```
+
+ specifies that this derivation depends on the `dev` output of
+ `curl`, and the `out` output of `unzip`.
+
+* `system`: The system type on which this derivation is to be built
+ (e.g. `x86_64-linux`).
+
+* `builder`: The absolute path of the program to be executed to run
+ the build. Typically this is the `bash` shell
+ (e.g. `/nix/store/r3j288vpmczbl500w6zz89gyfa4nr0b1-bash-4.4-p23/bin/bash`).
+
+* `args`: The command-line arguments passed to the `builder`.
+
+* `env`: The environment passed to the `builder`.
+
+)""
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 26bd277f5..10d0ae985 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -16,7 +16,7 @@ struct CmdCopySigs : StorePathsCommand
addFlag({
.longName = "substituter",
.shortName = 's',
- .description = "use signatures from specified store",
+ .description = "Copy signatures from the specified store.",
.labels = {"store-uri"},
.handler = {[&](std::string s) { substituterUris.push_back(s); }},
});
@@ -24,11 +24,9 @@ struct CmdCopySigs : StorePathsCommand
std::string description() override
{
- return "copy path signatures from substituters (like binary caches)";
+ return "copy store path signatures from substituters";
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store, StorePaths storePaths) override
{
if (substituterUris.empty())
@@ -93,18 +91,18 @@ struct CmdCopySigs : StorePathsCommand
}
};
-static auto rCmdCopySigs = registerCommand<CmdCopySigs>("copy-sigs");
+static auto rCmdCopySigs = registerCommand2<CmdCopySigs>({"store", "copy-sigs"});
-struct CmdSignPaths : StorePathsCommand
+struct CmdSign : StorePathsCommand
{
Path secretKeyFile;
- CmdSignPaths()
+ CmdSign()
{
addFlag({
.longName = "key-file",
.shortName = 'k',
- .description = "file containing the secret signing key",
+ .description = "File containing the secret signing key.",
.labels = {"file"},
.handler = {&secretKeyFile},
.completer = completePath
@@ -113,11 +111,9 @@ struct CmdSignPaths : StorePathsCommand
std::string description() override
{
- return "sign the specified paths";
+ return "sign store paths";
}
- Category category() override { return catUtility; }
-
void run(ref<Store> store, StorePaths storePaths) override
{
if (secretKeyFile.empty())
@@ -145,4 +141,89 @@ struct CmdSignPaths : StorePathsCommand
}
};
-static auto rCmdSignPaths = registerCommand<CmdSignPaths>("sign-paths");
+static auto rCmdSign = registerCommand2<CmdSign>({"store", "sign"});
+
+struct CmdKeyGenerateSecret : Command
+{
+ std::optional<std::string> keyName;
+
+ CmdKeyGenerateSecret()
+ {
+ addFlag({
+ .longName = "key-name",
+ .description = "Identifier of the key (e.g. `cache.example.org-1`).",
+ .labels = {"name"},
+ .handler = {&keyName},
+ });
+ }
+
+ std::string description() override
+ {
+ return "generate a secret key for signing store paths";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "key-generate-secret.md"
+ ;
+ }
+
+ void run() override
+ {
+ if (!keyName)
+ throw UsageError("required argument '--key-name' is missing");
+
+ std::cout << SecretKey::generate(*keyName).to_string();
+ }
+};
+
+struct CmdKeyConvertSecretToPublic : Command
+{
+ std::string description() override
+ {
+ return "generate a public key for verifying store paths from a secret key read from standard input";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "key-convert-secret-to-public.md"
+ ;
+ }
+
+ void run() override
+ {
+ SecretKey secretKey(drainFD(STDIN_FILENO));
+ std::cout << secretKey.toPublicKey().to_string();
+ }
+};
+
+struct CmdKey : NixMultiCommand
+{
+ CmdKey()
+ : MultiCommand({
+ {"generate-secret", []() { return make_ref<CmdKeyGenerateSecret>(); }},
+ {"convert-secret-to-public", []() { return make_ref<CmdKeyConvertSecretToPublic>(); }},
+ })
+ {
+ }
+
+ std::string description() override
+ {
+ return "generate and convert Nix signing keys";
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix flake' requires a sub-command.");
+ settings.requireExperimentalFeature("flakes");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdKey = registerCommand<CmdKey>("key");
diff --git a/src/nix/store-cat.md b/src/nix/store-cat.md
new file mode 100644
index 000000000..da2073473
--- /dev/null
+++ b/src/nix/store-cat.md
@@ -0,0 +1,19 @@
+R""(
+
+# Examples
+
+* Show the contents of a file in a binary cache:
+
+ ```console
+ # nix store cat --store https://cache.nixos.org/ \
+ /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10/bin/hello | hexdump -C | head -n1
+ 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
+ ```
+
+# Description
+
+This command prints on standard output the contents of the regular
+file *path* in a Nix store. *path* can be a top-level store path or
+any file inside a store path.
+
+)""
diff --git a/src/nix/store-delete.cc b/src/nix/store-delete.cc
new file mode 100644
index 000000000..10245978e
--- /dev/null
+++ b/src/nix/store-delete.cc
@@ -0,0 +1,44 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreDelete : StorePathsCommand
+{
+ GCOptions options { .action = GCOptions::gcDeleteSpecific };
+
+ CmdStoreDelete()
+ {
+ addFlag({
+ .longName = "ignore-liveness",
+ .description = "Do not check whether the paths are reachable from a root.",
+ .handler = {&options.ignoreLiveness, true}
+ });
+ }
+
+ std::string description() override
+ {
+ return "delete paths from the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-delete.md"
+ ;
+ }
+
+ void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ {
+ for (auto & path : storePaths)
+ options.pathsToDelete.insert(path);
+
+ GCResults results;
+ PrintFreed freed(true, results);
+ store->collectGarbage(options, results);
+ }
+};
+
+static auto rCmdStoreDelete = registerCommand2<CmdStoreDelete>({"store", "delete"});
diff --git a/src/nix/store-delete.md b/src/nix/store-delete.md
new file mode 100644
index 000000000..db535f87c
--- /dev/null
+++ b/src/nix/store-delete.md
@@ -0,0 +1,24 @@
+R""(
+
+# Examples
+
+* Delete a specific store path:
+
+ ```console
+ # nix store delete /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ ```
+
+# Description
+
+This command deletes the store paths specified by *installables*. ,
+but only if it is safe to do so; that is, when the path is not
+reachable from a root of the garbage collector. This means that you
+can only delete paths that would also be deleted by `nix store
+gc`. Thus, `nix store delete` is a more targeted version of `nix store
+gc`.
+
+With the option `--ignore-liveness`, reachability from the roots is
+ignored. However, the path still won't be deleted if there are other
+paths in the store that refer to it (i.e., depend on it).
+
+)""
diff --git a/src/nix/store-dump-path.md b/src/nix/store-dump-path.md
new file mode 100644
index 000000000..4ef563526
--- /dev/null
+++ b/src/nix/store-dump-path.md
@@ -0,0 +1,23 @@
+R""(
+
+# Examples
+
+* To get a NAR containing the GNU Hello package:
+
+ ```console
+ # nix store dump-path nixpkgs#hello > hello.nar
+ ```
+
+* To get a NAR from the binary cache https://cache.nixos.org/:
+
+ ```console
+ # nix store dump-path --store https://cache.nixos.org/ \
+ /nix/store/7crrmih8c52r8fbnqb933dxrsp44md93-glibc-2.25 > glibc.nar
+ ```
+
+# Description
+
+This command generates a NAR file containing the serialisation of the
+store path *installable*. The NAR is written to standard output.
+
+)""
diff --git a/src/nix/store-gc.cc b/src/nix/store-gc.cc
new file mode 100644
index 000000000..a2d74066e
--- /dev/null
+++ b/src/nix/store-gc.cc
@@ -0,0 +1,43 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreGC : StoreCommand, MixDryRun
+{
+ GCOptions options;
+
+ CmdStoreGC()
+ {
+ addFlag({
+ .longName = "max",
+ .description = "Stop after freeing *n* bytes of disk space.",
+ .labels = {"n"},
+ .handler = {&options.maxFreed}
+ });
+ }
+
+ std::string description() override
+ {
+ return "perform garbage collection on a Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-gc.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ options.action = dryRun ? GCOptions::gcReturnDead : GCOptions::gcDeleteDead;
+ GCResults results;
+ PrintFreed freed(options.action == GCOptions::gcDeleteDead, results);
+ store->collectGarbage(options, results);
+ }
+};
+
+static auto rCmdStoreGC = registerCommand2<CmdStoreGC>({"store", "gc"});
diff --git a/src/nix/store-gc.md b/src/nix/store-gc.md
new file mode 100644
index 000000000..956b3c872
--- /dev/null
+++ b/src/nix/store-gc.md
@@ -0,0 +1,21 @@
+R""(
+
+# Examples
+
+* Delete unreachable paths in the Nix store:
+
+ ```console
+ # nix store gc
+ ```
+
+* Delete up to 1 gigabyte of garbage:
+
+ ```console
+ # nix store gc --max 1G
+ ```
+
+# Description
+
+This command deletes unreachable paths in the Nix store.
+
+)""
diff --git a/src/nix/store-ls.md b/src/nix/store-ls.md
new file mode 100644
index 000000000..836efce42
--- /dev/null
+++ b/src/nix/store-ls.md
@@ -0,0 +1,27 @@
+R""(
+
+# Examples
+
+* To list the contents of a store path in a binary cache:
+
+ ```console
+ # nix store ls --store https://cache.nixos.org/ -lR /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10
+ dr-xr-xr-x 0 ./bin
+ -r-xr-xr-x 38184 ./bin/hello
+ dr-xr-xr-x 0 ./share
+ …
+ ```
+
+* To show information about a specific file in a binary cache:
+
+ ```console
+ # nix store ls --store https://cache.nixos.org/ -l /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10/bin/hello
+ -r-xr-xr-x 38184 hello
+ ```
+
+# Description
+
+This command shows information about *path* in a Nix store. *path* can
+be a top-level store path or any file inside a store path.
+
+)""
diff --git a/src/nix/store-prefetch-file.md b/src/nix/store-prefetch-file.md
new file mode 100644
index 000000000..1663b847b
--- /dev/null
+++ b/src/nix/store-prefetch-file.md
@@ -0,0 +1,32 @@
+R""(
+
+# Examples
+
+* Download a file to the Nix store:
+
+ ```console
+ # nix store prefetch-file https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz
+ Downloaded 'https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz' to
+ '/nix/store/vbdbi42hgnc4h7pyqzp6h2yf77kw93aw-source' (hash
+ 'sha256-qKheVd5D0BervxMDbt+1hnTKE2aRWC8XCAwc0SeHt6s=').
+ ```
+
+* Download a file and get the SHA-512 hash:
+
+ ```console
+ # nix store prefetch-file --json --hash-type sha512 \
+ https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz \
+ | jq -r .hash
+ sha512-6XJxfym0TNH9knxeH4ZOvns6wElFy3uahunl2hJgovACCMEMXSy42s69zWVyGJALXTI+86tpDJGlIcAySEKBbA==
+ ```
+
+# Description
+
+This command downloads the file *url* to the Nix store. It prints out
+the resulting store path and the cryptographic hash of the contents of
+the file.
+
+The name component of the store path defaults to the last component of
+*url*, but this can be overriden using `--name`.
+
+)""
diff --git a/src/nix/store-repair.cc b/src/nix/store-repair.cc
new file mode 100644
index 000000000..1c7a4392e
--- /dev/null
+++ b/src/nix/store-repair.cc
@@ -0,0 +1,27 @@
+#include "command.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreRepair : StorePathsCommand
+{
+ std::string description() override
+ {
+ return "repair store paths";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-repair.md"
+ ;
+ }
+
+ void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ {
+ for (auto & path : storePaths)
+ store->repairPath(path);
+ }
+};
+
+static auto rStoreRepair = registerCommand2<CmdStoreRepair>({"store", "repair"});
diff --git a/src/nix/store-repair.md b/src/nix/store-repair.md
new file mode 100644
index 000000000..92d2205a9
--- /dev/null
+++ b/src/nix/store-repair.md
@@ -0,0 +1,32 @@
+R""(
+
+# Examples
+
+* Repair a store path, after determining that it is corrupt:
+
+ ```console
+ # nix store verify /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ path '/nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10' was
+ modified! expected hash
+ 'sha256:1hd5vnh6xjk388gdk841vflicy8qv7qzj2hb7xlyh8lpb43j921l', got
+ 'sha256:1a25lf78x5wi6pfkrxalf0n13kdaca0bqmjqnp7wfjza2qz5ssgl'
+
+ # nix store repair /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ ```
+
+# Description
+
+This command attempts to "repair" the store paths specified by
+*installables* by redownloading them using the available
+substituters. If no substitutes are available, then repair is not
+possible.
+
+> **Warning**
+>
+> During repair, there is a very small time window during which the old
+> path (if it exists) is moved out of the way and replaced with the new
+> path. If repair is interrupted in between, then the system may be left
+> in a broken state (e.g., if the path contains a critical system
+> component like the GNU C Library).
+
+)""
diff --git a/src/nix/store.cc b/src/nix/store.cc
new file mode 100644
index 000000000..44e53c7c7
--- /dev/null
+++ b/src/nix/store.cc
@@ -0,0 +1,26 @@
+#include "command.hh"
+
+using namespace nix;
+
+struct CmdStore : virtual NixMultiCommand
+{
+ CmdStore() : MultiCommand(RegisterCommand::getCommandsFor({"store"}))
+ { }
+
+ std::string description() override
+ {
+ return "manipulate a Nix store";
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix store' requires a sub-command.");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdStore = registerCommand<CmdStore>("store");
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 66ecc5b34..9cd567896 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -19,14 +19,14 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
addFlag({
.longName = "profile",
.shortName = 'p',
- .description = "the Nix profile to upgrade",
+ .description = "The path to the Nix profile to upgrade.",
.labels = {"profile-dir"},
.handler = {&profileDir}
});
addFlag({
.longName = "nix-store-paths-url",
- .description = "URL of the file that contains the store paths of the latest Nix release",
+ .description = "The URL of the file that contains the store paths of the latest Nix release.",
.labels = {"url"},
.handler = {&storePathsUrl}
});
@@ -37,18 +37,11 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
return "upgrade Nix to the latest stable version";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To upgrade Nix to the latest stable version:",
- "nix upgrade-nix"
- },
- Example{
- "To upgrade Nix in a specific profile:",
- "nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile"
- },
- };
+ return
+ #include "upgrade-nix.md"
+ ;
}
Category category() override { return catNixInstallation; }
@@ -68,10 +61,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
if (dryRun) {
stopProgressBar();
- logWarning({
- .name = "Version update",
- .hint = hintfmt("would upgrade to version %s", version)
- });
+ warn("would upgrade to version %s", version);
return;
}
diff --git a/src/nix/upgrade-nix.md b/src/nix/upgrade-nix.md
new file mode 100644
index 000000000..4d27daad9
--- /dev/null
+++ b/src/nix/upgrade-nix.md
@@ -0,0 +1,28 @@
+R""(
+
+# Examples
+
+* Upgrade Nix to the latest stable version:
+
+ ```console
+ # nix upgrade-nix
+ ```
+
+* Upgrade Nix in a specific profile:
+
+ ```console
+ # nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile
+ ```
+
+# Description
+
+This command upgrades Nix to the latest version. By default, it
+locates the directory containing the `nix` binary in the `$PATH`
+environment variable. If that directory is a Nix profile, it will
+upgrade the `nix` package in that profile to the latest stable binary
+release.
+
+You cannot use this command to upgrade Nix in the system profile of a
+NixOS system (that is, if `nix` is found in `/run/current-system`).
+
+)""
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 6fedc8509..e01014440 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -18,16 +18,33 @@ struct CmdVerify : StorePathsCommand
CmdVerify()
{
- mkFlag(0, "no-contents", "do not verify the contents of each store path", &noContents);
- mkFlag(0, "no-trust", "do not verify whether each store path is trusted", &noTrust);
+ addFlag({
+ .longName = "no-contents",
+ .description = "Do not verify the contents of each store path.",
+ .handler = {&noContents, true},
+ });
+
+ addFlag({
+ .longName = "no-trust",
+ .description = "Do not verify whether each store path is trusted.",
+ .handler = {&noTrust, true},
+ });
+
addFlag({
.longName = "substituter",
.shortName = 's',
- .description = "use signatures from specified store",
+ .description = "Use signatures from the specified store.",
.labels = {"store-uri"},
.handler = {[&](std::string s) { substituterUris.push_back(s); }}
});
- mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
+
+ addFlag({
+ .longName = "sigs-needed",
+ .shortName = 'n',
+ .description = "Require that each path has at least *n* valid signatures.",
+ .labels = {"n"},
+ .handler = {&sigsNeeded}
+ });
}
std::string description() override
@@ -35,22 +52,13 @@ struct CmdVerify : StorePathsCommand
return "verify the integrity of store paths";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To verify the entire Nix store:",
- "nix verify --all"
- },
- Example{
- "To check whether each path in the closure of Firefox has at least 2 signatures:",
- "nix verify -r -n2 --no-contents $(type -p firefox)"
- },
- };
+ return
+ #include "verify.md"
+ ;
}
- Category category() override { return catSecondary; }
-
void run(ref<Store> store, StorePaths storePaths) override
{
std::vector<ref<Store>> substituters;
@@ -102,14 +110,10 @@ struct CmdVerify : StorePathsCommand
if (hash.first != info->narHash) {
corrupted++;
act2.result(resCorruptedPath, store->printStorePath(info->path));
- logError({
- .name = "Hash error - path modified",
- .hint = hintfmt(
- "path '%s' was modified! expected hash '%s', got '%s'",
- store->printStorePath(info->path),
- info->narHash.to_string(Base32, true),
- hash.first.to_string(Base32, true))
- });
+ printError("path '%s' was modified! expected hash '%s', got '%s'",
+ store->printStorePath(info->path),
+ info->narHash.to_string(Base32, true),
+ hash.first.to_string(Base32, true));
}
}
@@ -157,12 +161,7 @@ struct CmdVerify : StorePathsCommand
if (!good) {
untrusted++;
act2.result(resUntrustedPath, store->printStorePath(info->path));
- logError({
- .name = "Untrusted path",
- .hint = hintfmt("path '%s' is untrusted",
- store->printStorePath(info->path))
- });
-
+ printError("path '%s' is untrusted", store->printStorePath(info->path));
}
}
@@ -189,4 +188,4 @@ struct CmdVerify : StorePathsCommand
}
};
-static auto rCmdVerify = registerCommand<CmdVerify>("verify");
+static auto rCmdVerify = registerCommand2<CmdVerify>({"store", "verify"});
diff --git a/src/nix/verify.md b/src/nix/verify.md
new file mode 100644
index 000000000..1c43792e7
--- /dev/null
+++ b/src/nix/verify.md
@@ -0,0 +1,49 @@
+R""(
+
+# Examples
+
+* Verify the entire Nix store:
+
+ ```console
+ # nix store verify --all
+ ```
+
+* Check whether each path in the closure of Firefox has at least 2
+ signatures:
+
+ ```console
+ # nix store verify -r -n2 --no-contents $(type -p firefox)
+ ```
+
+* Verify a store path in the binary cache `https://cache.nixos.org/`:
+
+ ```console
+ # nix store verify --store https://cache.nixos.org/ \
+ /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
+ ```
+
+# Description
+
+This command verifies the integrity of the store paths *installables*,
+or, if `--all` is given, the entire Nix store. For each path, it
+checks that
+
+* its contents match the NAR hash recorded in the Nix database; and
+
+* it is *trusted*, that is, it is signed by at least one trusted
+ signing key, is content-addressed, or is built locally ("ultimately
+ trusted").
+
+# Exit status
+
+The exit status of this command is the sum of the following values:
+
+* **1** if any path is corrupted (i.e. its contents don't match the
+ recorded NAR hash).
+
+* **2** if any path is untrusted.
+
+* **4** if any path couldn't be verified for any other reason (such as
+ an I/O error).
+
+)""
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 63bf087e6..7a4ca5172 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -40,7 +40,7 @@ struct CmdWhyDepends : SourceExprCommand
addFlag({
.longName = "all",
.shortName = 'a',
- .description = "show all edges in the dependency graph leading from 'package' to 'dependency', rather than just a shortest path",
+ .description = "Show all edges in the dependency graph leading from *package* to *dependency*, rather than just a shortest path.",
.handler = {&all, true},
});
}
@@ -50,22 +50,11 @@ struct CmdWhyDepends : SourceExprCommand
return "show why a package has another package in its closure";
}
- Examples examples() override
+ std::string doc() override
{
- return {
- Example{
- "To show one path through the dependency graph leading from Hello to 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"
- },
- Example{
- "To show why Glibc depends on itself:",
- "nix why-depends nixpkgs#glibc nixpkgs#glibc"
- },
- };
+ return
+ #include "why-depends.md"
+ ;
}
Category category() override { return catSecondary; }
@@ -156,7 +145,7 @@ struct CmdWhyDepends : SourceExprCommand
auto pathS = store->printStorePath(node.path);
assert(node.dist != inf);
- logger->stdout("%s%s%s%s" ANSI_NORMAL,
+ logger->cout("%s%s%s%s" ANSI_NORMAL,
firstPad,
node.visited ? "\e[38;5;244m" : "",
firstPad != "" ? "→ " : "",
diff --git a/src/nix/why-depends.md b/src/nix/why-depends.md
new file mode 100644
index 000000000..dc13619e1
--- /dev/null
+++ b/src/nix/why-depends.md
@@ -0,0 +1,80 @@
+R""(
+
+# Examples
+
+* Show one path through the dependency graph leading from Hello to
+ Glibc:
+
+ ```console
+ # nix why-depends nixpkgs#hello nixpkgs#glibc
+ /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
+ └───bin/hello: …...................../nix/store/9l06v7fc38c1x3r2iydl15ksgz0ysb82-glibc-2.32/lib/ld-linux-x86-64.…
+ → /nix/store/9l06v7fc38c1x3r2iydl15ksgz0ysb82-glibc-2.32
+ ```
+
+* Show all files and paths in the dependency graph leading from
+ Thunderbird to libX11:
+
+ ```console
+ # nix why-depends --all nixpkgs#thunderbird nixpkgs#xorg.libX11
+ /nix/store/qfc8729nzpdln1h0hvi1ziclsl3m84sr-thunderbird-78.5.1
+ ├───lib/thunderbird/libxul.so: …6wrw-libxcb-1.14/lib:/nix/store/adzfjjh8w25vdr0xdx9x16ah4f5rqrw5-libX11-1.7.0/lib:/nix/store/ssf…
+ │ → /nix/store/adzfjjh8w25vdr0xdx9x16ah4f5rqrw5-libX11-1.7.0
+ ├───lib/thunderbird/libxul.so: …pxyc-libXt-1.2.0/lib:/nix/store/1qj29ipxl2fyi2b13l39hdircq17gnk0-libXdamage-1.1.5/lib:/nix/store…
+ │ → /nix/store/1qj29ipxl2fyi2b13l39hdircq17gnk0-libXdamage-1.1.5
+ │ ├───lib/libXdamage.so.1.1.0: …-libXfixes-5.0.3/lib:/nix/store/adzfjjh8w25vdr0xdx9x16ah4f5rqrw5-libX11-1.7.0/lib:/nix/store/9l0…
+ │ │ → /nix/store/adzfjjh8w25vdr0xdx9x16ah4f5rqrw5-libX11-1.7.0
+ …
+ ```
+
+* Show why Glibc depends on itself:
+
+ ```console
+ # nix why-depends nixpkgs#glibc nixpkgs#glibc
+ /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31
+ └───lib/ld-2.31.so: …che Do not use /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31/etc/ld.so.cache. --…
+ → /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31
+ ```
+
+* Show why Geeqie has a build-time dependency on `systemd`:
+
+ ```console
+ # nix why-depends --derivation nixpkgs#geeqie nixpkgs#systemd
+ /nix/store/drrpq2fqlrbj98bmazrnww7hm1in3wgj-geeqie-1.4.drv
+ └───/: …atch.drv",["out"]),("/nix/store/qzh8dyq3lfbk3i1acbp7x9wh3il2imiv-gtk+3-3.24.21.drv",["dev"]),("/…
+ → /nix/store/qzh8dyq3lfbk3i1acbp7x9wh3il2imiv-gtk+3-3.24.21.drv
+ └───/: …16.0.drv",["dev"]),("/nix/store/8kp79fyslf3z4m3dpvlh6w46iaadz5c2-cups-2.3.3.drv",["dev"]),("/nix…
+ → /nix/store/8kp79fyslf3z4m3dpvlh6w46iaadz5c2-cups-2.3.3.drv
+ └───/: ….3.1.drv",["out"]),("/nix/store/yd3ihapyi5wbz1kjacq9dbkaq5v5hqjg-systemd-246.4.drv",["dev"]),("/…
+ → /nix/store/yd3ihapyi5wbz1kjacq9dbkaq5v5hqjg-systemd-246.4.drv
+ ```
+
+# Description
+
+Nix automatically determines potential runtime dependencies between
+store paths by scanning for the *hash parts* of store paths. For
+instance, if there exists a store path
+`/nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31`, and a file
+inside another store path contains the string `9df65igw…`, then the
+latter store path *refers* to the former, and thus might need it at
+runtime. Nix always maintains the existence of the transitive closure
+of a store path under the references relationship; it is therefore not
+possible to install a store path without having all of its references
+present.
+
+Sometimes Nix packages end up with unexpected runtime dependencies;
+for instance, a reference to a compiler might accidentally end up in a
+binary, causing the former to be in the latter's closure. This kind of
+*closure size bloat* is undesirable.
+
+`nix why-depends` allows you to diagnose the cause of such issues. It
+shows why the store path *package* depends on the store path
+*dependency*, by showing a shortest sequence in the references graph
+from the former to the latter. Also, for each node along this path, it
+shows a file fragment containing a reference to the next store path in
+the sequence.
+
+To show why derivation *package* has a build-time rather than runtime
+dependency on derivation *dependency*, use `--derivation`.
+
+)""