aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-to-store.cc12
-rw-r--r--src/nix/app.cc33
-rw-r--r--src/nix/build.cc20
-rw-r--r--src/nix/build.md2
-rw-r--r--src/nix/bundle.cc13
-rw-r--r--src/nix/copy.md2
-rw-r--r--src/nix/daemon.cc330
-rw-r--r--src/nix/daemon.md30
-rw-r--r--src/nix/derivation-add.cc45
-rw-r--r--src/nix/derivation-add.md18
-rw-r--r--src/nix/derivation-show.cc (renamed from src/nix/show-derivation.cc)6
-rw-r--r--src/nix/derivation-show.md (renamed from src/nix/show-derivation.md)18
-rw-r--r--src/nix/derivation.cc25
-rw-r--r--src/nix/describe-stores.cc44
-rw-r--r--src/nix/develop.cc14
-rw-r--r--src/nix/doctor.cc21
-rw-r--r--src/nix/edit.cc6
-rw-r--r--src/nix/eval.cc12
-rw-r--r--src/nix/eval.md2
-rw-r--r--src/nix/flake-check.md2
-rw-r--r--src/nix/flake.cc199
-rw-r--r--src/nix/flake.md66
-rw-r--r--src/nix/fmt.cc6
-rw-r--r--src/nix/help-stores.md46
-rw-r--r--src/nix/local.mk6
-rw-r--r--src/nix/log.cc20
-rw-r--r--src/nix/main.cc151
-rw-r--r--src/nix/nar-ls.md4
-rw-r--r--src/nix/nix.md26
-rw-r--r--src/nix/path-info.md8
-rw-r--r--src/nix/ping-store.cc5
-rw-r--r--src/nix/prefetch.cc19
-rw-r--r--src/nix/profile-list.md46
-rw-r--r--src/nix/profile.cc157
-rw-r--r--src/nix/profile.md103
-rw-r--r--src/nix/realisation.cc2
-rw-r--r--src/nix/registry.cc2
-rw-r--r--src/nix/repl.cc23
-rw-r--r--src/nix/run.cc6
-rw-r--r--src/nix/run.hh1
-rw-r--r--src/nix/search.cc7
-rw-r--r--src/nix/search.md4
-rw-r--r--src/nix/shell.md8
-rw-r--r--src/nix/store-ls.md4
-rw-r--r--src/nix/upgrade-nix.cc13
-rw-r--r--src/nix/upgrade-nix.md2
-rw-r--r--src/nix/verify.md2
-rw-r--r--src/nix/why-depends.cc10
48 files changed, 1104 insertions, 497 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index 5168413d2..39e5cc99d 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -42,14 +42,16 @@ struct CmdAddToStore : MixDryRun, StoreCommand
}
ValidPathInfo info {
- store->makeFixedOutputPath(ingestionMethod, hash, *namePart),
+ *store,
+ std::move(*namePart),
+ FixedOutputInfo {
+ .method = std::move(ingestionMethod),
+ .hash = std::move(hash),
+ .references = {},
+ },
narHash,
};
info.narSize = sink.s.size();
- info.ca = std::optional { FixedOutputHash {
- .method = ingestionMethod,
- .hash = hash,
- } };
if (!dryRun) {
auto source = StringSource(sink.s);
diff --git a/src/nix/app.cc b/src/nix/app.cc
index bfd75e278..34fac9935 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -1,11 +1,13 @@
#include "installables.hh"
#include "installable-derived-path.hh"
+#include "installable-value.hh"
#include "store-api.hh"
#include "eval-inline.hh"
#include "eval-cache.hh"
#include "names.hh"
#include "command.hh"
#include "derivations.hh"
+#include "downstream-placeholder.hh"
namespace nix {
@@ -18,13 +20,22 @@ StringPairs resolveRewrites(
const std::vector<BuiltPathWithResult> & dependencies)
{
StringPairs res;
- for (auto & dep : dependencies)
- if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path))
- for (auto & [ outputName, outputPath ] : drvDep->outputs)
- res.emplace(
- downstreamPlaceholder(store, drvDep->drvPath, outputName),
- store.printStorePath(outputPath)
- );
+ if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
+ for (auto & dep : dependencies) {
+ if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path)) {
+ for (auto & [ outputName, outputPath ] : drvDep->outputs) {
+ res.emplace(
+ DownstreamPlaceholder::fromSingleDerivedPathBuilt(
+ SingleDerivedPath::Built {
+ .drvPath = make_ref<SingleDerivedPath>(drvDep->drvPath->discardOutputPath()),
+ .output = outputName,
+ }).render(),
+ store.printStorePath(outputPath)
+ );
+ }
+ }
+ }
+ }
return res;
}
@@ -40,7 +51,7 @@ std::string resolveString(
return rewriteStrings(toResolve, rewrites);
}
-UnresolvedApp Installable::toApp(EvalState & state)
+UnresolvedApp InstallableValue::toApp(EvalState & state)
{
auto cursor = getCursor(state);
auto attrPath = cursor->getAttrPath();
@@ -62,7 +73,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
[&](const NixStringContextElem::DrvDeep & d) -> DerivedPath {
/* We want all outputs of the drv */
return DerivedPath::Built {
- .drvPath = d.drvPath,
+ .drvPath = makeConstantStorePathRef(d.drvPath),
.outputs = OutputsSpec::All {},
};
},
@@ -77,7 +88,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
.path = o.path,
};
},
- }, c.raw()));
+ }, c.raw));
}
return UnresolvedApp{App {
@@ -103,7 +114,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto program = outPath + "/bin/" + mainProgram;
return UnresolvedApp { App {
.context = { DerivedPath::Built {
- .drvPath = drvPath,
+ .drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::Names { outputName },
} },
.program = program,
diff --git a/src/nix/build.cc b/src/nix/build.cc
index bca20e97c..479100186 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -1,4 +1,3 @@
-#include "eval.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
@@ -10,26 +9,28 @@
using namespace nix;
-nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref<Store> store)
+static nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, Store & store)
{
auto res = nlohmann::json::array();
for (auto & t : paths) {
- std::visit([&res, store](const auto & t) {
+ std::visit([&](const auto & t) {
res.push_back(t.toJSON(store));
}, t.raw());
}
return res;
}
-nlohmann::json builtPathsWithResultToJSON(const std::vector<BuiltPathWithResult> & buildables, ref<Store> store)
+static nlohmann::json builtPathsWithResultToJSON(const std::vector<BuiltPathWithResult> & buildables, const Store & store)
{
auto res = nlohmann::json::array();
for (auto & b : buildables) {
std::visit([&](const auto & t) {
auto j = t.toJSON(store);
if (b.result) {
- j["startTime"] = b.result->startTime;
- j["stopTime"] = b.result->stopTime;
+ if (b.result->startTime)
+ j["startTime"] = b.result->startTime;
+ if (b.result->stopTime)
+ j["stopTime"] = b.result->stopTime;
if (b.result->cpuUser)
j["cpuUser"] = ((double) b.result->cpuUser->count()) / 1000000;
if (b.result->cpuSystem)
@@ -124,7 +125,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
printMissing(store, pathsToBuild, lvlError);
if (json)
- logger->cout("%s", derivedPathsToJSON(pathsToBuild, store).dump());
+ logger->cout("%s", derivedPathsToJSON(pathsToBuild, *store).dump());
return;
}
@@ -132,9 +133,10 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
auto buildables = Installable::build(
getEvalStore(), store,
Realise::Outputs,
- installables, buildMode);
+ installables,
+ repair ? bmRepair : buildMode);
- if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, store).dump());
+ if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, *store).dump());
if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
diff --git a/src/nix/build.md b/src/nix/build.md
index ee414dc86..0fbb39cc3 100644
--- a/src/nix/build.md
+++ b/src/nix/build.md
@@ -44,7 +44,7 @@ R""(
`release.nix`:
```console
- # nix build -f release.nix build.x86_64-linux
+ # nix build --file release.nix build.x86_64-linux
```
* Build a NixOS system configuration from a flake, and make a profile
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 973bbd423..fbc83b08e 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -1,14 +1,15 @@
-#include "command.hh"
#include "installable-flake.hh"
+#include "command-installable-value.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
#include "local-fs-store.hh"
#include "fs-accessor.hh"
+#include "eval-inline.hh"
using namespace nix;
-struct CmdBundle : InstallableCommand
+struct CmdBundle : InstallableValueCommand
{
std::string bundler = "github:NixOS/bundlers";
std::optional<Path> outLink;
@@ -70,7 +71,7 @@ struct CmdBundle : InstallableCommand
return res;
}
- void run(ref<Store> store, ref<Installable> installable) override
+ void run(ref<Store> store, ref<InstallableValue> installable) override
{
auto evalState = getEvalState();
@@ -79,7 +80,7 @@ struct CmdBundle : InstallableCommand
auto [bundlerFlakeRef, bundlerName, extendedOutputsSpec] = parseFlakeRefWithFragmentAndExtendedOutputsSpec(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false };
InstallableFlake bundler{this,
- evalState, std::move(bundlerFlakeRef), bundlerName, extendedOutputsSpec,
+ evalState, std::move(bundlerFlakeRef), bundlerName, std::move(extendedOutputsSpec),
{"bundlers." + settings.thisSystem.get() + ".default",
"defaultBundler." + settings.thisSystem.get()
},
@@ -97,7 +98,7 @@ struct CmdBundle : InstallableCommand
if (!attr1)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
- PathSet context2;
+ NixStringContext context2;
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto attr2 = vRes->attrs->get(evalState->sOutPath);
@@ -108,7 +109,7 @@ struct CmdBundle : InstallableCommand
store->buildPaths({
DerivedPath::Built {
- .drvPath = drvPath,
+ .drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All { },
},
});
diff --git a/src/nix/copy.md b/src/nix/copy.md
index 25e0ddadc..199006436 100644
--- a/src/nix/copy.md
+++ b/src/nix/copy.md
@@ -15,7 +15,7 @@ R""(
SSH:
```console
- # nix copy -s --to ssh://server /run/current-system
+ # nix copy --substitute-on-destination --to ssh://server /run/current-system
```
The `-s` flag causes the remote machine to try to substitute missing
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
index 7e4a7ba86..af428018a 100644
--- a/src/nix/daemon.cc
+++ b/src/nix/daemon.cc
@@ -1,7 +1,10 @@
+///@file
+
#include "command.hh"
#include "shared.hh"
#include "local-store.hh"
#include "remote-store.hh"
+#include "remote-store-connection.hh"
#include "util.hh"
#include "serialise.hh"
#include "archive.hh"
@@ -22,6 +25,7 @@
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/select.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
@@ -34,36 +38,52 @@
using namespace nix;
using namespace nix::daemon;
+/**
+ * Settings related to authenticating clients for the Nix daemon.
+ *
+ * For pipes we have little good information about the client side, but
+ * for Unix domain sockets we do. So currently these options implemented
+ * mandatory access control based on user names and group names (looked
+ * up and translated to UID/GIDs in the CLI process that runs the code
+ * in this file).
+ *
+ * No code outside of this file knows about these settings (this is not
+ * exposed in a header); all authentication and authorization happens in
+ * `daemon.cc`.
+ */
struct AuthorizationSettings : Config {
Setting<Strings> trustedUsers{
this, {"root"}, "trusted-users",
R"(
- A list of names of users (separated by whitespace) that have
- additional rights when connecting to the Nix daemon, such as the
- ability to specify additional binary caches, or to import unsigned
- NARs. You can also specify groups by prefixing them with `@`; for
- instance, `@wheel` means all users in the `wheel` group. The default
- is `root`.
+ A list of user names, separated by whitespace.
+ These users will have additional rights when connecting to the Nix daemon, such as the ability to specify additional [substituters](#conf-substituters), or to import unsigned [NARs](@docroot@/glossary.md#gloss-nar).
+
+ You can also specify groups by prefixing names with `@`.
+ For instance, `@wheel` means all users in the `wheel` group.
> **Warning**
>
- > Adding a user to `trusted-users` is essentially equivalent to
- > giving that user root access to the system. For example, the user
- > can set `sandbox-paths` and thereby obtain read access to
- > directories that are otherwise inacessible to them.
+ > Adding a user to `trusted-users` is essentially equivalent to giving that user root access to the system.
+ > For example, the user can access or replace store path contents that are critical for system security.
)"};
- /* ?Who we trust to use the daemon in safe ways */
+ /**
+ * Who we trust to use the daemon in safe ways
+ */
Setting<Strings> allowedUsers{
this, {"*"}, "allowed-users",
R"(
- A list of names of users (separated by whitespace) that are allowed
- to connect to the Nix daemon. As with the `trusted-users` option,
- you can specify groups by prefixing them with `@`. Also, you can
- allow all users by specifying `*`. The default is `*`.
+ A list user names, separated by whitespace.
+ These users are allowed to connect to the Nix daemon.
+
+ You can specify groups by prefixing names with `@`.
+ For instance, `@wheel` means all users in the `wheel` group.
+ Also, you can allow all users by specifying `*`.
- Note that trusted users are always allowed to connect.
+ > **Note**
+ >
+ > Trusted users (set in [`trusted-users`](#conf-trusted-users)) can always connect to the Nix daemon.
)"};
};
@@ -112,8 +132,36 @@ static void setSigChldAction(bool autoReap)
throw SysError("setting SIGCHLD handler");
}
+/**
+ * @return Is the given user a member of this group?
+ *
+ * @param user User specified by username.
+ *
+ * @param group Group the user might be a member of.
+ */
+static bool matchUser(std::string_view user, const struct group & gr)
+{
+ for (char * * mem = gr.gr_mem; *mem; mem++)
+ if (user == std::string_view(*mem)) return true;
+ return false;
+}
+
-bool matchUser(const std::string & user, const std::string & group, const Strings & users)
+/**
+ * Does the given user (specified by user name and primary group name)
+ * match the given user/group whitelist?
+ *
+ * If the list allows all users: Yes.
+ *
+ * If the username is in the set: Yes.
+ *
+ * If the groupname is in the set: Yes.
+ *
+ * If the user is in another group which is in the set: yes.
+ *
+ * Otherwise: No.
+ */
+static bool matchUser(const std::string & user, const std::string & group, const Strings & users)
{
if (find(users.begin(), users.end(), "*") != users.end())
return true;
@@ -126,8 +174,7 @@ bool matchUser(const std::string & user, const std::string & group, const String
if (group == i.substr(1)) return true;
struct group * gr = getgrnam(i.c_str() + 1);
if (!gr) continue;
- for (char * * mem = gr->gr_mem; *mem; mem++)
- if (user == std::string(*mem)) return true;
+ if (matchUser(user, *gr)) return true;
}
return false;
@@ -145,7 +192,9 @@ struct PeerInfo
};
-// Get the identity of the caller, if possible.
+/**
+ * Get the identity of the caller, if possible.
+ */
static PeerInfo getPeerInfo(int remote)
{
PeerInfo peer = { false, 0, false, 0, false, 0 };
@@ -179,6 +228,9 @@ static PeerInfo getPeerInfo(int remote)
#define SD_LISTEN_FDS_START 3
+/**
+ * Open a store without a path info cache.
+ */
static ref<Store> openUncachedStore()
{
Store::Params params; // FIXME: get params from somewhere
@@ -187,8 +239,49 @@ static ref<Store> openUncachedStore()
return openStore(settings.storeUri, params);
}
+/**
+ * Authenticate a potential client
+ *
+ * @param peer Information about other end of the connection, the client which
+ * wants to communicate with us.
+ *
+ * @return A pair of a `TrustedFlag`, whether the potential client is trusted,
+ * and the name of the user (useful for printing messages).
+ *
+ * If the potential client is not allowed to talk to us, we throw an `Error`.
+ */
+static std::pair<TrustedFlag, std::string> authPeer(const PeerInfo & peer)
+{
+ TrustedFlag trusted = NotTrusted;
+
+ struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
+ std::string user = pw ? pw->pw_name : std::to_string(peer.uid);
+
+ struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
+ std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
+
+ const Strings & trustedUsers = authorizationSettings.trustedUsers;
+ const Strings & allowedUsers = authorizationSettings.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);
+
+ return { trusted, std::move(user) };
+}
+
-static void daemonLoop()
+/**
+ * Run a server. The loop opens a socket and accepts new connections from that
+ * socket.
+ *
+ * @param forceTrustClientOpt If present, force trusting or not trusted
+ * the client. Otherwise, decide based on the authentication settings
+ * and user credentials (from the unix domain socket).
+ */
+static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
{
if (chdir("/") == -1)
throw SysError("cannot change current directory");
@@ -231,23 +324,18 @@ static void daemonLoop()
closeOnExec(remote.get());
- TrustedFlag trusted = NotTrusted;
- PeerInfo peer = getPeerInfo(remote.get());
+ PeerInfo peer { .pidKnown = false };
+ TrustedFlag trusted;
+ std::string user;
- struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
- std::string user = pw ? pw->pw_name : std::to_string(peer.uid);
-
- struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
- std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
-
- Strings trustedUsers = authorizationSettings.trustedUsers;
- Strings allowedUsers = authorizationSettings.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);
+ if (forceTrustClientOpt)
+ trusted = *forceTrustClientOpt;
+ else {
+ peer = getPeerInfo(remote.get());
+ auto [_trusted, _user] = authPeer(peer);
+ trusted = _trusted;
+ user = _user;
+ };
printInfo((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""),
peer.pidKnown ? std::to_string(peer.pid) : "<unknown>",
@@ -294,53 +382,91 @@ static void daemonLoop()
}
}
-static void runDaemon(bool stdio)
+/**
+ * Forward a standard IO connection to the given remote store.
+ *
+ * We just act as a middleman blindly ferry output between the standard
+ * input/output and the remote store connection, not processing anything.
+ *
+ * Loops until standard input disconnects, or an error is encountered.
+ */
+static void forwardStdioConnection(RemoteStore & store) {
+ 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;
+ }
+ }
+}
+
+/**
+ * Process a client connecting to us via standard input/output
+ *
+ * Unlike `forwardStdioConnection()` we do process commands ourselves in
+ * this case, not delegating to another daemon.
+ *
+ * @param trustClient Whether to trust the client. Forwarded directly to
+ * `processConnection()`.
+ */
+static void processStdioConnection(ref<Store> store, TrustedFlag trustClient)
+{
+ FdSource from(STDIN_FILENO);
+ FdSink to(STDOUT_FILENO);
+ processConnection(store, from, to, trustClient, NotRecursive);
+}
+
+/**
+ * Entry point shared between the new CLI `nix daemon` and old CLI
+ * `nix-daemon`.
+ *
+ * @param forceTrustClientOpt See `daemonLoop()` and the parameter with
+ * the same name over there for details.
+ */
+static void runDaemon(bool stdio, std::optional<TrustedFlag> forceTrustClientOpt)
{
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);
- }
+ auto store = openUncachedStore();
+
+ // If --force-untrusted is passed, we cannot forward the connection and
+ // must process it ourselves (before delegating to the next store) to
+ // force untrusting the client.
+ if (auto remoteStore = store.dynamic_pointer_cast<RemoteStore>(); remoteStore && (!forceTrustClientOpt || *forceTrustClientOpt != NotTrusted))
+ forwardStdioConnection(*remoteStore);
+ else
+ // `Trusted` is passed in the auto (no override case) because we
+ // cannot see who is on the other side of a plain pipe. Limiting
+ // access to those is explicitly not `nix-daemon`'s responsibility.
+ processStdioConnection(store, forceTrustClientOpt.value_or(Trusted));
} else
- daemonLoop();
+ daemonLoop(forceTrustClientOpt);
}
static int main_nix_daemon(int argc, char * * argv)
{
{
auto stdio = false;
+ std::optional<TrustedFlag> isTrustedOpt = std::nullopt;
parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
if (*arg == "--daemon")
@@ -351,11 +477,20 @@ static int main_nix_daemon(int argc, char * * argv)
printVersion("nix-daemon");
else if (*arg == "--stdio")
stdio = true;
- else return false;
+ else if (*arg == "--force-trusted") {
+ experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
+ isTrustedOpt = Trusted;
+ } else if (*arg == "--force-untrusted") {
+ experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
+ isTrustedOpt = NotTrusted;
+ } else if (*arg == "--default-trust") {
+ experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
+ isTrustedOpt = std::nullopt;
+ } else return false;
return true;
});
- runDaemon(stdio);
+ runDaemon(stdio, isTrustedOpt);
return 0;
}
@@ -365,6 +500,45 @@ static RegisterLegacyCommand r_nix_daemon("nix-daemon", main_nix_daemon);
struct CmdDaemon : StoreCommand
{
+ bool stdio = false;
+ std::optional<TrustedFlag> isTrustedOpt = std::nullopt;
+
+ CmdDaemon()
+ {
+ addFlag({
+ .longName = "stdio",
+ .description = "Attach to standard I/O, instead of trying to bind to a UNIX socket.",
+ .handler = {&stdio, true},
+ });
+
+ addFlag({
+ .longName = "force-trusted",
+ .description = "Force the daemon to trust connecting clients.",
+ .handler = {[&]() {
+ isTrustedOpt = Trusted;
+ }},
+ .experimentalFeature = Xp::DaemonTrustOverride,
+ });
+
+ addFlag({
+ .longName = "force-untrusted",
+ .description = "Force the daemon to not trust connecting clients. The connection will be processed by the receiving daemon before forwarding commands.",
+ .handler = {[&]() {
+ isTrustedOpt = NotTrusted;
+ }},
+ .experimentalFeature = Xp::DaemonTrustOverride,
+ });
+
+ addFlag({
+ .longName = "default-trust",
+ .description = "Use Nix's default trust.",
+ .handler = {[&]() {
+ isTrustedOpt = std::nullopt;
+ }},
+ .experimentalFeature = Xp::DaemonTrustOverride,
+ });
+ }
+
std::string description() override
{
return "daemon to perform store operations on behalf of non-root clients";
@@ -381,7 +555,7 @@ struct CmdDaemon : StoreCommand
void run(ref<Store> store) override
{
- runDaemon(false);
+ runDaemon(stdio, isTrustedOpt);
}
};
diff --git a/src/nix/daemon.md b/src/nix/daemon.md
index d5cdadf08..b1ea850ed 100644
--- a/src/nix/daemon.md
+++ b/src/nix/daemon.md
@@ -1,20 +1,44 @@
R""(
-# Example
+# Examples
-* Run the daemon in the foreground:
+* Run the daemon:
```console
# nix daemon
```
+* Run the daemon and listen on standard I/O instead of binding to a UNIX socket:
+
+ ```console
+ # nix daemon --stdio
+ ```
+
+* Run the daemon and force all connections to be trusted:
+
+ ```console
+ # nix daemon --force-trusted
+ ```
+
+* Run the daemon and force all connections to be untrusted:
+
+ ```console
+ # nix daemon --force-untrusted
+ ```
+
+* Run the daemon, listen on standard I/O, and force all connections to use Nix's default trust:
+
+ ```console
+ # nix daemon --stdio --default-trust
+ ```
+
# Description
This command runs the Nix daemon, which is a required component in
multi-user Nix installations. It runs build tasks 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`.
+management framework such as `systemd` on Linux, or `launchctl` on Darwin.
Note that this daemon does not fork into the background.
diff --git a/src/nix/derivation-add.cc b/src/nix/derivation-add.cc
new file mode 100644
index 000000000..4d91d4538
--- /dev/null
+++ b/src/nix/derivation-add.cc
@@ -0,0 +1,45 @@
+// FIXME: rename to 'nix plan add' or 'nix derivation add'?
+
+#include "command.hh"
+#include "common-args.hh"
+#include "store-api.hh"
+#include "archive.hh"
+#include "derivations.hh"
+#include <nlohmann/json.hpp>
+
+using namespace nix;
+using json = nlohmann::json;
+
+struct CmdAddDerivation : MixDryRun, StoreCommand
+{
+ std::string description() override
+ {
+ return "Add a store derivation";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "derivation-add.md"
+ ;
+ }
+
+ Category category() override { return catUtility; }
+
+ void run(ref<Store> store) override
+ {
+ auto json = nlohmann::json::parse(drainFD(STDIN_FILENO));
+
+ auto drv = Derivation::fromJSON(*store, json);
+
+ auto drvPath = writeDerivation(*store, drv, NoRepair, /* read only */ dryRun);
+
+ drv.checkInvariants(*store, drvPath);
+
+ writeDerivation(*store, drv, NoRepair, dryRun);
+
+ logger->cout("%s", store->printStorePath(drvPath));
+ }
+};
+
+static auto rCmdAddDerivation = registerCommand2<CmdAddDerivation>({"derivation", "add"});
diff --git a/src/nix/derivation-add.md b/src/nix/derivation-add.md
new file mode 100644
index 000000000..f116681ab
--- /dev/null
+++ b/src/nix/derivation-add.md
@@ -0,0 +1,18 @@
+R""(
+
+# Description
+
+This command reads from standard input a JSON representation of a
+[store derivation] to which an [*installable*](./nix.md#installables) evaluates.
+
+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.
+
+[store derivation]: ../../glossary.md#gloss-store-derivation
+
+The JSON format is documented under the [`derivation show`] command.
+
+[`derivation show`]: ./nix3-derivation-show.md
+
+)""
diff --git a/src/nix/show-derivation.cc b/src/nix/derivation-show.cc
index 4a406ae08..bf637246d 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/derivation-show.cc
@@ -1,5 +1,5 @@
// FIXME: integrate this with nix path-info?
-// FIXME: rename to 'nix store show-derivation' or 'nix debug show-derivation'?
+// FIXME: rename to 'nix store derivation show' or 'nix debug derivation show'?
#include "command.hh"
#include "common-args.hh"
@@ -33,7 +33,7 @@ struct CmdShowDerivation : InstallablesCommand
std::string doc() override
{
return
- #include "show-derivation.md"
+ #include "derivation-show.md"
;
}
@@ -61,4 +61,4 @@ struct CmdShowDerivation : InstallablesCommand
}
};
-static auto rCmdShowDerivation = registerCommand<CmdShowDerivation>("show-derivation");
+static auto rCmdShowDerivation = registerCommand2<CmdShowDerivation>({"derivation", "show"});
diff --git a/src/nix/show-derivation.md b/src/nix/derivation-show.md
index 1d37c6f5a..1296e2885 100644
--- a/src/nix/show-derivation.md
+++ b/src/nix/derivation-show.md
@@ -8,7 +8,7 @@ R""(
[store derivation]: ../../glossary.md#gloss-store-derivation
```console
- # nix show-derivation nixpkgs#hello
+ # nix derivation show nixpkgs#hello
{
"/nix/store/s6rn4jz1sin56rf4qj5b5v8jxjm32hlk-hello-2.10.drv": {
@@ -20,14 +20,14 @@ R""(
NixOS system:
```console
- # nix show-derivation -r /run/current-system
+ # nix derivation show -r /run/current-system
```
* Print all files fetched using `fetchurl` by Firefox's dependency
graph:
```console
- # nix show-derivation -r nixpkgs#firefox \
+ # nix derivation show -r nixpkgs#firefox \
| jq -r '.[] | select(.outputs.out.hash and .env.urls) | .env.urls' \
| uniq | sort
```
@@ -39,10 +39,11 @@ R""(
# Description
This command prints on standard output a JSON representation of the
-[store derivation]s to which [*installables*](./nix.md#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.
+[store derivation]s to which [*installables*](./nix.md#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.
@@ -51,6 +52,9 @@ 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:
+* `name`: The name of the derivation. This is used when calculating the
+ store paths of the derivation's outputs.
+
* `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
diff --git a/src/nix/derivation.cc b/src/nix/derivation.cc
new file mode 100644
index 000000000..cd3975a4f
--- /dev/null
+++ b/src/nix/derivation.cc
@@ -0,0 +1,25 @@
+#include "command.hh"
+
+using namespace nix;
+
+struct CmdDerivation : virtual NixMultiCommand
+{
+ CmdDerivation() : MultiCommand(RegisterCommand::getCommandsFor({"derivation"}))
+ { }
+
+ std::string description() override
+ {
+ return "Work with derivations, Nix's notion of a build plan.";
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix derivation' requires a sub-command.");
+ command->second->run();
+ }
+};
+
+static auto rCmdDerivation = registerCommand<CmdDerivation>("derivation");
diff --git a/src/nix/describe-stores.cc b/src/nix/describe-stores.cc
deleted file mode 100644
index eafcedd1f..000000000
--- a/src/nix/describe-stores.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-#include "command.hh"
-#include "common-args.hh"
-#include "shared.hh"
-#include "store-api.hh"
-
-#include <nlohmann/json.hpp>
-
-using namespace nix;
-
-struct CmdDescribeStores : Command, MixJSON
-{
- std::string description() override
- {
- return "show registered store types and their available options";
- }
-
- Category category() override { return catUtility; }
-
- void run() override
- {
- auto res = nlohmann::json::object();
- for (auto & implem : *Implementations::registered) {
- auto storeConfig = implem.getConfig();
- auto storeName = storeConfig->name();
- res[storeName] = storeConfig->toJSON();
- }
- if (json) {
- logger->cout("%s", res);
- } else {
- for (auto & [storeName, storeConfig] : res.items()) {
- std::cout << "## " << storeName << std::endl << std::endl;
- for (auto & [optionName, optionDesc] : storeConfig.items()) {
- std::cout << "### " << optionName << std::endl << std::endl;
- std::cout << optionDesc["description"].get<std::string>() << std::endl;
- std::cout << "default: " << optionDesc["defaultValue"] << std::endl <<std::endl;
- if (!optionDesc["aliases"].empty())
- std::cout << "aliases: " << optionDesc["aliases"] << std::endl << std::endl;
- }
- }
- }
- }
-};
-
-static auto rDescribeStore = registerCommand<CmdDescribeStores>("describe-stores");
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index f06ade008..c01ef1a2a 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -1,6 +1,6 @@
#include "eval.hh"
-#include "command.hh"
#include "installable-flake.hh"
+#include "command-installable-value.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
@@ -208,7 +208,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
drv.name += "-env";
drv.env.emplace("name", drv.name);
drv.inputSrcs.insert(std::move(getEnvShPath));
- if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
+ if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) {
output.second = DerivationOutput::Deferred {},
drv.env[output.first] = hashPlaceholder(output.first);
@@ -235,7 +235,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
/* Build the derivation. */
store->buildPaths(
{ DerivedPath::Built {
- .drvPath = shellDrvPath,
+ .drvPath = makeConstantStorePathRef(shellDrvPath),
.outputs = OutputsSpec::All { },
}},
bmNormal, evalStore);
@@ -538,12 +538,16 @@ struct CmdDevelop : Common, MixEnvironment
nixpkgsLockFlags.inputOverrides = {};
nixpkgsLockFlags.inputUpdates = {};
+ auto nixpkgs = defaultNixpkgsFlakeRef();
+ if (auto * i = dynamic_cast<const InstallableFlake *>(&*installable))
+ nixpkgs = i->nixpkgsFlakeRef();
+
auto bashInstallable = make_ref<InstallableFlake>(
this,
state,
- installable->nixpkgsFlakeRef(),
+ std::move(nixpkgs),
"bashInteractive",
- DefaultOutputs(),
+ ExtendedOutputsSpec::Default(),
Strings{},
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
nixpkgsLockFlags);
diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc
index 7da4549a1..1aa6831d3 100644
--- a/src/nix/doctor.cc
+++ b/src/nix/doctor.cc
@@ -33,12 +33,24 @@ bool checkFail(const std::string & msg) {
return false;
}
+void checkInfo(const std::string & msg) {
+ notice(ANSI_BLUE "[INFO] " ANSI_NORMAL + msg);
+}
+
}
struct CmdDoctor : StoreCommand
{
bool success = true;
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::string description() override
{
return "check your system for potential problems and print a PASS or FAIL for each check";
@@ -55,6 +67,7 @@ struct CmdDoctor : StoreCommand
success &= checkProfileRoots(store);
}
success &= checkStoreProtocol(store->getProtocol());
+ checkTrustedUser(store);
if (!success)
throw Exit(2);
@@ -130,6 +143,14 @@ struct CmdDoctor : StoreCommand
return checkPass("Client protocol matches store protocol.");
}
+
+ void checkTrustedUser(ref<Store> store)
+ {
+ std::string_view trusted = store->isTrustedClient()
+ ? "trusted"
+ : "not trusted";
+ checkInfo(fmt("You are %s by store uri: %s", trusted, store->getUri()));
+ }
};
static auto rCmdDoctor = registerCommand<CmdDoctor>("doctor");
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index c46c1c23c..66629fab0 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -1,4 +1,4 @@
-#include "command.hh"
+#include "command-installable-value.hh"
#include "shared.hh"
#include "eval.hh"
#include "attr-path.hh"
@@ -9,7 +9,7 @@
using namespace nix;
-struct CmdEdit : InstallableCommand
+struct CmdEdit : InstallableValueCommand
{
std::string description() override
{
@@ -25,7 +25,7 @@ struct CmdEdit : InstallableCommand
Category category() override { return catSecondary; }
- void run(ref<Store> store, ref<Installable> installable) override
+ void run(ref<Store> store, ref<InstallableValue> installable) override
{
auto state = getEvalState();
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 6c2b60427..d880bef0a 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -1,4 +1,4 @@
-#include "command.hh"
+#include "command-installable-value.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
@@ -11,13 +11,13 @@
using namespace nix;
-struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
+struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
{
bool raw = false;
std::optional<std::string> apply;
std::optional<Path> writeTo;
- CmdEval() : InstallableCommand()
+ CmdEval() : InstallableValueCommand()
{
addFlag({
.longName = "raw",
@@ -54,7 +54,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
Category category() override { return catSecondary; }
- void run(ref<Store> store, ref<Installable> installable) override
+ void run(ref<Store> store, ref<InstallableValue> installable) override
{
if (raw && json)
throw UsageError("--raw and --json are mutually exclusive");
@@ -62,11 +62,11 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
auto state = getEvalState();
auto [v, pos] = installable->toValue(*state);
- PathSet context;
+ NixStringContext context;
if (apply) {
auto vApply = state->allocValue();
- state->eval(state->parseExprFromString(*apply, absPath(".")), *vApply);
+ state->eval(state->parseExprFromString(*apply, state->rootPath(CanonPath::fromCwd())), *vApply);
auto vRes = state->allocValue();
state->callFunction(*vApply, *v, *vRes, noPos);
v = vRes;
diff --git a/src/nix/eval.md b/src/nix/eval.md
index 3b510737a..48d5aa597 100644
--- a/src/nix/eval.md
+++ b/src/nix/eval.md
@@ -18,7 +18,7 @@ R""(
* Evaluate a Nix expression from a file:
```console
- # nix eval -f ./my-nixpkgs hello.name
+ # nix eval --file ./my-nixpkgs hello.name
```
* Get the current version of the `nixpkgs` flake:
diff --git a/src/nix/flake-check.md b/src/nix/flake-check.md
index 07031c909..c8307f8d8 100644
--- a/src/nix/flake-check.md
+++ b/src/nix/flake-check.md
@@ -68,6 +68,6 @@ The following flake output attributes must be
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`.
+`legacyPackages`.*system* output is evaluated like `nix-env --query --available `.
)""
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 0a6616396..87dd4da1b 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -4,6 +4,7 @@
#include "shared.hh"
#include "eval.hh"
#include "eval-inline.hh"
+#include "eval-settings.hh"
#include "flake/flake.hh"
#include "get-drvs.hh"
#include "store-api.hh"
@@ -179,6 +180,8 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
j["locked"] = fetchers::attrsToJSON(flake.lockedRef.toAttrs());
if (auto rev = flake.lockedRef.input.getRev())
j["revision"] = rev->to_string(Base16, false);
+ if (auto dirtyRev = fetchers::maybeGetStrAttr(flake.lockedRef.toAttrs(), "dirtyRev"))
+ j["dirtyRevision"] = *dirtyRev;
if (auto revCount = flake.lockedRef.input.getRevCount())
j["revCount"] = *revCount;
if (auto lastModified = flake.lockedRef.input.getLastModified())
@@ -204,6 +207,10 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
logger->cout(
ANSI_BOLD "Revision:" ANSI_NORMAL " %s",
rev->to_string(Base16, false));
+ if (auto dirtyRev = fetchers::maybeGetStrAttr(flake.lockedRef.toAttrs(), "dirtyRev"))
+ logger->cout(
+ ANSI_BOLD "Revision:" ANSI_NORMAL " %s",
+ *dirtyRev);
if (auto revCount = flake.lockedRef.input.getRevCount())
logger->cout(
ANSI_BOLD "Revisions:" ANSI_NORMAL " %s",
@@ -259,6 +266,7 @@ struct CmdFlakeInfo : CmdFlakeMetadata
struct CmdFlakeCheck : FlakeCommand
{
bool build = true;
+ bool checkAllSystems = false;
CmdFlakeCheck()
{
@@ -267,6 +275,11 @@ struct CmdFlakeCheck : FlakeCommand
.description = "Do not build checks.",
.handler = {&build, false}
});
+ addFlag({
+ .longName = "all-systems",
+ .description = "Check the outputs for all systems.",
+ .handler = {&checkAllSystems, true}
+ });
}
std::string description() override
@@ -292,6 +305,7 @@ struct CmdFlakeCheck : FlakeCommand
lockFlags.applyNixConfig = true;
auto flake = lockFlake();
+ auto localSystem = std::string(settings.thisSystem.get());
bool hasErrors = false;
auto reportError = [&](const Error & e) {
@@ -307,6 +321,8 @@ struct CmdFlakeCheck : FlakeCommand
}
};
+ std::set<std::string> omittedSystems;
+
// FIXME: rewrite to use EvalCache.
auto resolve = [&] (PosIdx p) {
@@ -327,6 +343,15 @@ struct CmdFlakeCheck : FlakeCommand
reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos)));
};
+ auto checkSystemType = [&](const std::string & system, const PosIdx pos) {
+ if (!checkAllSystems && system != localSystem) {
+ omittedSystems.insert(system);
+ return false;
+ } else {
+ return true;
+ }
+ };
+
auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional<StorePath> {
try {
auto drvInfo = getDerivation(*state, v, false);
@@ -362,8 +387,10 @@ struct CmdFlakeCheck : FlakeCommand
auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
state->forceValue(v, pos);
- if (!v.isLambda()
- || v.lambda.fun->hasFormals()
+ if (!v.isLambda()) {
+ throw Error("overlay is not a function, but %s instead", showType(v));
+ }
+ if (v.lambda.fun->hasFormals()
|| !argHasName(v.lambda.fun->arg, "final"))
throw Error("overlay does not take an argument named 'final'");
auto body = dynamic_cast<ExprLambda *>(v.lambda.fun->body);
@@ -438,10 +465,10 @@ struct CmdFlakeCheck : FlakeCommand
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) {
- PathSet context;
+ NixStringContext context;
auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
- if (!store->isInStore(path))
- throw Error("template '%s' has a bad 'path' attribute");
+ if (!path.pathExists())
+ throw Error("template '%s' refers to a non-existent path '%s'", attrPath, path);
// TODO: recursively check the flake in 'path'.
}
} else
@@ -509,16 +536,18 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- state->forceAttrs(*attr.value, attr.pos, "");
- for (auto & attr2 : *attr.value->attrs) {
- auto drvPath = checkDerivation(
- fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
- *attr2.value, attr2.pos);
- if (drvPath && attr_name == settings.thisSystem.get()) {
- drvPaths.push_back(DerivedPath::Built {
- .drvPath = *drvPath,
- .outputs = OutputsSpec::All { },
- });
+ if (checkSystemType(attr_name, attr.pos)) {
+ state->forceAttrs(*attr.value, attr.pos, "");
+ for (auto & attr2 : *attr.value->attrs) {
+ auto drvPath = checkDerivation(
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
+ if (drvPath && attr_name == settings.thisSystem.get()) {
+ drvPaths.push_back(DerivedPath::Built {
+ .drvPath = makeConstantStorePathRef(*drvPath),
+ .outputs = OutputsSpec::All { },
+ });
+ }
}
}
}
@@ -529,9 +558,11 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- checkApp(
- fmt("%s.%s", name, attr_name),
- *attr.value, attr.pos);
+ if (checkSystemType(attr_name, attr.pos)) {
+ checkApp(
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
+ };
}
}
@@ -540,11 +571,13 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- state->forceAttrs(*attr.value, attr.pos, "");
- for (auto & attr2 : *attr.value->attrs)
- checkDerivation(
- fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
- *attr2.value, attr2.pos);
+ if (checkSystemType(attr_name, attr.pos)) {
+ state->forceAttrs(*attr.value, attr.pos, "");
+ for (auto & attr2 : *attr.value->attrs)
+ checkDerivation(
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
+ };
}
}
@@ -553,11 +586,13 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- state->forceAttrs(*attr.value, attr.pos, "");
- for (auto & attr2 : *attr.value->attrs)
- checkApp(
- fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
- *attr2.value, attr2.pos);
+ if (checkSystemType(attr_name, attr.pos)) {
+ state->forceAttrs(*attr.value, attr.pos, "");
+ for (auto & attr2 : *attr.value->attrs)
+ checkApp(
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
+ };
}
}
@@ -566,9 +601,11 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- checkDerivation(
- fmt("%s.%s", name, attr_name),
- *attr.value, attr.pos);
+ if (checkSystemType(attr_name, attr.pos)) {
+ checkDerivation(
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
+ };
}
}
@@ -577,9 +614,11 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- checkApp(
- fmt("%s.%s", name, attr_name),
- *attr.value, attr.pos);
+ if (checkSystemType(attr_name, attr.pos) ) {
+ checkApp(
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
+ };
}
}
@@ -587,6 +626,7 @@ struct CmdFlakeCheck : FlakeCommand
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
checkSystemName(state->symbols[attr.name], attr.pos);
+ checkSystemType(state->symbols[attr.name], attr.pos);
// FIXME: do getDerivations?
}
}
@@ -636,9 +676,11 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- checkBundler(
- fmt("%s.%s", name, attr_name),
- *attr.value, attr.pos);
+ if (checkSystemType(attr_name, attr.pos)) {
+ checkBundler(
+ fmt("%s.%s", name, attr_name),
+ *attr.value, attr.pos);
+ };
}
}
@@ -647,12 +689,14 @@ struct CmdFlakeCheck : FlakeCommand
for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
- state->forceAttrs(*attr.value, attr.pos, "");
- for (auto & attr2 : *attr.value->attrs) {
- checkBundler(
- fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
- *attr2.value, attr2.pos);
- }
+ if (checkSystemType(attr_name, attr.pos)) {
+ state->forceAttrs(*attr.value, attr.pos, "");
+ for (auto & attr2 : *attr.value->attrs) {
+ checkBundler(
+ fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
+ *attr2.value, attr2.pos);
+ }
+ };
}
}
@@ -685,7 +729,15 @@ struct CmdFlakeCheck : FlakeCommand
}
if (hasErrors)
throw Error("some errors were encountered during the evaluation");
- }
+
+ if (!omittedSystems.empty()) {
+ warn(
+ "The check omitted these incompatible systems: %s\n"
+ "Use '--all-systems' to check all.",
+ concatStringsSep(", ", omittedSystems)
+ );
+ };
+ };
};
static Strings defaultTemplateAttrPathsPrefixes{"templates."};
@@ -726,7 +778,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
auto installable = InstallableFlake(nullptr,
- evalState, std::move(templateFlakeRef), templateName, DefaultOutputs(),
+ evalState, std::move(templateFlakeRef), templateName, ExtendedOutputsSpec::Default(),
defaultTemplateAttrPaths,
defaultTemplateAttrPathsPrefixes,
lockFlags);
@@ -1026,36 +1078,43 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto visitor2 = visitor.getAttr(attrName);
- if ((attrPathS[0] == "apps"
- || attrPathS[0] == "checks"
- || attrPathS[0] == "devShells"
- || attrPathS[0] == "legacyPackages"
- || attrPathS[0] == "packages")
- && (attrPathS.size() == 1 || attrPathS.size() == 2)) {
- for (const auto &subAttr : visitor2->getAttrs()) {
- if (hasContent(*visitor2, attrPath2, subAttr)) {
- return true;
+ try {
+ if ((attrPathS[0] == "apps"
+ || attrPathS[0] == "checks"
+ || attrPathS[0] == "devShells"
+ || attrPathS[0] == "legacyPackages"
+ || attrPathS[0] == "packages")
+ && (attrPathS.size() == 1 || attrPathS.size() == 2)) {
+ for (const auto &subAttr : visitor2->getAttrs()) {
+ if (hasContent(*visitor2, attrPath2, subAttr)) {
+ return true;
+ }
}
+ return false;
}
- return false;
- }
- if ((attrPathS.size() == 1)
- && (attrPathS[0] == "formatter"
- || attrPathS[0] == "nixosConfigurations"
- || attrPathS[0] == "nixosModules"
- || attrPathS[0] == "overlays"
- )) {
- for (const auto &subAttr : visitor2->getAttrs()) {
- if (hasContent(*visitor2, attrPath2, subAttr)) {
- return true;
+ if ((attrPathS.size() == 1)
+ && (attrPathS[0] == "formatter"
+ || attrPathS[0] == "nixosConfigurations"
+ || attrPathS[0] == "nixosModules"
+ || attrPathS[0] == "overlays"
+ )) {
+ for (const auto &subAttr : visitor2->getAttrs()) {
+ if (hasContent(*visitor2, attrPath2, subAttr)) {
+ return true;
+ }
}
+ return false;
}
- return false;
- }
- // If we don't recognize it, it's probably content
- return true;
+ // If we don't recognize it, it's probably content
+ return true;
+ } catch (EvalError & e) {
+ // Some attrs may contain errors, eg. legacyPackages of
+ // nixpkgs. We still want to recurse into it, instead of
+ // skipping it at all.
+ return true;
+ }
};
std::function<nlohmann::json(
@@ -1328,7 +1387,7 @@ struct CmdFlake : NixMultiCommand
{
if (!command)
throw UsageError("'nix flake' requires a sub-command.");
- settings.requireExperimentalFeature(Xp::Flakes);
+ experimentalFeatureSettings.require(Xp::Flakes);
command->second->run();
}
};
diff --git a/src/nix/flake.md b/src/nix/flake.md
index 8eaa41b96..92f477917 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -71,8 +71,6 @@ inputs.nixpkgs = {
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 overridden to a
@@ -93,6 +91,23 @@ Here are some examples of flake references in their URL-like representation:
* `https://github.com/NixOS/patchelf/archive/master.tar.gz`: A tarball
flake.
+## Path-like syntax
+
+Flakes corresponding to a local path can also be referred to by a direct path reference, either `/absolute/path/to/the/flake` or `./relative/path/to/the/flake` (note that the leading `./` is mandatory for relative paths to avoid any ambiguity).
+
+The semantic of such a path is as follows:
+
+* If the directory is part of a Git repository, then the input will be treated as a `git+file:` URL, otherwise it will be treated as a `path:` url;
+* If the directory doesn't contain a `flake.nix` file, then Nix will search for such a file upwards in the file system hierarchy until it finds any of:
+ 1. The Git repository root, or
+ 2. The filesystem root (/), or
+ 3. A folder on a different mount point.
+
+### Examples
+
+* `.`: The flake to which the current directory belongs to.
+* `/home/alice/src/patchelf`: A flake in some other directory.
+
## Flake reference attributes
The following generic flake reference attributes are supported:
@@ -221,11 +236,46 @@ Currently the `type` attribute can be one of the following:
commit hash (`rev`). Note that unlike Git, GitHub allows fetching by
commit hash without specifying a branch or tag.
+ You can also specify `host` as a parameter, to point to a custom GitHub
+ Enterprise server.
+
Some examples:
* `github:edolstra/dwarffs`
* `github:edolstra/dwarffs/unstable`
* `github:edolstra/dwarffs/d3f2baba8f425779026c6ec04021b2e927f61e31`
+ * `github:internal/project?host=company-github.example.org`
+
+* `gitlab`: Similar to `github`, is a more efficient way to fetch
+ GitLab repositories. The following attributes are required:
+
+ * `owner`: The owner of the repository.
+
+ * `repo`: The name of the repository.
+
+ Like `github`, these are downloaded as tarball archives.
+
+ The URL syntax for `gitlab` flakes is:
+
+ `gitlab:<owner>/<repo>(/<rev-or-ref>)?(\?<params>)?`
+
+ `<rev-or-ref>` works the same as `github`. Either a branch or tag name
+ (`ref`), or a commit hash (`rev`) can be specified.
+
+ Since GitLab allows for self-hosting, you can specify `host` as
+ a parameter, to point to any instances other than `gitlab.com`.
+
+ Some examples:
+
+ * `gitlab:veloren/veloren`
+ * `gitlab:veloren/veloren/master`
+ * `gitlab:veloren/veloren/80a4d7f13492d916e47d6195be23acae8001985a`
+ * `gitlab:openldap/openldap?host=git.openldap.org`
+
+ When accessing a project in a (nested) subgroup, make sure to URL-encode any
+ slashes, i.e. replace `/` with `%2F`:
+
+ * `gitlab:veloren%2Fdev/rfcs`
* `sourcehut`: Similar to `github`, is a more efficient way to fetch
SourceHut repositories. The following attributes are required:
@@ -317,6 +367,8 @@ The following attributes are supported in `flake.nix`:
also contains some metadata about the inputs. These are:
* `outPath`: The path in the Nix store of the flake's source tree.
+ This way, the attribute set can be passed to `import` as if it was a path,
+ as in the example above (`import nixpkgs`).
* `rev`: The commit hash of the flake's repository, if applicable.
@@ -344,10 +396,12 @@ The following attributes are supported in `flake.nix`:
* `nixConfig`: a set of `nix.conf` options to be set when evaluating any
part of a flake. In the interests of security, only a small set of
- whitelisted options (currently `bash-prompt`, `bash-prompt-prefix`,
- `bash-prompt-suffix`, and `flake-registry`) are allowed to be set without
- confirmation so long as `accept-flake-config` is not set in the global
- configuration.
+ set of options is allowed to be set without confirmation so long as [`accept-flake-config`](@docroot@/command-ref/conf-file.md#conf-accept-flake-config) is not enabled in the global configuration:
+ - [`bash-prompt`](@docroot@/command-ref/conf-file.md#conf-bash-prompt)
+ - [`bash-prompt-prefix`](@docroot@/command-ref/conf-file.md#conf-bash-prompt-prefix)
+ - [`bash-prompt-suffix`](@docroot@/command-ref/conf-file.md#conf-bash-prompt-suffix)
+ - [`flake-registry`](@docroot@/command-ref/conf-file.md#conf-flake-registry)
+ - [`commit-lockfile-summary`](@docroot@/command-ref/conf-file.md#conf-commit-lockfile-summary)
## Flake inputs
diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc
index 6f6a4a632..c85eacded 100644
--- a/src/nix/fmt.cc
+++ b/src/nix/fmt.cc
@@ -1,4 +1,5 @@
#include "command.hh"
+#include "installable-value.hh"
#include "run.hh"
using namespace nix;
@@ -31,8 +32,9 @@ struct CmdFmt : SourceExprCommand {
auto evalState = getEvalState();
auto evalStore = getEvalStore();
- auto installable = parseInstallable(store, ".");
- auto app = installable->toApp(*evalState).resolve(evalStore, store);
+ auto installable_ = parseInstallable(store, ".");
+ auto & installable = InstallableValue::require(*installable_);
+ auto app = installable.toApp(*evalState).resolve(evalStore, store);
Strings programArgs{app.program};
diff --git a/src/nix/help-stores.md b/src/nix/help-stores.md
new file mode 100644
index 000000000..47ba9b94d
--- /dev/null
+++ b/src/nix/help-stores.md
@@ -0,0 +1,46 @@
+R"(
+
+Nix supports different types of stores. These are described below.
+
+## Store URL format
+
+Stores are specified using a URL-like syntax. For example, the command
+
+```console
+# nix path-info --store https://cache.nixos.org/ --json \
+ /nix/store/a7gvj343m05j2s32xcnwr35v31ynlypr-coreutils-9.1
+```
+
+fetches information about a store path in the HTTP binary cache
+located at https://cache.nixos.org/, which is a type of store.
+
+Store URLs can specify **store settings** using URL query strings,
+i.e. by appending `?name1=value1&name2=value2&...` to the URL. For
+instance,
+
+```
+--store ssh://machine.example.org?ssh-key=/path/to/my/key
+```
+
+tells Nix to access the store on a remote machine via the SSH
+protocol, using `/path/to/my/key` as the SSH private key. The
+supported settings for each store type are documented below.
+
+The special store URL `auto` causes Nix to automatically select a
+store as follows:
+
+* Use the [local store](#local-store) `/nix/store` if `/nix/var/nix`
+ is writable by the current user.
+
+* Otherwise, if `/nix/var/nix/daemon-socket/socket` exists, [connect
+ to the Nix daemon listening on that socket](#local-daemon-store).
+
+* Otherwise, on Linux only, use the [local chroot store](#local-store)
+ `~/.local/share/nix/root`, which will be created automatically if it
+ does not exist.
+
+* Otherwise, use the [local store](#local-store) `/nix/store`.
+
+@stores@
+
+)"
diff --git a/src/nix/local.mk b/src/nix/local.mk
index 0f2f016ec..20ea29d10 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -32,3 +32,9 @@ src/nix/develop.cc: src/nix/get-env.sh.gen.hh
src/nix-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
src/nix/main.cc: doc/manual/generate-manpage.nix.gen.hh doc/manual/utils.nix.gen.hh
+
+src/nix/doc/files/%.md: doc/manual/src/command-ref/files/%.md
+ @mkdir -p $$(dirname $@)
+ @cp $< $@
+
+src/nix/profile.cc: src/nix/profile.md src/nix/doc/files/profiles.md.gen.hh
diff --git a/src/nix/log.cc b/src/nix/log.cc
index aaf829764..9a9bd30f9 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -33,6 +33,17 @@ struct CmdLog : InstallableCommand
auto b = installable->toDerivedPath();
+ // For compat with CLI today, TODO revisit
+ auto oneUp = std::visit(overloaded {
+ [&](const DerivedPath::Opaque & bo) {
+ return make_ref<SingleDerivedPath>(bo);
+ },
+ [&](const DerivedPath::Built & bfd) {
+ return bfd.drvPath;
+ },
+ }, b.path.raw());
+ auto path = resolveDerivedPath(*store, *oneUp);
+
RunPager pager;
for (auto & sub : subs) {
auto * logSubP = dynamic_cast<LogStore *>(&*sub);
@@ -42,14 +53,7 @@ struct CmdLog : InstallableCommand
}
auto & logSub = *logSubP;
- auto log = std::visit(overloaded {
- [&](const DerivedPath::Opaque & bo) {
- return logSub.getBuildLog(bo.path);
- },
- [&](const DerivedPath::Built & bfd) {
- return logSub.getBuildLog(bfd.drvPath);
- },
- }, b.path.raw());
+ auto log = logSub.getBuildLog(path);
if (!log) continue;
stopProgressBar();
printInfo("got build log for '%s' from '%s'", installable->what(), logSub.getUri());
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 7b715f281..d05bac68e 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -3,6 +3,7 @@
#include "command.hh"
#include "common-args.hh"
#include "eval.hh"
+#include "eval-settings.hh"
#include "globals.hh"
#include "legacy.hh"
#include "shared.hh"
@@ -54,17 +55,17 @@ static bool haveInternet()
std::string programPath;
-struct HelpRequested { };
-
struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{
bool useNet = true;
bool refresh = false;
+ bool helpRequested = false;
bool showVersion = false;
NixArgs() : MultiCommand(RegisterCommand::getCommandsFor({})), MixCommonArgs("nix")
{
categories.clear();
+ categories[catHelp] = "Help commands";
categories[Command::catDefault] = "Main commands";
categories[catSecondary] = "Infrequently used commands";
categories[catUtility] = "Utility/scripting commands";
@@ -74,7 +75,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.longName = "help",
.description = "Show usage information.",
.category = miscCategory,
- .handler = {[&]() { throw HelpRequested(); }},
+ .handler = {[this]() { this->helpRequested = true; }},
});
addFlag({
@@ -83,6 +84,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Print full build logs on standard error.",
.category = loggingCategory,
.handler = {[&]() { logger->setPrintBuildLogs(true); }},
+ .experimentalFeature = Xp::NixCommand,
});
addFlag({
@@ -98,6 +100,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Disable substituters and consider all previously downloaded files up-to-date.",
.category = miscCategory,
.handler = {[&]() { useNet = false; }},
+ .experimentalFeature = Xp::NixCommand,
});
addFlag({
@@ -105,6 +108,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Consider all previously downloaded files out-of-date.",
.category = miscCategory,
.handler = {[&]() { refresh = true; }},
+ .experimentalFeature = Xp::NixCommand,
});
}
@@ -124,6 +128,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{"optimise-store", {"store", "optimise"}},
{"ping-store", {"store", "ping"}},
{"sign-paths", {"store", "sign"}},
+ {"show-derivation", {"derivation", "show"}},
{"to-base16", {"hash", "to-base16"}},
{"to-base32", {"hash", "to-base32"}},
{"to-base64", {"hash", "to-base64"}},
@@ -164,11 +169,31 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{
commands = RegisterCommand::getCommandsFor({});
}
+
+ std::string dumpCli()
+ {
+ auto res = nlohmann::json::object();
+
+ res["args"] = toJSON();
+
+ auto stores = nlohmann::json::object();
+ for (auto & implem : *Implementations::registered) {
+ auto storeConfig = implem.getConfig();
+ auto storeName = storeConfig->name();
+ auto & j = stores[storeName];
+ j["doc"] = storeConfig->doc();
+ j["settings"] = storeConfig->toJSON();
+ j["experimentalFeature"] = storeConfig->experimentalFeature();
+ }
+ res["stores"] = std::move(stores);
+
+ return res.dump();
+ }
};
/* Render the help for the specified subcommand to stdout using
lowdown. */
-static void showHelp(std::vector<std::string> subcommand, MultiCommand & toplevel)
+static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
{
auto mdName = subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand));
@@ -179,21 +204,21 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
auto vGenerateManpage = state.allocValue();
state.eval(state.parseExprFromString(
#include "generate-manpage.nix.gen.hh"
- , "/"), *vGenerateManpage);
+ , CanonPath::root), *vGenerateManpage);
auto vUtils = state.allocValue();
state.cacheFile(
- "/utils.nix", "/utils.nix",
+ CanonPath("/utils.nix"), CanonPath("/utils.nix"),
state.parseExprFromString(
#include "utils.nix.gen.hh"
- , "/"),
+ , CanonPath::root),
*vUtils);
- auto attrs = state.buildBindings(16);
- attrs.alloc("toplevel").mkString(toplevel.toJSON().dump());
+ auto vDump = state.allocValue();
+ vDump->mkString(toplevel.dumpCli());
auto vRes = state.allocValue();
- state.callFunction(*vGenerateManpage, state.allocValue()->mkAttrs(attrs), *vRes, noPos);
+ state.callFunction(*vGenerateManpage, *vDump, *vRes, noPos);
auto attr = vRes->attrs->get(state.symbols.create(mdName + ".md"));
if (!attr)
@@ -205,6 +230,14 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
std::cout << renderMarkdownToTerminal(markdown) << "\n";
}
+static NixArgs & getNixArgs(Command & cmd)
+{
+ assert(cmd.parent);
+ MultiCommand * toplevel = cmd.parent;
+ while (toplevel->parent) toplevel = toplevel->parent;
+ return dynamic_cast<NixArgs &>(*toplevel);
+}
+
struct CmdHelp : Command
{
std::vector<std::string> subcommand;
@@ -229,17 +262,43 @@ struct CmdHelp : Command
;
}
+ Category category() override { return catHelp; }
+
void run() override
{
assert(parent);
MultiCommand * toplevel = parent;
while (toplevel->parent) toplevel = toplevel->parent;
- showHelp(subcommand, *toplevel);
+ showHelp(subcommand, getNixArgs(*this));
}
};
static auto rCmdHelp = registerCommand<CmdHelp>("help");
+struct CmdHelpStores : Command
+{
+ std::string description() override
+ {
+ return "show help about store types and their settings";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "help-stores.md"
+ ;
+ }
+
+ Category category() override { return catHelp; }
+
+ void run() override
+ {
+ showHelp({"help-stores"}, getNixArgs(*this));
+ }
+};
+
+static auto rCmdHelpStores = registerCommand<CmdHelpStores>("help-stores");
+
void mainWrapped(int argc, char * * argv)
{
savedArgv = argv;
@@ -291,31 +350,57 @@ void mainWrapped(int argc, char * * argv)
NixArgs args;
- if (argc == 2 && std::string(argv[1]) == "__dump-args") {
- logger->cout("%s", args.toJSON());
+ if (argc == 2 && std::string(argv[1]) == "__dump-cli") {
+ logger->cout(args.dumpCli());
return;
}
- if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
- settings.experimentalFeatures = {Xp::Flakes, Xp::FetchClosure};
+ if (argc == 2 && std::string(argv[1]) == "__dump-language") {
+ experimentalFeatureSettings.experimentalFeatures = {
+ Xp::Flakes,
+ Xp::FetchClosure,
+ Xp::DynamicDerivations,
+ };
evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://"));
auto res = nlohmann::json::object();
- auto builtins = state.baseEnv.values[0]->attrs;
- for (auto & builtin : *builtins) {
- auto b = nlohmann::json::object();
- if (!builtin.value->isPrimOp()) continue;
- auto primOp = builtin.value->primOp;
- if (!primOp->doc) continue;
- b["arity"] = primOp->arity;
- b["args"] = primOp->args;
- b["doc"] = trim(stripIndentation(primOp->doc));
- res[state.symbols[builtin.name]] = std::move(b);
- }
+ res["builtins"] = ({
+ auto builtinsJson = nlohmann::json::object();
+ auto builtins = state.baseEnv.values[0]->attrs;
+ for (auto & builtin : *builtins) {
+ auto b = nlohmann::json::object();
+ if (!builtin.value->isPrimOp()) continue;
+ auto primOp = builtin.value->primOp;
+ if (!primOp->doc) continue;
+ b["arity"] = primOp->arity;
+ b["args"] = primOp->args;
+ b["doc"] = trim(stripIndentation(primOp->doc));
+ b["experimental-feature"] = primOp->experimentalFeature;
+ builtinsJson[state.symbols[builtin.name]] = std::move(b);
+ }
+ std::move(builtinsJson);
+ });
+ res["constants"] = ({
+ auto constantsJson = nlohmann::json::object();
+ for (auto & [name, info] : state.constantInfos) {
+ auto c = nlohmann::json::object();
+ if (!info.doc) continue;
+ c["doc"] = trim(stripIndentation(info.doc));
+ c["type"] = showType(info.type, false);
+ c["impure-only"] = info.impureOnly;
+ constantsJson[name] = std::move(c);
+ }
+ std::move(constantsJson);
+ });
logger->cout("%s", res);
return;
}
+ if (argc == 2 && std::string(argv[1]) == "__dump-xp-features") {
+ logger->cout(documentExperimentalFeatures().dump());
+ return;
+ }
+
Finally printCompletions([&]()
{
if (completions) {
@@ -334,7 +419,11 @@ void mainWrapped(int argc, char * * argv)
try {
args.parseCmdline(argvToStrings(argc, argv));
- } catch (HelpRequested &) {
+ } catch (UsageError &) {
+ if (!args.helpRequested && !completions) throw;
+ }
+
+ if (args.helpRequested) {
std::vector<std::string> subcommand;
MultiCommand * command = &args;
while (command) {
@@ -346,8 +435,6 @@ void mainWrapped(int argc, char * * argv)
}
showHelp(subcommand, args);
return;
- } catch (UsageError &) {
- if (!completions) throw;
}
if (completions) {
@@ -363,10 +450,8 @@ void mainWrapped(int argc, char * * argv)
if (!args.command)
throw UsageError("no subcommand specified");
- if (args.command->first != "repl"
- && args.command->first != "doctor"
- && args.command->first != "upgrade-nix")
- settings.requireExperimentalFeature(Xp::NixCommand);
+ experimentalFeatureSettings.require(
+ args.command->second->experimentalFeature());
if (args.useNet && !haveInternet()) {
warn("you don't have Internet access; disabling some network-dependent features");
diff --git a/src/nix/nar-ls.md b/src/nix/nar-ls.md
index d373f9715..5a03c5d82 100644
--- a/src/nix/nar-ls.md
+++ b/src/nix/nar-ls.md
@@ -5,7 +5,7 @@ R""(
* To list a specific file in a NAR:
```console
- # nix nar ls -l ./hello.nar /bin/hello
+ # nix nar ls --long ./hello.nar /bin/hello
-r-xr-xr-x 38184 hello
```
@@ -13,7 +13,7 @@ R""(
format:
```console
- # nix nar ls --json -R ./hello.nar /bin
+ # nix nar ls --json --recursive ./hello.nar /bin
{"type":"directory","entries":{"hello":{"type":"regular","size":38184,"executable":true,"narOffset":400}}}
```
diff --git a/src/nix/nix.md b/src/nix/nix.md
index 0a90fa6c9..e0f459d6b 100644
--- a/src/nix/nix.md
+++ b/src/nix/nix.md
@@ -48,21 +48,33 @@ manual](https://nixos.org/manual/nix/stable/).
# Installables
+> **Warning** \
+> Installables are part of the unstable
+> [`nix-command` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-nix-command),
+> and subject to change without notice.
+
Many `nix` subcommands operate on one or more *installables*.
These are command line arguments that represent something that can be realised in the Nix store.
The following types of installable are supported by most commands:
-- [Flake output attribute](#flake-output-attribute)
+- [Flake output attribute](#flake-output-attribute) (experimental)
- [Store path](#store-path)
- [Nix file](#nix-file), optionally qualified by an attribute path
- [Nix expression](#nix-expression), optionally qualified by an attribute path
-For most commands, if no installable is specified, `.` as assumed.
+For most commands, if no installable is specified, `.` is assumed.
That is, Nix will operate on the default flake output attribute of the flake in the current directory.
### Flake output attribute
+> **Warning** \
+> Flake output attribute installables depend on both the
+> [`flakes`](@docroot@/contributing/experimental-features.md#xp-feature-flakes)
+> and
+> [`nix-command`](@docroot@/contributing/experimental-features.md#xp-feature-nix-command)
+> experimental features, and subject to change without notice.
+
Example: `nixpkgs#hello`
These have the form *flakeref*[`#`*attrpath*], where *flakeref* is a
@@ -90,6 +102,7 @@ way:
available in the flake. If this is undesirable, specify `path:<directory>` explicitly;
For example, if `/foo/bar` is a git repository with the following structure:
+
```
.
└── baz
@@ -185,7 +198,7 @@ operate are determined as follows:
of all outputs of the `glibc` package in the binary cache:
```console
- # nix path-info -S --eval-store auto --store https://cache.nixos.org 'nixpkgs#glibc^*'
+ # nix path-info --closure-size --eval-store auto --store https://cache.nixos.org 'nixpkgs#glibc^*'
/nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123 33208200
/nix/store/851dp95qqiisjifi639r0zzg5l465ny4-glibc-2.33-123-bin 36142896
/nix/store/kdgs3q6r7xdff1p7a9hnjr43xw2404z7-glibc-2.33-123-debug 155787312
@@ -196,7 +209,7 @@ operate are determined as follows:
and likewise, using a store path to a "drv" file to specify the derivation:
```console
- # nix path-info -S '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*'
+ # nix path-info --closure-size '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*'
```
* If you didn't specify the desired outputs, but the derivation has an
@@ -220,8 +233,7 @@ operate are determined as follows:
# Nix stores
-Most `nix` subcommands operate on a *Nix store*.
-
-TODO: list store types, options
+Most `nix` subcommands operate on a *Nix store*. These are documented
+in [`nix help-stores`](./nix3-help-stores.md).
)""
diff --git a/src/nix/path-info.md b/src/nix/path-info.md
index 6ad23a02e..2dda866d0 100644
--- a/src/nix/path-info.md
+++ b/src/nix/path-info.md
@@ -13,7 +13,7 @@ R""(
closure, sorted by size:
```console
- # nix path-info -rS /run/current-system | sort -nk2
+ # nix path-info --recursive --closure-size /run/current-system | sort -nk2
/nix/store/hl5xwp9kdrd1zkm0idm3kkby9q66z404-empty 96
/nix/store/27324qvqhnxj3rncazmxc4mwy79kz8ha-nameservers 112
@@ -25,7 +25,7 @@ R""(
readable sizes:
```console
- # nix path-info -rsSh nixpkgs#rustc
+ # nix path-info --recursive --size --closure-size --human-readable nixpkgs#rustc
/nix/store/01rrgsg5zk3cds0xgdsq40zpk6g51dz9-ncurses-6.2-dev 386.7K 69.1M
/nix/store/0q783wnvixpqz6dxjp16nw296avgczam-libpfm-4.11.0 5.9M 37.4M
@@ -34,7 +34,7 @@ R""(
* 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/
+ # nix path-info --recursive /nix/store/blzxgyvrk32ki6xga10phr4sby2xf25q-geeqie-1.5.1 --store https://cache.nixos.org/
path '/nix/store/blzxgyvrk32ki6xga10phr4sby2xf25q-geeqie-1.5.1' is not valid
```
@@ -57,7 +57,7 @@ R""(
size:
```console
- # nix path-info --json --all -S \
+ # nix path-info --json --all --closure-size \
| jq 'map(select(.closureSize > 1e9)) | sort_by(.closureSize) | map([.path, .closureSize])'
[
…,
diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc
index 5c44510ab..ec450e8e0 100644
--- a/src/nix/ping-store.cc
+++ b/src/nix/ping-store.cc
@@ -28,15 +28,20 @@ struct CmdPingStore : StoreCommand, MixJSON
store->connect();
if (auto version = store->getVersion())
notice("Version: %s", *version);
+ if (auto trusted = store->isTrustedClient())
+ notice("Trusted: %s", *trusted);
} else {
nlohmann::json res;
Finally printRes([&]() {
logger->cout("%s", res);
});
+
res["url"] = store->getUri();
store->connect();
if (auto version = store->getVersion())
res["version"] = *version;
+ if (auto trusted = store->isTrustedClient())
+ res["trusted"] = *trusted;
}
}
};
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
index 51c8a3319..b67d381ca 100644
--- a/src/nix/prefetch.cc
+++ b/src/nix/prefetch.cc
@@ -27,7 +27,10 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors;
// FIXME: use nixpkgs flake
- state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
+ state.eval(state.parseExprFromString(
+ "import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>",
+ state.rootPath(CanonPath::root)),
+ vMirrors);
state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
@@ -67,7 +70,11 @@ std::tuple<StorePath, Hash> prefetchFile(
the store. */
if (expectedHash) {
hashType = expectedHash->type;
- storePath = store->makeFixedOutputPath(ingestionMethod, *expectedHash, *name);
+ storePath = store->makeFixedOutputPath(*name, FixedOutputInfo {
+ .method = ingestionMethod,
+ .hash = *expectedHash,
+ .references = {},
+ });
if (store->isValidPath(*storePath))
hash = expectedHash;
else
@@ -118,7 +125,7 @@ std::tuple<StorePath, Hash> prefetchFile(
auto info = store->addToStoreSlow(*name, tmpFile, ingestionMethod, hashType, expectedHash);
storePath = info.path;
assert(info.ca);
- hash = getContentAddressHash(*info.ca);
+ hash = info.ca->hash;
}
return {storePath.value(), hash.value()};
@@ -192,9 +199,11 @@ static int main_nix_prefetch_url(int argc, char * * argv)
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);
+ state->evalFile(
+ resolveExprPath(
+ lookupFileArg(*state, args.empty() ? "." : args[0])),
+ vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
diff --git a/src/nix/profile-list.md b/src/nix/profile-list.md
index fa786162f..5d7fcc0ec 100644
--- a/src/nix/profile-list.md
+++ b/src/nix/profile-list.md
@@ -6,26 +6,48 @@ R""(
```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#packages.x86_64-linux.default github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#packages.x86_64-linux.default /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0
+ Index: 0
+ Flake attribute: legacyPackages.x86_64-linux.gdb
+ Original flake URL: flake:nixpkgs
+ Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
+ Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1
+
+ Index: 1
+ Flake attribute: packages.x86_64-linux.default
+ Original flake URL: flake:blender-bin
+ Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender
+ Store paths: /nix/store/i798sxl3j40wpdi1rgf391id1b5klw7g-blender-bin-3.1.2
```
+ Note that you can unambiguously rebuild a package from a profile
+ through its locked flake URL and flake attribute, e.g.
+
+ ```console
+ # nix build github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender#packages.x86_64-linux.default
+ ```
+
+ will build the package with index 1 shown above.
+
# Description
This command shows what packages are currently installed in a
-profile. The output consists of one line per package, with the
-following fields:
+profile. For each installed package, it shows the following
+information:
+
+* `Index`: An integer that can be used to unambiguously identify the
+ package in invocations of `nix profile remove` and `nix profile
+ upgrade`.
-* An integer that can be used to unambiguously identify the package in
- invocations of `nix profile remove` and `nix profile upgrade`.
+* `Flake attribute`: The flake output attribute path that provides the
+ package (e.g. `packages.x86_64-linux.hello`).
-* The original ("unlocked") flake reference and output attribute path
- used at installation time.
+* `Original flake URL`: The original ("unlocked") flake reference
+ specified by the user when the package was first installed via `nix
+ profile install`.
-* The locked flake reference to which the unlocked flake reference was
- resolved.
+* `Locked flake URL`: The locked flake reference to which the original
+ flake reference was resolved.
-* The store path(s) of the package.
+* `Store paths`: The store path(s) of the package.
)""
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index d72dd1a13..476ddcd60 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -21,7 +21,7 @@ struct ProfileElementSource
{
FlakeRef originalRef;
// FIXME: record original attrpath.
- FlakeRef resolvedRef;
+ FlakeRef lockedRef;
std::string attrPath;
ExtendedOutputsSpec outputs;
@@ -31,6 +31,11 @@ struct ProfileElementSource
std::tuple(originalRef.to_string(), attrPath, outputs) <
std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
}
+
+ std::string to_string() const
+ {
+ return fmt("%s#%s%s", originalRef, attrPath, outputs.to_string());
+ }
};
const int defaultPriority = 5;
@@ -42,16 +47,30 @@ struct ProfileElement
bool active = true;
int priority = defaultPriority;
- std::string describe() const
+ std::string identifier() const
{
if (source)
- return fmt("%s#%s%s", source->originalRef, source->attrPath, source->outputs.to_string());
+ return source->to_string();
StringSet names;
for (auto & path : storePaths)
names.insert(DrvName(path.name()).name);
return concatStringsSep(", ", names);
}
+ /**
+ * Return a string representing an installable corresponding to the current
+ * element, either a flakeref or a plain store path
+ */
+ std::set<std::string> toInstallables(Store & store)
+ {
+ if (source)
+ return {source->to_string()};
+ StringSet rawPaths;
+ for (auto & path : storePaths)
+ rawPaths.insert(store.printStorePath(path));
+ return rawPaths;
+ }
+
std::string versions() const
{
StringSet versions;
@@ -62,7 +81,7 @@ struct ProfileElement
bool operator < (const ProfileElement & other) const
{
- return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
+ return std::tuple(identifier(), storePaths) < std::tuple(other.identifier(), other.storePaths);
}
void updateStorePaths(
@@ -149,7 +168,7 @@ struct ProfileManifest
}
}
- std::string toJSON(Store & store) const
+ nlohmann::json toJSON(Store & store) const
{
auto array = nlohmann::json::array();
for (auto & element : elements) {
@@ -162,7 +181,7 @@ struct ProfileManifest
obj["priority"] = element.priority;
if (element.source) {
obj["originalUrl"] = element.source->originalRef.to_string();
- obj["url"] = element.source->resolvedRef.to_string();
+ obj["url"] = element.source->lockedRef.to_string();
obj["attrPath"] = element.source->attrPath;
obj["outputs"] = element.source->outputs;
}
@@ -171,7 +190,7 @@ struct ProfileManifest
nlohmann::json json;
json["version"] = 2;
json["elements"] = array;
- return json.dump();
+ return json;
}
StorePath build(ref<Store> store)
@@ -191,7 +210,7 @@ struct ProfileManifest
buildProfile(tempDir, std::move(pkgs));
- writeFile(tempDir + "/manifest.json", toJSON(*store));
+ writeFile(tempDir + "/manifest.json", toJSON(*store).dump());
/* Add the symlink tree to the store. */
StringSink sink;
@@ -200,12 +219,20 @@ struct ProfileManifest
auto narHash = hashString(htSHA256, sink.s);
ValidPathInfo info {
- store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "profile", references),
+ *store,
+ "profile",
+ FixedOutputInfo {
+ .method = FileIngestionMethod::Recursive,
+ .hash = narHash,
+ .references = {
+ .others = std::move(references),
+ // profiles never refer to themselves
+ .self = false,
+ },
+ },
narHash,
};
- info.references = std::move(references);
info.narSize = sink.s.size();
- info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash };
StringSource source(sink.s);
store->addToStore(info, source);
@@ -227,13 +254,13 @@ struct ProfileManifest
bool changes = false;
while (i != prevElems.end() || j != curElems.end()) {
- if (j != curElems.end() && (i == prevElems.end() || i->describe() > j->describe())) {
- logger->cout("%s%s: ∅ -> %s", indent, j->describe(), j->versions());
+ if (j != curElems.end() && (i == prevElems.end() || i->identifier() > j->identifier())) {
+ logger->cout("%s%s: ∅ -> %s", indent, j->identifier(), j->versions());
changes = true;
++j;
}
- else if (i != prevElems.end() && (j == curElems.end() || i->describe() < j->describe())) {
- logger->cout("%s%s: %s -> ∅", indent, i->describe(), i->versions());
+ else if (i != prevElems.end() && (j == curElems.end() || i->identifier() < j->identifier())) {
+ logger->cout("%s%s: %s -> ∅", indent, i->identifier(), i->versions());
changes = true;
++i;
}
@@ -241,7 +268,7 @@ struct ProfileManifest
auto v1 = i->versions();
auto v2 = j->versions();
if (v1 != v2) {
- logger->cout("%s%s: %s -> %s", indent, i->describe(), v1, v2);
+ logger->cout("%s%s: %s -> %s", indent, i->identifier(), v1, v2);
changes = true;
}
++i;
@@ -254,13 +281,19 @@ struct ProfileManifest
}
};
-static std::map<Installable *, std::pair<BuiltPaths, ExtraPathInfo>>
+static std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>>
builtPathsPerInstallable(
const std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> & builtPaths)
{
- std::map<Installable *, std::pair<BuiltPaths, ExtraPathInfo>> res;
+ std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>> res;
for (auto & [installable, builtPath] : builtPaths) {
- auto & r = res[&*installable];
+ auto & r = res.insert({
+ &*installable,
+ {
+ {},
+ make_ref<ExtraPathInfo>(),
+ }
+ }).first->second;
/* Note that there could be conflicting info
(e.g. meta.priority fields) if the installable returned
multiple derivations. So pick one arbitrarily. FIXME:
@@ -307,14 +340,16 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
for (auto & installable : installables) {
ProfileElement element;
- auto & [res, info] = builtPaths[&*installable];
+ auto iter = builtPaths.find(&*installable);
+ if (iter == builtPaths.end()) continue;
+ auto & [res, info] = iter->second;
- if (info.originalRef && info.resolvedRef && info.attrPath && info.extendedOutputsSpec) {
+ if (auto * info2 = dynamic_cast<ExtraPathInfoFlake *>(&*info)) {
element.source = ProfileElementSource {
- .originalRef = *info.originalRef,
- .resolvedRef = *info.resolvedRef,
- .attrPath = *info.attrPath,
- .outputs = *info.extendedOutputsSpec,
+ .originalRef = info2->flake.originalRef,
+ .lockedRef = info2->flake.lockedRef,
+ .attrPath = info2->value.attrPath,
+ .outputs = info2->value.extendedOutputsSpec,
};
}
@@ -323,7 +358,12 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
element.priority =
priority
? *priority
- : info.priority.value_or(defaultPriority);
+ : ({
+ auto * info2 = dynamic_cast<ExtraPathInfoValue *>(&*info);
+ info2
+ ? info2->value.priority.value_or(defaultPriority)
+ : defaultPriority;
+ });
element.updateStorePaths(getEvalStore(), store, res);
@@ -340,10 +380,10 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
auto profileElement = *it;
for (auto & storePath : profileElement.storePaths) {
if (conflictError.fileA.starts_with(store->printStorePath(storePath))) {
- return std::pair(conflictError.fileA, profileElement.source->originalRef);
+ return std::pair(conflictError.fileA, profileElement.toInstallables(*store));
}
if (conflictError.fileB.starts_with(store->printStorePath(storePath))) {
- return std::pair(conflictError.fileB, profileElement.source->originalRef);
+ return std::pair(conflictError.fileB, profileElement.toInstallables(*store));
}
}
}
@@ -352,9 +392,9 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
// There are 2 conflicting files. We need to find out which one is from the already installed package and
// which one is the package that is the new package that is being installed.
// The first matching package is the one that was already installed (original).
- auto [originalConflictingFilePath, originalConflictingRef] = findRefByFilePath(manifest.elements.begin(), manifest.elements.end());
+ auto [originalConflictingFilePath, originalConflictingRefs] = findRefByFilePath(manifest.elements.begin(), manifest.elements.end());
// The last matching package is the one that was going to be installed (new).
- auto [newConflictingFilePath, newConflictingRef] = findRefByFilePath(manifest.elements.rbegin(), manifest.elements.rend());
+ auto [newConflictingFilePath, newConflictingRefs] = findRefByFilePath(manifest.elements.rbegin(), manifest.elements.rend());
throw Error(
"An existing package already provides the following file:\n"
@@ -380,8 +420,8 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
" nix profile install %4% --priority %7%\n",
originalConflictingFilePath,
newConflictingFilePath,
- originalConflictingRef.to_string(),
- newConflictingRef.to_string(),
+ concatStringsSep(" ", originalConflictingRefs),
+ concatStringsSep(" ", newConflictingRefs),
conflictError.priority,
conflictError.priority - 1,
conflictError.priority + 1
@@ -468,7 +508,7 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
if (!matches(*store, element, i, matchers)) {
newManifest.elements.push_back(std::move(element));
} else {
- notice("removing '%s'", element.describe());
+ notice("removing '%s'", element.identifier());
}
}
@@ -541,19 +581,20 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
auto derivedPaths = installable->toDerivedPaths();
if (derivedPaths.empty()) continue;
- auto & info = derivedPaths[0].info;
-
- assert(info.resolvedRef && info.attrPath);
+ auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
+ // `InstallableFlake` should use `ExtraPathInfoFlake`.
+ assert(infop);
+ auto & info = *infop;
- if (element.source->resolvedRef == info.resolvedRef) continue;
+ if (element.source->lockedRef == info.flake.lockedRef) continue;
printInfo("upgrading '%s' from flake '%s' to '%s'",
- element.source->attrPath, element.source->resolvedRef, *info.resolvedRef);
+ element.source->attrPath, element.source->lockedRef, info.flake.lockedRef);
element.source = ProfileElementSource {
.originalRef = installable->flakeRef,
- .resolvedRef = *info.resolvedRef,
- .attrPath = *info.attrPath,
+ .lockedRef = info.flake.lockedRef,
+ .attrPath = info.value.attrPath,
.outputs = installable->extendedOutputsSpec,
};
@@ -582,14 +623,17 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
for (size_t i = 0; i < installables.size(); ++i) {
auto & installable = installables.at(i);
auto & element = manifest.elements[indices.at(i)];
- element.updateStorePaths(getEvalStore(), store, builtPaths[&*installable].first);
+ element.updateStorePaths(
+ getEvalStore(),
+ store,
+ builtPaths.find(&*installable)->second.first);
}
updateProfile(manifest.build(store));
}
};
-struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
+struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile, MixJSON
{
std::string description() override
{
@@ -607,12 +651,22 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
{
ProfileManifest manifest(*getEvalState(), *profile);
- for (size_t i = 0; i < manifest.elements.size(); ++i) {
- auto & element(manifest.elements[i]);
- logger->cout("%d %s %s %s", i,
- element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
- element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
- concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
+ if (json) {
+ std::cout << manifest.toJSON(*store).dump() << "\n";
+ } else {
+ for (size_t i = 0; i < manifest.elements.size(); ++i) {
+ auto & element(manifest.elements[i]);
+ if (i) logger->cout("");
+ logger->cout("Index: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
+ i,
+ element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
+ if (element.source) {
+ logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
+ logger->cout("Original flake URL: %s", element.source->originalRef.to_string());
+ logger->cout("Locked flake URL: %s", element.source->lockedRef.to_string());
+ }
+ logger->cout("Store paths: %s", concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
+ }
}
}
};
@@ -760,9 +814,10 @@ struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRu
void run(ref<Store> store) override
{
- if (minAge)
- deleteGenerationsOlderThan(*profile, *minAge, dryRun);
- else
+ if (minAge) {
+ auto t = parseOlderThanTimeSpec(*minAge);
+ deleteGenerationsOlderThan(*profile, t, dryRun);
+ } else
deleteOldGenerations(*profile, dryRun);
}
};
diff --git a/src/nix/profile.md b/src/nix/profile.md
index 273e02280..bd13f906f 100644
--- a/src/nix/profile.md
+++ b/src/nix/profile.md
@@ -7,100 +7,39 @@ 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
+# Files
-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/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*.
+#include "doc/files/profiles.md.gen.hh"
-# Filesystem layout
+R""(
-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:
+### Profile compatibility
-```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
-```
+> **Warning**
+>
+> Once you have used [`nix profile`] you can no longer use [`nix-env`] without first deleting `$XDG_STATE_HOME/nix/profiles/profile`
-Each of these symlinks is a root for the Nix garbage collector.
+[`nix-env`]: @docroot@/command-ref/nix-env.md
+[`nix profile`]: @docroot@/command-ref/new-cli/nix3-profile.md
-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.
+Once you installed a package with [`nix profile`], you get the following error message when using [`nix-env`]:
```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
-
-…
+$ nix-env -f '<nixpkgs>' -iA 'hello'
+error: nix-env
+profile '/home/alice/.local/state/nix/profiles/profile' is incompatible with 'nix-env'; please use 'nix profile' instead
```
-The file `manifest.json` records the provenance of the packages that
-are installed in this version of the profile. It looks like this:
+To migrate back to `nix-env` you can delete your current profile:
-```json
-{
- "version": 1,
- "elements": [
- {
- "active": true,
- "attrPath": "legacyPackages.x86_64-linux.zoom-us",
- "originalUrl": "flake:nixpkgs",
- "storePaths": [
- "/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927"
- ],
- "uri": "github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a"
- },
- …
- ]
-}
-```
+> **Warning**
+>
+> This will delete packages that have been installed before, so you may want to back up this information before running the command.
-Each object in the array `elements` denotes an installed package and
-has the following fields:
-
-* `originalUrl`: 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 locked flake reference to which `originalUrl` 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
- `packages.x86_64-linux.default`.
-
-* `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.
+```console
+ $ rm -rf "${XDG_STATE_HOME-$HOME/.local/state}/nix/profiles/profile"
+```
)""
diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc
index 13db80282..e19e93219 100644
--- a/src/nix/realisation.cc
+++ b/src/nix/realisation.cc
@@ -45,7 +45,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON
void run(ref<Store> store, BuiltPaths && paths) override
{
- settings.requireExperimentalFeature(Xp::CaDerivations);
+ experimentalFeatureSettings.require(Xp::CaDerivations);
RealisedPath::Set realisations;
for (auto & builtPath : paths) {
diff --git a/src/nix/registry.cc b/src/nix/registry.cc
index 1f4f820ac..cb94bbd31 100644
--- a/src/nix/registry.cc
+++ b/src/nix/registry.cc
@@ -224,7 +224,7 @@ struct CmdRegistry : virtual NixMultiCommand
void run() override
{
- settings.requireExperimentalFeature(Xp::Flakes);
+ experimentalFeatureSettings.require(Xp::Flakes);
if (!command)
throw UsageError("'nix registry' requires a sub-command.");
command->second->run();
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 51d3074b4..9677c1b48 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -1,6 +1,8 @@
#include "eval.hh"
+#include "eval-settings.hh"
#include "globals.hh"
#include "command.hh"
+#include "installable-value.hh"
#include "repl.hh"
namespace nix {
@@ -11,6 +13,14 @@ struct CmdRepl : RawInstallablesCommand
evalSettings.pureEval = false;
}
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::vector<std::string> files;
Strings getDefaultFlakeAttrPaths() override
@@ -37,7 +47,7 @@ struct CmdRepl : RawInstallablesCommand
void applyDefaultInstallables(std::vector<std::string> & rawInstallables) override
{
- if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && rawInstallables.size() >= 1) {
+ if (!experimentalFeatureSettings.isEnabled(Xp::ReplFlake) && !(file) && rawInstallables.size() >= 1) {
warn("future versions of Nix will require using `--file` to load a file");
if (rawInstallables.size() > 1)
warn("more than one input file is not currently supported");
@@ -57,11 +67,12 @@ struct CmdRepl : RawInstallablesCommand
auto getValues = [&]()->AbstractNixRepl::AnnotatedValues{
auto installables = parseInstallables(store, rawInstallables);
AbstractNixRepl::AnnotatedValues values;
- for (auto & installable: installables){
- auto what = installable->what();
+ for (auto & installable_: installables){
+ auto & installable = InstallableValue::require(*installable_);
+ auto what = installable.what();
if (file){
- auto [val, pos] = installable->toValue(*state);
- auto what = installable->what();
+ auto [val, pos] = installable.toValue(*state);
+ auto what = installable.what();
state->forceValue(*val, pos);
auto autoArgs = getAutoArgs(*state);
auto valPost = state->allocValue();
@@ -69,7 +80,7 @@ struct CmdRepl : RawInstallablesCommand
state->forceValue(*valPost, pos);
values.push_back( {valPost, what });
} else {
- auto [val, pos] = installable->toValue(*state);
+ auto [val, pos] = installable.toValue(*state);
values.push_back( {val, what} );
}
}
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 56605d9d5..1baf299ab 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -1,5 +1,5 @@
#include "run.hh"
-#include "command.hh"
+#include "command-installable-value.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
@@ -137,7 +137,7 @@ struct CmdShell : InstallablesCommand, MixEnvironment
static auto rCmdShell = registerCommand<CmdShell>("shell");
-struct CmdRun : InstallableCommand
+struct CmdRun : InstallableValueCommand
{
using InstallableCommand::run;
@@ -183,7 +183,7 @@ struct CmdRun : InstallableCommand
return res;
}
- void run(ref<Store> store, ref<Installable> installable) override
+ void run(ref<Store> store, ref<InstallableValue> installable) override
{
auto state = getEvalState();
diff --git a/src/nix/run.hh b/src/nix/run.hh
index fed360158..97ddef19b 100644
--- a/src/nix/run.hh
+++ b/src/nix/run.hh
@@ -1,4 +1,5 @@
#pragma once
+///@file
#include "store-api.hh"
diff --git a/src/nix/search.cc b/src/nix/search.cc
index 994ec44c2..ef0139e09 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -1,7 +1,8 @@
-#include "command.hh"
+#include "command-installable-value.hh"
#include "globals.hh"
#include "eval.hh"
#include "eval-inline.hh"
+#include "eval-settings.hh"
#include "names.hh"
#include "get-drvs.hh"
#include "common-args.hh"
@@ -22,7 +23,7 @@ std::string wrap(std::string prefix, std::string s)
return concatStrings(prefix, s, ANSI_NORMAL);
}
-struct CmdSearch : InstallableCommand, MixJSON
+struct CmdSearch : InstallableValueCommand, MixJSON
{
std::vector<std::string> res;
std::vector<std::string> excludeRes;
@@ -61,7 +62,7 @@ struct CmdSearch : InstallableCommand, MixJSON
};
}
- void run(ref<Store> store, ref<Installable> installable) override
+ void run(ref<Store> store, ref<InstallableValue> installable) override
{
settings.readOnlyMode = true;
evalSettings.enableImportFromDerivation.setDefault(false);
diff --git a/src/nix/search.md b/src/nix/search.md
index 4caa90654..0c5d22549 100644
--- a/src/nix/search.md
+++ b/src/nix/search.md
@@ -52,12 +52,12 @@ R""(
* Search for packages containing `neovim` but hide ones containing either `gui` or `python`:
```console
- # nix search nixpkgs neovim -e 'python|gui'
+ # nix search nixpkgs neovim --exclude 'python|gui'
```
or
```console
- # nix search nixpkgs neovim -e 'python' -e 'gui'
+ # nix search nixpkgs neovim --exclude 'python' --exclude 'gui'
```
# Description
diff --git a/src/nix/shell.md b/src/nix/shell.md
index 13a389103..f36919575 100644
--- a/src/nix/shell.md
+++ b/src/nix/shell.md
@@ -19,26 +19,26 @@ R""(
* Run GNU Hello:
```console
- # nix shell nixpkgs#hello -c hello --greeting 'Hi everybody!'
+ # nix shell nixpkgs#hello --command hello --greeting 'Hi everybody!'
Hi everybody!
```
* Run multiple commands in a shell environment:
```console
- # nix shell nixpkgs#gnumake -c sh -c "cd src && make"
+ # nix shell nixpkgs#gnumake --command sh -c "cd src && make"
```
* Run GNU Hello in a chroot store:
```console
- # nix shell --store ~/my-nix nixpkgs#hello -c hello
+ # nix shell --store ~/my-nix nixpkgs#hello --command hello
```
* Start a shell providing GNU Hello in a chroot store:
```console
- # nix shell --store ~/my-nix nixpkgs#hello nixpkgs#bashInteractive -c bash
+ # nix shell --store ~/my-nix nixpkgs#hello nixpkgs#bashInteractive --command bash
```
Note that it's necessary to specify `bash` explicitly because your
diff --git a/src/nix/store-ls.md b/src/nix/store-ls.md
index 836efce42..14c4627c9 100644
--- a/src/nix/store-ls.md
+++ b/src/nix/store-ls.md
@@ -5,7 +5,7 @@ R""(
* 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
+ # nix store ls --store https://cache.nixos.org/ --long --recursive /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
@@ -15,7 +15,7 @@ R""(
* 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
+ # nix store ls --store https://cache.nixos.org/ --long /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10/bin/hello
-r-xr-xr-x 38184 hello
```
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 17796d6b8..d238456db 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -3,6 +3,7 @@
#include "store-api.hh"
#include "filetransfer.hh"
#include "eval.hh"
+#include "eval-settings.hh"
#include "attr-path.hh"
#include "names.hh"
#include "progress-bar.hh"
@@ -32,6 +33,14 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
});
}
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::string description() override
{
return "upgrade Nix to the stable version declared in Nixpkgs";
@@ -138,9 +147,9 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
auto req = FileTransferRequest(storePathsUrl);
auto res = getFileTransfer()->download(req);
- auto state = std::make_unique<EvalState>(Strings(), store);
+ auto state = std::make_unique<EvalState>(SearchPath{}, store);
auto v = state->allocValue();
- state->eval(state->parseExprFromString(res.data, "/no-such-path"), *v);
+ state->eval(state->parseExprFromString(res.data, state->rootPath(CanonPath("/no-such-path"))), *v);
Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
diff --git a/src/nix/upgrade-nix.md b/src/nix/upgrade-nix.md
index 084c80ba2..cce88c397 100644
--- a/src/nix/upgrade-nix.md
+++ b/src/nix/upgrade-nix.md
@@ -11,7 +11,7 @@ R""(
* Upgrade Nix in a specific profile:
```console
- # nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile
+ # nix upgrade-nix --profile ~alice/.local/state/nix/profiles/profile
```
# Description
diff --git a/src/nix/verify.md b/src/nix/verify.md
index cc1122c02..e1d55eab4 100644
--- a/src/nix/verify.md
+++ b/src/nix/verify.md
@@ -12,7 +12,7 @@ R""(
signatures:
```console
- # nix store verify -r -n2 --no-contents $(type -p firefox)
+ # nix store verify --recursive --sigs-needed 2 --no-contents $(type -p firefox)
```
* Verify a store path in the binary cache `https://cache.nixos.org/`:
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index a3a9dc698..592de773c 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -239,7 +239,7 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
if (pos != std::string::npos) {
size_t margin = 32;
auto pos2 = pos >= margin ? pos - margin : 0;
- hits[hash].emplace_back(fmt("%s: …%s…\n",
+ hits[hash].emplace_back(fmt("%s: …%s…",
p2,
hilite(filterPrintable(
std::string(contents, pos2, pos - pos2 + hash.size() + margin)),
@@ -255,7 +255,7 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
for (auto & hash : hashes) {
auto pos = target.find(hash);
if (pos != std::string::npos)
- hits[hash].emplace_back(fmt("%s -> %s\n", p2,
+ hits[hash].emplace_back(fmt("%s -> %s", p2,
hilite(target, pos, StorePath::HashLen, getColour(hash))));
}
}
@@ -272,9 +272,9 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
for (auto & hit : hits[hash]) {
bool first = hit == *hits[hash].begin();
- std::cout << tailPad
- << (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine))
- << hit;
+ logger->cout("%s%s%s", tailPad,
+ (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine)),
+ hit);
if (!all) break;
}