aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/command.cc27
-rw-r--r--src/libcmd/command.hh30
-rw-r--r--src/libcmd/editor-for.cc20
-rw-r--r--src/libcmd/editor-for.hh11
-rw-r--r--src/libcmd/installable-attr-path.cc109
-rw-r--r--src/libcmd/installable-attr-path.hh56
-rw-r--r--src/libcmd/installable-derived-path.cc70
-rw-r--r--src/libcmd/installable-derived-path.hh28
-rw-r--r--src/libcmd/installable-flake.cc236
-rw-r--r--src/libcmd/installable-flake.hh50
-rw-r--r--src/libcmd/installable-value.hh14
-rw-r--r--src/libcmd/installables.cc408
-rw-r--r--src/libcmd/installables.hh50
-rw-r--r--src/libcmd/local.mk2
-rw-r--r--src/libcmd/nix-cmd.pc.in2
-rw-r--r--src/libcmd/repl.cc128
-rw-r--r--src/libcmd/repl.hh39
-rw-r--r--src/libexpr/eval.cc2
-rw-r--r--src/libexpr/nix-expr.pc.in2
-rw-r--r--src/libexpr/nixexpr.hh6
-rw-r--r--src/libexpr/parser.y31
-rw-r--r--src/libexpr/primops.cc6
-rw-r--r--src/libexpr/primops/fetchTree.cc11
-rw-r--r--src/libfetchers/git.cc41
-rw-r--r--src/libmain/nix-main.pc.in2
-rw-r--r--src/libstore/build/local-derivation-goal.cc109
-rw-r--r--src/libstore/derivations.cc60
-rw-r--r--src/libstore/derivations.hh7
-rw-r--r--src/libstore/filetransfer.cc1
-rw-r--r--src/libstore/globals.hh21
-rw-r--r--src/libstore/http-binary-cache-store.cc2
-rw-r--r--src/libstore/nar-info-disk-cache.cc49
-rw-r--r--src/libstore/nar-info-disk-cache.hh7
-rw-r--r--src/libstore/nix-store.pc.in2
-rw-r--r--src/libstore/profiles.cc4
-rw-r--r--src/libstore/profiles.hh5
-rw-r--r--src/libstore/s3-binary-cache-store.cc2
-rw-r--r--src/libstore/sqlite.cc14
-rw-r--r--src/libstore/tests/derivation.cc124
-rw-r--r--src/libstore/tests/nar-info-disk-cache.cc123
-rw-r--r--src/libutil/args.cc10
-rw-r--r--src/libutil/namespaces.cc97
-rw-r--r--src/libutil/namespaces.hh13
-rw-r--r--src/libutil/util.cc46
-rw-r--r--src/libutil/util.hh7
-rwxr-xr-xsrc/nix-channel/nix-channel.cc4
-rw-r--r--src/nix-env/nix-env.cc7
-rw-r--r--src/nix/app.cc27
-rw-r--r--src/nix/bundle.cc1
-rw-r--r--src/nix/daemon.cc10
-rw-r--r--src/nix/develop.cc1
-rw-r--r--src/nix/diff-closures.cc2
-rw-r--r--src/nix/edit.cc1
-rw-r--r--src/nix/eval.cc4
-rw-r--r--src/nix/flake.cc1
-rw-r--r--src/nix/profile.cc1
-rw-r--r--src/nix/repl.cc95
-rw-r--r--src/nix/show-derivation.cc52
-rw-r--r--src/nix/why-depends.cc2
59 files changed, 1505 insertions, 787 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index 0740ea960..ab51c229d 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -4,6 +4,7 @@
#include "derivations.hh"
#include "nixexpr.hh"
#include "profiles.hh"
+#include "repl.hh"
#include <nlohmann/json.hpp>
@@ -121,12 +122,22 @@ ref<EvalState> EvalCommand::getEvalState()
;
if (startReplOnEvalErrors) {
- evalState->debugRepl = &runRepl;
+ evalState->debugRepl = &AbstractNixRepl::runSimple;
};
}
return ref<EvalState>(evalState);
}
+MixOperateOnOptions::MixOperateOnOptions()
+{
+ addFlag({
+ .longName = "derivation",
+ .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
+ .category = installablesCategory,
+ .handler = {&operateOn, OperateOn::Derivation},
+ });
+}
+
BuiltPathsCommand::BuiltPathsCommand(bool recursive)
: recursive(recursive)
{
@@ -208,20 +219,6 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath
run(store, *storePaths.begin());
}
-Strings editorFor(const Path & file, uint32_t line)
-{
- auto editor = getEnv("EDITOR").value_or("cat");
- auto args = tokenizeString<Strings>(editor);
- if (line > 0 && (
- editor.find("emacs") != std::string::npos ||
- editor.find("nano") != std::string::npos ||
- editor.find("vim") != std::string::npos ||
- editor.find("kak") != std::string::npos))
- args.push_back(fmt("+%d", line));
- args.push_back(file);
- return args;
-}
-
MixProfile::MixProfile()
{
addFlag({
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 3b4b40981..b6d554aab 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -94,12 +94,8 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
{
std::optional<Path> file;
std::optional<std::string> expr;
- bool readOnlyMode = false;
- // FIXME: move this; not all commands (e.g. 'nix run') use it.
- OperateOn operateOn = OperateOn::Output;
-
- SourceExprCommand(bool supportReadOnlyMode = false);
+ SourceExprCommand();
std::vector<std::shared_ptr<Installable>> parseInstallables(
ref<Store> store, std::vector<std::string> ss);
@@ -114,6 +110,11 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
void completeInstallable(std::string_view prefix);
};
+struct MixReadOnlyOption : virtual Args
+{
+ MixReadOnlyOption();
+};
+
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
struct InstallablesCommand : virtual Args, SourceExprCommand
@@ -139,7 +140,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> installable;
- InstallableCommand(bool supportReadOnlyMode = false);
+ InstallableCommand();
void prepare() override;
@@ -153,8 +154,15 @@ private:
std::string _installable{"."};
};
+struct MixOperateOnOptions : virtual Args
+{
+ OperateOn operateOn = OperateOn::Output;
+
+ MixOperateOnOptions();
+};
+
/* A command that operates on zero or more store paths. */
-struct BuiltPathsCommand : public InstallablesCommand
+struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
{
private:
@@ -227,10 +235,6 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name)
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
}
-/* Helper function to generate args that invoke $EDITOR on
- filename:lineno. */
-Strings editorFor(const Path & file, uint32_t line);
-
struct MixProfile : virtual StoreCommand
{
std::optional<Path> profile;
@@ -280,8 +284,4 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
-
-void runRepl(
- ref<EvalState> evalState,
- const ValMap & extraEnv);
}
diff --git a/src/libcmd/editor-for.cc b/src/libcmd/editor-for.cc
new file mode 100644
index 000000000..f674f32bd
--- /dev/null
+++ b/src/libcmd/editor-for.cc
@@ -0,0 +1,20 @@
+#include "util.hh"
+#include "editor-for.hh"
+
+namespace nix {
+
+Strings editorFor(const Path & file, uint32_t line)
+{
+ auto editor = getEnv("EDITOR").value_or("cat");
+ auto args = tokenizeString<Strings>(editor);
+ if (line > 0 && (
+ editor.find("emacs") != std::string::npos ||
+ editor.find("nano") != std::string::npos ||
+ editor.find("vim") != std::string::npos ||
+ editor.find("kak") != std::string::npos))
+ args.push_back(fmt("+%d", line));
+ args.push_back(file);
+ return args;
+}
+
+}
diff --git a/src/libcmd/editor-for.hh b/src/libcmd/editor-for.hh
new file mode 100644
index 000000000..8fbd08792
--- /dev/null
+++ b/src/libcmd/editor-for.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "types.hh"
+
+namespace nix {
+
+/* Helper function to generate args that invoke $EDITOR on
+ filename:lineno. */
+Strings editorFor(const Path & file, uint32_t line);
+
+}
diff --git a/src/libcmd/installable-attr-path.cc b/src/libcmd/installable-attr-path.cc
new file mode 100644
index 000000000..d9377f0d6
--- /dev/null
+++ b/src/libcmd/installable-attr-path.cc
@@ -0,0 +1,109 @@
+#include "globals.hh"
+#include "installable-attr-path.hh"
+#include "outputs-spec.hh"
+#include "util.hh"
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+#include "flake/flake.hh"
+#include "eval-cache.hh"
+#include "url.hh"
+#include "registry.hh"
+#include "build-result.hh"
+
+#include <regex>
+#include <queue>
+
+#include <nlohmann/json.hpp>
+
+namespace nix {
+
+InstallableAttrPath::InstallableAttrPath(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ const std::string & attrPath,
+ ExtendedOutputsSpec extendedOutputsSpec)
+ : InstallableValue(state)
+ , cmd(cmd)
+ , v(allocRootValue(v))
+ , attrPath(attrPath)
+ , extendedOutputsSpec(std::move(extendedOutputsSpec))
+{ }
+
+std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
+{
+ auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
+ state.forceValue(*vRes, pos);
+ return {vRes, pos};
+}
+
+DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
+{
+ auto v = toValue(*state).first;
+
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
+
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
+
+ // Backward compatibility hack: group results by drvPath. This
+ // helps keep .all output together.
+ std::map<StorePath, OutputsSpec> byDrvPath;
+
+ for (auto & drvInfo : drvInfos) {
+ auto drvPath = drvInfo.queryDrvPath();
+ if (!drvPath)
+ throw Error("'%s' is not a derivation", what());
+
+ auto newOutputs = std::visit(overloaded {
+ [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
+ std::set<std::string> outputsToInstall;
+ for (auto & output : drvInfo.queryOutputs(false, true))
+ outputsToInstall.insert(output.first);
+ return OutputsSpec::Names { std::move(outputsToInstall) };
+ },
+ [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
+ return e;
+ },
+ }, extendedOutputsSpec.raw());
+
+ auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
+
+ if (!didInsert)
+ iter->second = iter->second.union_(newOutputs);
+ }
+
+ DerivedPathsWithInfo res;
+ for (auto & [drvPath, outputs] : byDrvPath)
+ res.push_back({
+ .path = DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = outputs,
+ },
+ });
+
+ return res;
+}
+
+InstallableAttrPath InstallableAttrPath::parse(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ std::string_view prefix,
+ ExtendedOutputsSpec extendedOutputsSpec)
+{
+ return {
+ state, cmd, v,
+ prefix == "." ? "" : std::string { prefix },
+ extendedOutputsSpec
+ };
+}
+
+}
diff --git a/src/libcmd/installable-attr-path.hh b/src/libcmd/installable-attr-path.hh
new file mode 100644
index 000000000..c06132ec8
--- /dev/null
+++ b/src/libcmd/installable-attr-path.hh
@@ -0,0 +1,56 @@
+#include "globals.hh"
+#include "installable-value.hh"
+#include "outputs-spec.hh"
+#include "util.hh"
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+#include "eval-cache.hh"
+#include "url.hh"
+#include "registry.hh"
+#include "build-result.hh"
+
+#include <regex>
+#include <queue>
+
+#include <nlohmann/json.hpp>
+
+namespace nix {
+
+class InstallableAttrPath : public InstallableValue
+{
+ SourceExprCommand & cmd;
+ RootValue v;
+ std::string attrPath;
+ ExtendedOutputsSpec extendedOutputsSpec;
+
+ InstallableAttrPath(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ const std::string & attrPath,
+ ExtendedOutputsSpec extendedOutputsSpec);
+
+ std::string what() const override { return attrPath; };
+
+ std::pair<Value *, PosIdx> toValue(EvalState & state) override;
+
+ DerivedPathsWithInfo toDerivedPaths() override;
+
+public:
+
+ static InstallableAttrPath parse(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ std::string_view prefix,
+ ExtendedOutputsSpec extendedOutputsSpec);
+};
+
+}
diff --git a/src/libcmd/installable-derived-path.cc b/src/libcmd/installable-derived-path.cc
new file mode 100644
index 000000000..a9921b901
--- /dev/null
+++ b/src/libcmd/installable-derived-path.cc
@@ -0,0 +1,70 @@
+#include "installable-derived-path.hh"
+#include "derivations.hh"
+
+namespace nix {
+
+std::string InstallableDerivedPath::what() const
+{
+ return derivedPath.to_string(*store);
+}
+
+DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
+{
+ return {{.path = derivedPath, .info = {} }};
+}
+
+std::optional<StorePath> InstallableDerivedPath::getStorePath()
+{
+ return std::visit(overloaded {
+ [&](const DerivedPath::Built & bfd) {
+ return bfd.drvPath;
+ },
+ [&](const DerivedPath::Opaque & bo) {
+ return bo.path;
+ },
+ }, derivedPath.raw());
+}
+
+InstallableDerivedPath InstallableDerivedPath::parse(
+ ref<Store> store,
+ std::string_view prefix,
+ ExtendedOutputsSpec extendedOutputsSpec)
+{
+ auto derivedPath = std::visit(overloaded {
+ // If the user did not use ^, we treat the output more liberally.
+ [&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
+ // First, we accept a symlink chain or an actual store path.
+ auto storePath = store->followLinksToStorePath(prefix);
+ // Second, we see if the store path ends in `.drv` to decide what sort
+ // of derived path they want.
+ //
+ // This handling predates the `^` syntax. The `^*` in
+ // `/nix/store/hash-foo.drv^*` unambiguously means "do the
+ // `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could
+ // also unambiguously mean "do the DerivedPath::Opaque` case".
+ //
+ // Issue #7261 tracks reconsidering this `.drv` dispatching.
+ return storePath.isDerivation()
+ ? (DerivedPath) DerivedPath::Built {
+ .drvPath = std::move(storePath),
+ .outputs = OutputsSpec::All {},
+ }
+ : (DerivedPath) DerivedPath::Opaque {
+ .path = std::move(storePath),
+ };
+ },
+ // If the user did use ^, we just do exactly what is written.
+ [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
+ return DerivedPath::Built {
+ .drvPath = store->parseStorePath(prefix),
+ .outputs = outputSpec,
+ };
+ },
+ }, extendedOutputsSpec.raw());
+ return InstallableDerivedPath {
+ store,
+ std::move(derivedPath),
+ };
+}
+
+}
diff --git a/src/libcmd/installable-derived-path.hh b/src/libcmd/installable-derived-path.hh
new file mode 100644
index 000000000..042878b91
--- /dev/null
+++ b/src/libcmd/installable-derived-path.hh
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "installables.hh"
+
+namespace nix {
+
+struct InstallableDerivedPath : Installable
+{
+ ref<Store> store;
+ DerivedPath derivedPath;
+
+ InstallableDerivedPath(ref<Store> store, DerivedPath && derivedPath)
+ : store(store), derivedPath(std::move(derivedPath))
+ { }
+
+ std::string what() const override;
+
+ DerivedPathsWithInfo toDerivedPaths() override;
+
+ std::optional<StorePath> getStorePath() override;
+
+ static InstallableDerivedPath parse(
+ ref<Store> store,
+ std::string_view prefix,
+ ExtendedOutputsSpec extendedOutputsSpec);
+};
+
+}
diff --git a/src/libcmd/installable-flake.cc b/src/libcmd/installable-flake.cc
new file mode 100644
index 000000000..60a97deaf
--- /dev/null
+++ b/src/libcmd/installable-flake.cc
@@ -0,0 +1,236 @@
+#include "globals.hh"
+#include "installable-flake.hh"
+#include "installable-derived-path.hh"
+#include "outputs-spec.hh"
+#include "util.hh"
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+#include "flake/flake.hh"
+#include "eval-cache.hh"
+#include "url.hh"
+#include "registry.hh"
+#include "build-result.hh"
+
+#include <regex>
+#include <queue>
+
+#include <nlohmann/json.hpp>
+
+namespace nix {
+
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
+
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
+
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, lockedFlake, *vFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ state.forceValue(*aOutputs->value, [&]() { return aOutputs->value->determinePos(noPos); });
+
+ return aOutputs->value;
+}
+
+static std::string showAttrPaths(const std::vector<std::string> & paths)
+{
+ std::string s;
+ for (const auto & [n, i] : enumerate(paths)) {
+ if (n > 0) s += n + 1 == paths.size() ? " or " : ", ";
+ s += '\''; s += i; s += '\'';
+ }
+ return s;
+}
+
+InstallableFlake::InstallableFlake(
+ SourceExprCommand * cmd,
+ ref<EvalState> state,
+ FlakeRef && flakeRef,
+ std::string_view fragment,
+ ExtendedOutputsSpec extendedOutputsSpec,
+ Strings attrPaths,
+ Strings prefixes,
+ const flake::LockFlags & lockFlags)
+ : InstallableValue(state),
+ flakeRef(flakeRef),
+ attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
+ prefixes(fragment == "" ? Strings{} : prefixes),
+ extendedOutputsSpec(std::move(extendedOutputsSpec)),
+ lockFlags(lockFlags)
+{
+ if (cmd && cmd->getAutoArgs(*state)->size())
+ throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
+}
+
+DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
+{
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
+
+ auto attr = getCursor(*state);
+
+ auto attrPath = attr->getAttrPathStr();
+
+ if (!attr->isDerivation()) {
+
+ // FIXME: use eval cache?
+ auto v = attr->forceValue();
+
+ if (v.type() == nPath) {
+ PathSet context;
+ auto storePath = state->copyPathToStore(context, Path(v.path));
+ return {{
+ .path = DerivedPath::Opaque {
+ .path = std::move(storePath),
+ }
+ }};
+ }
+
+ else if (v.type() == nString) {
+ PathSet context;
+ auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath));
+ auto storePath = state->store->maybeParseStorePath(s);
+ if (storePath && context.count(std::string(s))) {
+ return {{
+ .path = DerivedPath::Opaque {
+ .path = std::move(*storePath),
+ }
+ }};
+ } else
+ throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s);
+ }
+
+ else
+ throw Error("flake output attribute '%s' is not a derivation or path", attrPath);
+ }
+
+ auto drvPath = attr->forceDerivation();
+
+ std::optional<NixInt> priority;
+
+ if (attr->maybeGetAttr(state->sOutputSpecified)) {
+ } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
+ if (auto aPriority = aMeta->maybeGetAttr("priority"))
+ priority = aPriority->getInt();
+ }
+
+ return {{
+ .path = DerivedPath::Built {
+ .drvPath = std::move(drvPath),
+ .outputs = std::visit(overloaded {
+ [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
+ std::set<std::string> outputsToInstall;
+ if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
+ if (aOutputSpecified->getBool()) {
+ if (auto aOutputName = attr->maybeGetAttr("outputName"))
+ outputsToInstall = { aOutputName->getString() };
+ }
+ } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
+ if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
+ for (auto & s : aOutputsToInstall->getListOfStrings())
+ outputsToInstall.insert(s);
+ }
+
+ if (outputsToInstall.empty())
+ outputsToInstall.insert("out");
+
+ return OutputsSpec::Names { std::move(outputsToInstall) };
+ },
+ [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
+ return e;
+ },
+ }, extendedOutputsSpec.raw()),
+ },
+ .info = {
+ .priority = priority,
+ .originalRef = flakeRef,
+ .resolvedRef = getLockedFlake()->flake.lockedRef,
+ .attrPath = attrPath,
+ .extendedOutputsSpec = extendedOutputsSpec,
+ }
+ }};
+}
+
+std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
+{
+ return {&getCursor(state)->forceValue(), noPos};
+}
+
+std::vector<ref<eval_cache::AttrCursor>>
+InstallableFlake::getCursors(EvalState & state)
+{
+ auto evalCache = openEvalCache(state,
+ std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
+
+ auto root = evalCache->getRoot();
+
+ std::vector<ref<eval_cache::AttrCursor>> res;
+
+ Suggestions suggestions;
+ auto attrPaths = getActualAttrPaths();
+
+ for (auto & attrPath : attrPaths) {
+ debug("trying flake output attribute '%s'", attrPath);
+
+ auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
+ if (attr) {
+ res.push_back(ref(*attr));
+ } else {
+ suggestions += attr.getSuggestions();
+ }
+ }
+
+ if (res.size() == 0)
+ throw Error(
+ suggestions,
+ "flake '%s' does not provide attribute %s",
+ flakeRef,
+ showAttrPaths(attrPaths));
+
+ return res;
+}
+
+std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
+{
+ if (!_lockedFlake) {
+ flake::LockFlags lockFlagsApplyConfig = lockFlags;
+ lockFlagsApplyConfig.applyNixConfig = true;
+ _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
+ }
+ return _lockedFlake;
+}
+
+FlakeRef InstallableFlake::nixpkgsFlakeRef() const
+{
+ auto lockedFlake = getLockedFlake();
+
+ if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
+ if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
+ debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
+ return std::move(lockedNode->lockedRef);
+ }
+ }
+
+ return Installable::nixpkgsFlakeRef();
+}
+
+}
diff --git a/src/libcmd/installable-flake.hh b/src/libcmd/installable-flake.hh
new file mode 100644
index 000000000..c75765086
--- /dev/null
+++ b/src/libcmd/installable-flake.hh
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "installable-value.hh"
+
+namespace nix {
+
+struct InstallableFlake : InstallableValue
+{
+ FlakeRef flakeRef;
+ Strings attrPaths;
+ Strings prefixes;
+ ExtendedOutputsSpec extendedOutputsSpec;
+ const flake::LockFlags & lockFlags;
+ mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
+
+ InstallableFlake(
+ SourceExprCommand * cmd,
+ ref<EvalState> state,
+ FlakeRef && flakeRef,
+ std::string_view fragment,
+ ExtendedOutputsSpec extendedOutputsSpec,
+ Strings attrPaths,
+ Strings prefixes,
+ const flake::LockFlags & lockFlags);
+
+ std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
+
+ std::vector<std::string> getActualAttrPaths();
+
+ Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
+
+ DerivedPathsWithInfo toDerivedPaths() override;
+
+ std::pair<Value *, PosIdx> toValue(EvalState & state) override;
+
+ /* Get a cursor to every attrpath in getActualAttrPaths()
+ that exists. However if none exists, throw an exception. */
+ std::vector<ref<eval_cache::AttrCursor>>
+ getCursors(EvalState & state) override;
+
+ std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
+
+ FlakeRef nixpkgsFlakeRef() const override;
+};
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake);
+
+}
diff --git a/src/libcmd/installable-value.hh b/src/libcmd/installable-value.hh
new file mode 100644
index 000000000..c6cdc4797
--- /dev/null
+++ b/src/libcmd/installable-value.hh
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "installables.hh"
+
+namespace nix {
+
+struct InstallableValue : Installable
+{
+ ref<EvalState> state;
+
+ InstallableValue(ref<EvalState> state) : state(state) {}
+};
+
+}
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 24f458f1a..00c6f9516 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -1,5 +1,8 @@
#include "globals.hh"
#include "installables.hh"
+#include "installable-derived-path.hh"
+#include "installable-attr-path.hh"
+#include "installable-flake.hh"
#include "outputs-spec.hh"
#include "util.hh"
#include "command.hh"
@@ -144,7 +147,7 @@ void MixFlakeOptions::completionHook()
completeFlakeInput(*prefix);
}
-SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
+SourceExprCommand::SourceExprCommand()
{
addFlag({
.longName = "file",
@@ -166,24 +169,18 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
.labels = {"expr"},
.handler = {&expr}
});
+}
+MixReadOnlyOption::MixReadOnlyOption()
+{
addFlag({
- .longName = "derivation",
- .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
- .category = installablesCategory,
- .handler = {&operateOn, OperateOn::Derivation},
+ .longName = "read-only",
+ .description =
+ "Do not instantiate each evaluated derivation. "
+ "This improves performance, but can cause errors when accessing "
+ "store paths of derivations during evaluation.",
+ .handler = {&settings.readOnlyMode, true},
});
-
- if (supportReadOnlyMode) {
- addFlag({
- .longName = "read-only",
- .description =
- "Do not instantiate each evaluated derivation. "
- "This improves performance, but can cause errors when accessing "
- "store paths of derivations during evaluation.",
- .handler = {&readOnlyMode, true},
- });
- }
}
Strings SourceExprCommand::getDefaultFlakeAttrPaths()
@@ -396,143 +393,6 @@ static StorePath getDeriver(
return *derivers.begin();
}
-struct InstallableStorePath : Installable
-{
- ref<Store> store;
- DerivedPath req;
-
- InstallableStorePath(ref<Store> store, DerivedPath && req)
- : store(store), req(std::move(req))
- { }
-
- std::string what() const override
- {
- return req.to_string(*store);
- }
-
- DerivedPathsWithInfo toDerivedPaths() override
- {
- return {{.path = req, .info = {} }};
- }
-
- std::optional<StorePath> getStorePath() override
- {
- return std::visit(overloaded {
- [&](const DerivedPath::Built & bfd) {
- return bfd.drvPath;
- },
- [&](const DerivedPath::Opaque & bo) {
- return bo.path;
- },
- }, req.raw());
- }
-};
-
-struct InstallableAttrPath : InstallableValue
-{
- SourceExprCommand & cmd;
- RootValue v;
- std::string attrPath;
- ExtendedOutputsSpec extendedOutputsSpec;
-
- InstallableAttrPath(
- ref<EvalState> state,
- SourceExprCommand & cmd,
- Value * v,
- const std::string & attrPath,
- ExtendedOutputsSpec extendedOutputsSpec)
- : InstallableValue(state)
- , cmd(cmd)
- , v(allocRootValue(v))
- , attrPath(attrPath)
- , extendedOutputsSpec(std::move(extendedOutputsSpec))
- { }
-
- std::string what() const override { return attrPath; }
-
- std::pair<Value *, PosIdx> toValue(EvalState & state) override
- {
- auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
- state.forceValue(*vRes, pos);
- return {vRes, pos};
- }
-
- DerivedPathsWithInfo toDerivedPaths() override
- {
- auto v = toValue(*state).first;
-
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
-
- DrvInfos drvInfos;
- getDerivations(*state, *v, "", autoArgs, drvInfos, false);
-
- // Backward compatibility hack: group results by drvPath. This
- // helps keep .all output together.
- std::map<StorePath, OutputsSpec> byDrvPath;
-
- for (auto & drvInfo : drvInfos) {
- auto drvPath = drvInfo.queryDrvPath();
- if (!drvPath)
- throw Error("'%s' is not a derivation", what());
-
- auto newOutputs = std::visit(overloaded {
- [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
- std::set<std::string> outputsToInstall;
- for (auto & output : drvInfo.queryOutputs(false, true))
- outputsToInstall.insert(output.first);
- return OutputsSpec::Names { std::move(outputsToInstall) };
- },
- [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
- return e;
- },
- }, extendedOutputsSpec.raw());
-
- auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
-
- if (!didInsert)
- iter->second = iter->second.union_(newOutputs);
- }
-
- DerivedPathsWithInfo res;
- for (auto & [drvPath, outputs] : byDrvPath)
- res.push_back({
- .path = DerivedPath::Built {
- .drvPath = drvPath,
- .outputs = outputs,
- },
- });
-
- return res;
- }
-};
-
-std::vector<std::string> InstallableFlake::getActualAttrPaths()
-{
- std::vector<std::string> res;
-
- for (auto & prefix : prefixes)
- res.push_back(prefix + *attrPaths.begin());
-
- for (auto & s : attrPaths)
- res.push_back(s);
-
- return res;
-}
-
-Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
-{
- auto vFlake = state.allocValue();
-
- callFlake(state, lockedFlake, *vFlake);
-
- auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
- assert(aOutputs);
-
- state.forceValue(*aOutputs->value, [&]() { return aOutputs->value->determinePos(noPos); });
-
- return aOutputs->value;
-}
-
ref<eval_cache::EvalCache> openEvalCache(
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake)
@@ -562,196 +422,11 @@ ref<eval_cache::EvalCache> openEvalCache(
});
}
-static std::string showAttrPaths(const std::vector<std::string> & paths)
-{
- std::string s;
- for (const auto & [n, i] : enumerate(paths)) {
- if (n > 0) s += n + 1 == paths.size() ? " or " : ", ";
- s += '\''; s += i; s += '\'';
- }
- return s;
-}
-
-InstallableFlake::InstallableFlake(
- SourceExprCommand * cmd,
- ref<EvalState> state,
- FlakeRef && flakeRef,
- std::string_view fragment,
- ExtendedOutputsSpec extendedOutputsSpec,
- Strings attrPaths,
- Strings prefixes,
- const flake::LockFlags & lockFlags)
- : InstallableValue(state),
- flakeRef(flakeRef),
- attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
- prefixes(fragment == "" ? Strings{} : prefixes),
- extendedOutputsSpec(std::move(extendedOutputsSpec)),
- lockFlags(lockFlags)
-{
- if (cmd && cmd->getAutoArgs(*state)->size())
- throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
-}
-
-DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
-{
- Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
-
- auto attr = getCursor(*state);
-
- auto attrPath = attr->getAttrPathStr();
-
- if (!attr->isDerivation()) {
-
- // FIXME: use eval cache?
- auto v = attr->forceValue();
-
- if (v.type() == nPath) {
- PathSet context;
- auto storePath = state->copyPathToStore(context, Path(v.path));
- return {{
- .path = DerivedPath::Opaque {
- .path = std::move(storePath),
- }
- }};
- }
-
- else if (v.type() == nString) {
- PathSet context;
- auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath));
- auto storePath = state->store->maybeParseStorePath(s);
- if (storePath && context.count(std::string(s))) {
- return {{
- .path = DerivedPath::Opaque {
- .path = std::move(*storePath),
- }
- }};
- } else
- throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s);
- }
-
- else
- throw Error("flake output attribute '%s' is not a derivation or path", attrPath);
- }
-
- auto drvPath = attr->forceDerivation();
-
- std::optional<NixInt> priority;
-
- if (attr->maybeGetAttr(state->sOutputSpecified)) {
- } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
- if (auto aPriority = aMeta->maybeGetAttr("priority"))
- priority = aPriority->getInt();
- }
-
- return {{
- .path = DerivedPath::Built {
- .drvPath = std::move(drvPath),
- .outputs = std::visit(overloaded {
- [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
- std::set<std::string> outputsToInstall;
- if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
- if (aOutputSpecified->getBool()) {
- if (auto aOutputName = attr->maybeGetAttr("outputName"))
- outputsToInstall = { aOutputName->getString() };
- }
- } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
- if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
- for (auto & s : aOutputsToInstall->getListOfStrings())
- outputsToInstall.insert(s);
- }
-
- if (outputsToInstall.empty())
- outputsToInstall.insert("out");
-
- return OutputsSpec::Names { std::move(outputsToInstall) };
- },
- [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
- return e;
- },
- }, extendedOutputsSpec.raw()),
- },
- .info = {
- .priority = priority,
- .originalRef = flakeRef,
- .resolvedRef = getLockedFlake()->flake.lockedRef,
- .attrPath = attrPath,
- .extendedOutputsSpec = extendedOutputsSpec,
- }
- }};
-}
-
-std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
-{
- return {&getCursor(state)->forceValue(), noPos};
-}
-
-std::vector<ref<eval_cache::AttrCursor>>
-InstallableFlake::getCursors(EvalState & state)
-{
- auto evalCache = openEvalCache(state,
- std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
-
- auto root = evalCache->getRoot();
-
- std::vector<ref<eval_cache::AttrCursor>> res;
-
- Suggestions suggestions;
- auto attrPaths = getActualAttrPaths();
-
- for (auto & attrPath : attrPaths) {
- debug("trying flake output attribute '%s'", attrPath);
-
- auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
- if (attr) {
- res.push_back(ref(*attr));
- } else {
- suggestions += attr.getSuggestions();
- }
- }
-
- if (res.size() == 0)
- throw Error(
- suggestions,
- "flake '%s' does not provide attribute %s",
- flakeRef,
- showAttrPaths(attrPaths));
-
- return res;
-}
-
-std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
-{
- if (!_lockedFlake) {
- flake::LockFlags lockFlagsApplyConfig = lockFlags;
- lockFlagsApplyConfig.applyNixConfig = true;
- _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
- }
- return _lockedFlake;
-}
-
-FlakeRef InstallableFlake::nixpkgsFlakeRef() const
-{
- auto lockedFlake = getLockedFlake();
-
- if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
- if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
- debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
- return std::move(lockedNode->lockedRef);
- }
- }
-
- return Installable::nixpkgsFlakeRef();
-}
-
std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
ref<Store> store, std::vector<std::string> ss)
{
std::vector<std::shared_ptr<Installable>> result;
- if (readOnlyMode) {
- settings.readOnlyMode = true;
- }
-
if (file || expr) {
if (file && expr)
throw UsageError("'--file' and '--expr' are exclusive");
@@ -777,9 +452,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back(
std::make_shared<InstallableAttrPath>(
- state, *this, vFile,
- prefix == "." ? "" : std::string { prefix },
- extendedOutputsSpec));
+ InstallableAttrPath::parse(
+ state, *this, vFile, prefix, extendedOutputsSpec)));
}
} else {
@@ -792,41 +466,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
auto prefix = std::move(prefix_);
auto extendedOutputsSpec = std::move(extendedOutputsSpec_);
- auto found = prefix.find('/');
- if (found != std::string::npos) {
+ if (prefix.find('/') != std::string::npos) {
try {
- auto derivedPath = std::visit(overloaded {
- // If the user did not use ^, we treat the output more liberally.
- [&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
- // First, we accept a symlink chain or an actual store path.
- auto storePath = store->followLinksToStorePath(prefix);
- // Second, we see if the store path ends in `.drv` to decide what sort
- // of derived path they want.
- //
- // This handling predates the `^` syntax. The `^*` in
- // `/nix/store/hash-foo.drv^*` unambiguously means "do the
- // `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could
- // also unambiguously mean "do the DerivedPath::Opaque` case".
- //
- // Issue #7261 tracks reconsidering this `.drv` dispatching.
- return storePath.isDerivation()
- ? (DerivedPath) DerivedPath::Built {
- .drvPath = std::move(storePath),
- .outputs = OutputsSpec::All {},
- }
- : (DerivedPath) DerivedPath::Opaque {
- .path = std::move(storePath),
- };
- },
- // If the user did use ^, we just do exactly what is written.
- [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
- return DerivedPath::Built {
- .drvPath = store->parseStorePath(prefix),
- .outputs = outputSpec,
- };
- },
- }, extendedOutputsSpec.raw());
- result.push_back(std::make_shared<InstallableStorePath>(store, std::move(derivedPath)));
+ result.push_back(std::make_shared<InstallableDerivedPath>(
+ InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec)));
continue;
} catch (BadStorePath &) {
} catch (...) {
@@ -1062,8 +705,8 @@ void InstallablesCommand::prepare()
installables = load();
}
-Installables InstallablesCommand::load() {
- Installables installables;
+Installables InstallablesCommand::load()
+{
if (_installables.empty() && useDefaultInstallables())
// FIXME: commands like "nix profile install" should not have a
// default, probably.
@@ -1073,16 +716,13 @@ Installables InstallablesCommand::load() {
std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
{
- if (_installables.empty()) {
- if (useDefaultInstallables())
- return {"."};
- return {};
- }
+ if (_installables.empty() && useDefaultInstallables())
+ return {"."};
return _installables;
}
-InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
- : SourceExprCommand(supportReadOnlyMode)
+InstallableCommand::InstallableCommand()
+ : SourceExprCommand()
{
expectArgs({
.label = "installable",
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index da6a3addd..be77fdc81 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -161,54 +161,4 @@ struct Installable
typedef std::vector<std::shared_ptr<Installable>> Installables;
-struct InstallableValue : Installable
-{
- ref<EvalState> state;
-
- InstallableValue(ref<EvalState> state) : state(state) {}
-};
-
-struct InstallableFlake : InstallableValue
-{
- FlakeRef flakeRef;
- Strings attrPaths;
- Strings prefixes;
- ExtendedOutputsSpec extendedOutputsSpec;
- const flake::LockFlags & lockFlags;
- mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
-
- InstallableFlake(
- SourceExprCommand * cmd,
- ref<EvalState> state,
- FlakeRef && flakeRef,
- std::string_view fragment,
- ExtendedOutputsSpec extendedOutputsSpec,
- Strings attrPaths,
- Strings prefixes,
- const flake::LockFlags & lockFlags);
-
- std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
-
- std::vector<std::string> getActualAttrPaths();
-
- Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
-
- DerivedPathsWithInfo toDerivedPaths() override;
-
- std::pair<Value *, PosIdx> toValue(EvalState & state) override;
-
- /* Get a cursor to every attrpath in getActualAttrPaths()
- that exists. However if none exists, throw an exception. */
- std::vector<ref<eval_cache::AttrCursor>>
- getCursors(EvalState & state) override;
-
- std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
-
- FlakeRef nixpkgsFlakeRef() const override;
-};
-
-ref<eval_cache::EvalCache> openEvalCache(
- EvalState & state,
- std::shared_ptr<flake::LockedFlake> lockedFlake);
-
}
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
index 152bc388d..541a7d2ba 100644
--- a/src/libcmd/local.mk
+++ b/src/libcmd/local.mk
@@ -6,7 +6,7 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc)
-libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
+libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) -pthread
diff --git a/src/libcmd/nix-cmd.pc.in b/src/libcmd/nix-cmd.pc.in
index a21d93f1d..39575f222 100644
--- a/src/libcmd/nix-cmd.pc.in
+++ b/src/libcmd/nix-cmd.pc.in
@@ -6,4 +6,4 @@ Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixcmd
-Cflags: -I${includedir}/nix -std=c++20
+Cflags: -I${includedir}/nix -std=c++2a
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 4158439b6..e3afb1531 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -19,6 +19,8 @@ extern "C" {
}
#endif
+#include "repl.hh"
+
#include "ansicolor.hh"
#include "shared.hh"
#include "eval.hh"
@@ -31,7 +33,9 @@ extern "C" {
#include "get-drvs.hh"
#include "derivations.hh"
#include "globals.hh"
-#include "command.hh"
+#include "flake/flake.hh"
+#include "flake/lockfile.hh"
+#include "editor-for.hh"
#include "finally.hh"
#include "markdown.hh"
#include "local-fs-store.hh"
@@ -45,18 +49,16 @@ extern "C" {
namespace nix {
struct NixRepl
+ : AbstractNixRepl
#if HAVE_BOEHMGC
- : gc
+ , gc
#endif
{
std::string curDir;
- ref<EvalState> state;
- Bindings * autoArgs;
size_t debugTraceIndex;
Strings loadedFiles;
- typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
std::function<AnnotatedValues()> getValues;
const static int envSize = 32768;
@@ -69,8 +71,11 @@ struct NixRepl
NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
- ~NixRepl();
- void mainLoop();
+ virtual ~NixRepl();
+
+ void mainLoop() override;
+ void initEnv() override;
+
StringSet completePrefix(const std::string & prefix);
bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v);
@@ -78,7 +83,6 @@ struct NixRepl
void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef);
- void initEnv();
void loadFiles();
void reloadFiles();
void addAttrsToScope(Value & attrs);
@@ -92,7 +96,6 @@ struct NixRepl
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
};
-
std::string removeWhitespace(std::string s)
{
s = chomp(s);
@@ -104,7 +107,7 @@ std::string removeWhitespace(std::string s)
NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<NixRepl::AnnotatedValues()> getValues)
- : state(state)
+ : AbstractNixRepl(state)
, debugTraceIndex(0)
, getValues(getValues)
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
@@ -1029,8 +1032,22 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
return str;
}
-void runRepl(
- ref<EvalState>evalState,
+
+std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
+ const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ std::function<AnnotatedValues()> getValues)
+{
+ return std::make_unique<NixRepl>(
+ searchPath,
+ openStore(),
+ state,
+ getValues
+ );
+}
+
+
+void AbstractNixRepl::runSimple(
+ ref<EvalState> evalState,
const ValMap & extraEnv)
{
auto getValues = [&]()->NixRepl::AnnotatedValues{
@@ -1054,91 +1071,4 @@ void runRepl(
repl->mainLoop();
}
-struct CmdRepl : InstallablesCommand
-{
- CmdRepl() {
- evalSettings.pureEval = false;
- }
-
- void prepare() override
- {
- if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
- warn("future versions of Nix will require using `--file` to load a file");
- if (this->_installables.size() > 1)
- warn("more than one input file is not currently supported");
- auto filePath = this->_installables[0].data();
- file = std::optional(filePath);
- _installables.front() = _installables.back();
- _installables.pop_back();
- }
- installables = InstallablesCommand::load();
- }
-
- std::vector<std::string> files;
-
- Strings getDefaultFlakeAttrPaths() override
- {
- return {""};
- }
-
- bool useDefaultInstallables() override
- {
- return file.has_value() or expr.has_value();
- }
-
- bool forceImpureByDefault() override
- {
- return true;
- }
-
- std::string description() override
- {
- return "start an interactive environment for evaluating Nix expressions";
- }
-
- std::string doc() override
- {
- return
- #include "repl.md"
- ;
- }
-
- void run(ref<Store> store) override
- {
- auto state = getEvalState();
- auto getValues = [&]()->NixRepl::AnnotatedValues{
- auto installables = load();
- NixRepl::AnnotatedValues values;
- for (auto & installable: installables){
- auto what = installable->what();
- if (file){
- auto [val, pos] = installable->toValue(*state);
- auto what = installable->what();
- state->forceValue(*val, pos);
- auto autoArgs = getAutoArgs(*state);
- auto valPost = state->allocValue();
- state->autoCallFunction(*autoArgs, *val, *valPost);
- state->forceValue(*valPost, pos);
- values.push_back( {valPost, what });
- } else {
- auto [val, pos] = installable->toValue(*state);
- values.push_back( {val, what} );
- }
- }
- return values;
- };
- auto repl = std::make_unique<NixRepl>(
- searchPath,
- openStore(),
- state,
- getValues
- );
- repl->autoArgs = getAutoArgs(*repl->state);
- repl->initEnv();
- repl->mainLoop();
- }
-};
-
-static auto rCmdRepl = registerCommand<CmdRepl>("repl");
-
}
diff --git a/src/libcmd/repl.hh b/src/libcmd/repl.hh
new file mode 100644
index 000000000..dfccc93e7
--- /dev/null
+++ b/src/libcmd/repl.hh
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "eval.hh"
+
+#if HAVE_BOEHMGC
+#define GC_INCLUDE_NEW
+#include <gc/gc_cpp.h>
+#endif
+
+namespace nix {
+
+struct AbstractNixRepl
+{
+ ref<EvalState> state;
+ Bindings * autoArgs;
+
+ AbstractNixRepl(ref<EvalState> state)
+ : state(state)
+ { }
+
+ virtual ~AbstractNixRepl()
+ { }
+
+ typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
+
+ static std::unique_ptr<AbstractNixRepl> create(
+ const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ std::function<AnnotatedValues()> getValues);
+
+ static void runSimple(
+ ref<EvalState> evalState,
+ const ValMap & extraEnv);
+
+ virtual void initEnv() = 0;
+
+ virtual void mainLoop() = 0;
+};
+
+}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index a48968656..3e37c7f60 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -2496,7 +2496,7 @@ Strings EvalSettings::getDefaultNixPath()
res.push_back(s ? *s + "=" + p : p);
};
- add(getHome() + "/.nix-defexpr/channels");
+ add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels");
add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
add(settings.nixStateDir + "/profiles/per-user/root/channels");
diff --git a/src/libexpr/nix-expr.pc.in b/src/libexpr/nix-expr.pc.in
index 95d452ca8..60ffb5dba 100644
--- a/src/libexpr/nix-expr.pc.in
+++ b/src/libexpr/nix-expr.pc.in
@@ -7,4 +7,4 @@ Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Requires: nix-store bdw-gc
Libs: -L${libdir} -lnixexpr
-Cflags: -I${includedir}/nix -std=c++20
+Cflags: -I${includedir}/nix -std=c++2a
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index ffe67f97d..4a81eaa47 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -186,7 +186,7 @@ struct ExprString : Expr
{
std::string s;
Value v;
- ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
+ ExprString(std::string &&s) : s(std::move(s)) { v.mkString(this->s.data()); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
@@ -233,7 +233,7 @@ struct ExprSelect : Expr
PosIdx pos;
Expr * e, * def;
AttrPath attrPath;
- ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
+ ExprSelect(const PosIdx & pos, Expr * e, const AttrPath && attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
@@ -243,7 +243,7 @@ struct ExprOpHasAttr : Expr
{
Expr * e;
AttrPath attrPath;
- ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { };
+ ExprOpHasAttr(Expr * e, const AttrPath && attrPath) : e(e), attrPath(std::move(attrPath)) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index ffb364a90..dec5818fc 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -90,7 +90,7 @@ static void dupAttr(const EvalState & state, Symbol attr, const PosIdx pos, cons
}
-static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
+static void addAttr(ExprAttrs * attrs, AttrPath && attrPath,
Expr * e, const PosIdx pos, const nix::EvalState & state)
{
AttrPath::iterator i;
@@ -188,7 +188,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
- std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> & es)
+ std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
{
if (es.empty()) return new ExprString("");
@@ -268,7 +268,7 @@ static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
s2 = std::string(s2, 0, p + 1);
}
- es2->emplace_back(i->first, new ExprString(s2));
+ es2->emplace_back(i->first, new ExprString(std::move(s2)));
};
for (; i != es.end(); ++i, --n) {
std::visit(overloaded { trimExpr, trimString }, i->second);
@@ -408,7 +408,7 @@ expr_op
| expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
- | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
+ | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, std::move(*$3)); delete $3; }
| expr_op '+' expr_op
{ $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
@@ -431,14 +431,14 @@ expr_app
expr_select
: expr_simple '.' attrpath
- { $$ = new ExprSelect(CUR_POS, $1, *$3, 0); }
+ { $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
| expr_simple '.' attrpath OR_KW expr_select
- { $$ = new ExprSelect(CUR_POS, $1, *$3, $5); }
+ { $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
| /* Backwards compatibility: because Nixpkgs has a rarely used
function named ‘or’, allow stuff like ‘map or [...]’. */
expr_simple OR_KW
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, data->symbols.create("or"))}); }
- | expr_simple { $$ = $1; }
+ | expr_simple
;
expr_simple
@@ -453,9 +453,10 @@ expr_simple
| FLOAT { $$ = new ExprFloat($1); }
| '"' string_parts '"' { $$ = $2; }
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
- $$ = stripIndentation(CUR_POS, data->symbols, *$2);
+ $$ = stripIndentation(CUR_POS, data->symbols, std::move(*$2));
+ delete $2;
}
- | path_start PATH_END { $$ = $1; }
+ | path_start PATH_END
| path_start string_parts_interpolated PATH_END {
$2->insert($2->begin(), {makeCurPos(@1, data), $1});
$$ = new ExprConcatStrings(CUR_POS, false, $2);
@@ -465,7 +466,7 @@ expr_simple
$$ = new ExprCall(CUR_POS,
new ExprVar(data->symbols.create("__findFile")),
{new ExprVar(data->symbols.create("__nixPath")),
- new ExprString(path)});
+ new ExprString(std::move(path))});
}
| URI {
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
@@ -533,7 +534,7 @@ ind_string_parts
;
binds
- : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); }
+ : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, std::move(*$2), $4, makeCurPos(@2, data), data->state); delete $2; }
| binds INHERIT attrs ';'
{ $$ = $1;
for (auto & i : *$3) {
@@ -542,6 +543,7 @@ binds
auto pos = makeCurPos(@3, data);
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true));
}
+ delete $3;
}
| binds INHERIT '(' expr ')' attrs ';'
{ $$ = $1;
@@ -551,6 +553,7 @@ binds
dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
}
+ delete $6;
}
| { $$ = new ExprAttrs(makeCurPos(@0, data)); }
;
@@ -596,7 +599,7 @@ attrpath
;
attr
- : ID { $$ = $1; }
+ : ID
| OR_KW { $$ = {"or", 2}; }
;
@@ -612,9 +615,9 @@ expr_list
formals
: formal ',' formals
- { $$ = $3; $$->formals.push_back(*$1); }
+ { $$ = $3; $$->formals.emplace_back(*$1); delete $1; }
| formal
- { $$ = new ParserFormals; $$->formals.push_back(*$1); $$->ellipsis = false; }
+ { $$ = new ParserFormals; $$->formals.emplace_back(*$1); $$->ellipsis = false; delete $1; }
|
{ $$ = new ParserFormals; $$->ellipsis = false; }
| ELLIPSIS
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index c6f41c4ca..fb7fc3ddb 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -3032,9 +3032,9 @@ static RegisterPrimOp primop_foldlStrict({
.doc = R"(
Reduce a list by applying a binary operator, from left to right,
e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2)
- ...`. The operator is applied strictly, i.e., its arguments are
- evaluated first. For example, `foldl' (x: y: x + y) 0 [1 2 3]`
- evaluates to 6.
+ ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.
+ The return value of each application of `op` is evaluated immediately,
+ even for intermediate values.
)",
.fun = prim_foldlStrict,
});
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 83d93b75c..2e924c302 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -456,6 +456,17 @@ static RegisterPrimOp primop_fetchGit({
> **Note**
>
> This behavior is disabled in *Pure evaluation mode*.
+
+ - To fetch the content of a checked-out work directory:
+
+ ```nix
+ builtins.fetchGit ./work-dir
+ ```
+
+ If the URL points to a local directory, and no `ref` or `rev` is
+ given, `fetchGit` will use the current content of the checked-out
+ files, even if they are not committed or added to Git's index. It will
+ only consider files added to the Git repository, as listed by `git ls-files`.
)",
.fun = prim_fetchGit,
});
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 1f7d7c07d..309a143f5 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -615,15 +615,42 @@ struct GitInputScheme : InputScheme
AutoDelete delTmpGitDir(tmpGitDir, true);
runProgram("git", true, { "-c", "init.defaultBranch=" + gitInitialBranch, "init", tmpDir, "--separate-git-dir", tmpGitDir });
- // TODO: repoDir might lack the ref (it only checks if rev
- // exists, see FIXME above) so use a big hammer and fetch
- // everything to ensure we get the rev.
- runProgram("git", true, { "-C", tmpDir, "fetch", "--quiet", "--force",
- "--update-head-ok", "--", repoDir, "refs/*:refs/*" });
+
+ {
+ // TODO: repoDir might lack the ref (it only checks if rev
+ // exists, see FIXME above) so use a big hammer and fetch
+ // everything to ensure we get the rev.
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("making temporary clone of '%s'", repoDir));
+ runProgram("git", true, { "-C", tmpDir, "fetch", "--quiet", "--force",
+ "--update-head-ok", "--", repoDir, "refs/*:refs/*" });
+ }
runProgram("git", true, { "-C", tmpDir, "checkout", "--quiet", input.getRev()->gitRev() });
- runProgram("git", true, { "-C", tmpDir, "remote", "add", "origin", actualUrl });
- runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" });
+
+ /* Ensure that we use the correct origin for fetching
+ submodules. This matters for submodules with relative
+ URLs. */
+ if (isLocal) {
+ writeFile(tmpGitDir + "/config", readFile(repoDir + "/" + gitDir + "/config"));
+
+ /* Restore the config.bare setting we may have just
+ copied erroneously from the user's repo. */
+ runProgram("git", true, { "-C", tmpDir, "config", "core.bare", "false" });
+ } else
+ runProgram("git", true, { "-C", tmpDir, "config", "remote.origin.url", actualUrl });
+
+ /* As an optimisation, copy the modules directory of the
+ source repo if it exists. */
+ auto modulesPath = repoDir + "/" + gitDir + "/modules";
+ if (pathExists(modulesPath)) {
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("copying submodules of '%s'", actualUrl));
+ runProgram("cp", true, { "-R", "--", modulesPath, tmpGitDir + "/modules" });
+ }
+
+ {
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching submodules of '%s'", actualUrl));
+ runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" });
+ }
filter = isNotDotGitDirectory;
} else {
diff --git a/src/libmain/nix-main.pc.in b/src/libmain/nix-main.pc.in
index b46ce1990..fb3ead6fa 100644
--- a/src/libmain/nix-main.pc.in
+++ b/src/libmain/nix-main.pc.in
@@ -6,4 +6,4 @@ Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixmain
-Cflags: -I${includedir}/nix -std=c++20
+Cflags: -I${includedir}/nix -std=c++2a
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 8ff83f748..a961d8eed 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -16,6 +16,7 @@
#include "json-utils.hh"
#include "cgroup.hh"
#include "personality.hh"
+#include "namespaces.hh"
#include <regex>
#include <queue>
@@ -167,7 +168,8 @@ void LocalDerivationGoal::killSandbox(bool getStats)
}
-void LocalDerivationGoal::tryLocalBuild() {
+void LocalDerivationGoal::tryLocalBuild()
+{
unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= settings.maxBuildJobs) {
state = &DerivationGoal::tryToBuild;
@@ -205,6 +207,17 @@ void LocalDerivationGoal::tryLocalBuild() {
#endif
}
+ #if __linux__
+ if (useChroot) {
+ if (!mountAndPidNamespacesSupported()) {
+ if (!settings.sandboxFallback)
+ throw Error("this system does not support the kernel namespaces that are required for sandboxing; use '--no-sandbox' to disable sandboxing");
+ debug("auto-disabling sandboxing because the prerequisite namespaces are not available");
+ useChroot = false;
+ }
+ }
+ #endif
+
if (useBuildUsers()) {
if (!buildUser)
buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot);
@@ -372,12 +385,6 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
}
-int childEntry(void * arg)
-{
- ((LocalDerivationGoal *) arg)->runChild();
- return 1;
-}
-
#if __linux__
static void linkOrCopy(const Path & from, const Path & to)
{
@@ -663,7 +670,8 @@ void LocalDerivationGoal::startBuilder()
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir + "/etc");
- chownToBuilder(chrootRootDir + "/etc");
+ if (parsedDrv->useUidRange())
+ chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
@@ -888,12 +896,7 @@ void LocalDerivationGoal::startBuilder()
userNamespaceSync.create();
- Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
- static bool userNamespacesEnabled =
- pathExists(maxUserNamespaces)
- && trim(readFile(maxUserNamespaces)) != "0";
-
- usingUserNamespace = userNamespacesEnabled;
+ usingUserNamespace = userNamespacesSupported();
Pid helper = startProcess([&]() {
@@ -908,76 +911,21 @@ void LocalDerivationGoal::startBuilder()
if (getuid() == 0 && setgroups(0, 0) == -1)
throw SysError("setgroups failed");
- size_t stackSize = 1 * 1024 * 1024;
- char * stack = (char *) mmap(0, stackSize,
- PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
- if (stack == MAP_FAILED) throw SysError("allocating stack");
-
- int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
+ ProcessOptions options;
+ options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork)
- flags |= CLONE_NEWNET;
+ options.cloneFlags |= CLONE_NEWNET;
if (usingUserNamespace)
- flags |= CLONE_NEWUSER;
-
- pid_t child = clone(childEntry, stack + stackSize, flags, this);
- if (child == -1 && errno == EINVAL) {
- /* Fallback for Linux < 2.13 where CLONE_NEWPID and
- CLONE_PARENT are not allowed together. */
- flags &= ~CLONE_NEWPID;
- child = clone(childEntry, stack + stackSize, flags, this);
- }
- if (usingUserNamespace && child == -1 && (errno == EPERM || errno == EINVAL)) {
- /* Some distros patch Linux to not allow unprivileged
- * user namespaces. If we get EPERM or EINVAL, try
- * without CLONE_NEWUSER and see if that works.
- * Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
- */
- usingUserNamespace = false;
- flags &= ~CLONE_NEWUSER;
- child = clone(childEntry, stack + stackSize, flags, this);
- }
- if (child == -1) {
- switch(errno) {
- case EPERM:
- case EINVAL: {
- int errno_ = errno;
- if (!userNamespacesEnabled && errno==EPERM)
- notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
- if (userNamespacesEnabled) {
- Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
- if (pathExists(procSysKernelUnprivilegedUsernsClone)
- && trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
- notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
- }
- }
- Path procSelfNsUser = "/proc/self/ns/user";
- if (!pathExists(procSelfNsUser))
- notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
- /* Otherwise exit with EPERM so we can handle this in the
- parent. This is only done when sandbox-fallback is set
- to true (the default). */
- if (settings.sandboxFallback)
- _exit(1);
- /* Mention sandbox-fallback in the error message so the user
- knows that having it disabled contributed to the
- unrecoverability of this failure */
- throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
- }
- default:
- throw SysError("creating sandboxed builder process using clone()");
- }
- }
+ options.cloneFlags |= CLONE_NEWUSER;
+
+ pid_t child = startProcess([&]() { runChild(); }, options);
+
writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child));
_exit(0);
});
- int res = helper.wait();
- if (res != 0 && settings.sandboxFallback) {
- useChroot = false;
- initTmpDir();
- goto fallback;
- } else if (res != 0)
+ if (helper.wait() != 0)
throw Error("unable to start build process");
userNamespaceSync.readSide = -1;
@@ -1045,9 +993,6 @@ void LocalDerivationGoal::startBuilder()
} else
#endif
{
-#if __linux__
- fallback:
-#endif
pid = startProcess([&]() {
runChild();
});
@@ -1906,6 +1851,10 @@ void LocalDerivationGoal::runChild()
}
}
+ /* Make /etc unwritable */
+ if (!parsedDrv->useUidRange())
+ chmod_(chrootRootDir + "/etc", 0555);
+
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index cf18724ef..05dc9a3cc 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -5,6 +5,7 @@
#include "worker-protocol.hh"
#include "fs-accessor.hh"
#include <boost/container/small_vector.hpp>
+#include <nlohmann/json.hpp>
namespace nix {
@@ -890,4 +891,63 @@ std::optional<BasicDerivation> Derivation::tryResolve(
const Hash impureOutputHash = hashString(htSHA256, "impure");
+nlohmann::json DerivationOutput::toJSON(
+ const Store & store, std::string_view drvName, std::string_view outputName) const
+{
+ nlohmann::json res = nlohmann::json::object();
+ std::visit(overloaded {
+ [&](const DerivationOutput::InputAddressed & doi) {
+ res["path"] = store.printStorePath(doi.path);
+ },
+ [&](const DerivationOutput::CAFixed & dof) {
+ res["path"] = store.printStorePath(dof.path(store, drvName, outputName));
+ res["hashAlgo"] = dof.hash.printMethodAlgo();
+ res["hash"] = dof.hash.hash.to_string(Base16, false);
+ },
+ [&](const DerivationOutput::CAFloating & dof) {
+ res["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType);
+ },
+ [&](const DerivationOutput::Deferred &) {},
+ [&](const DerivationOutput::Impure & doi) {
+ res["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType);
+ res["impure"] = true;
+ },
+ }, raw());
+ return res;
+}
+
+nlohmann::json Derivation::toJSON(const Store & store) const
+{
+ nlohmann::json res = nlohmann::json::object();
+
+ {
+ nlohmann::json & outputsObj = res["outputs"];
+ outputsObj = nlohmann::json::object();
+ for (auto & [outputName, output] : outputs) {
+ outputsObj[outputName] = output.toJSON(store, name, outputName);
+ }
+ }
+
+ {
+ auto& inputsList = res["inputSrcs"];
+ inputsList = nlohmann::json ::array();
+ for (auto & input : inputSrcs)
+ inputsList.emplace_back(store.printStorePath(input));
+ }
+
+ {
+ auto& inputDrvsObj = res["inputDrvs"];
+ inputDrvsObj = nlohmann::json ::object();
+ for (auto & input : inputDrvs)
+ inputDrvsObj[store.printStorePath(input.first)] = input.second;
+ }
+
+ res["system"] = platform;
+ res["builder"] = builder;
+ res["args"] = args;
+ res["env"] = env;
+
+ return res;
+}
+
}
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index f42c13cdc..8456b29e7 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -83,6 +83,11 @@ struct DerivationOutput : _DerivationOutputRaw
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
+
+ nlohmann::json toJSON(
+ const Store & store,
+ std::string_view drvName,
+ std::string_view outputName) const;
};
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
@@ -210,6 +215,8 @@ struct Derivation : BasicDerivation
Derivation() = default;
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
+
+ nlohmann::json toJSON(const Store & store) const;
};
diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc
index 756bd4423..b25089ec3 100644
--- a/src/libstore/filetransfer.cc
+++ b/src/libstore/filetransfer.cc
@@ -101,6 +101,7 @@ struct curlFileTransfer : public FileTransfer
this->result.data.append(data);
})
{
+ requestHeaders = curl_slist_append(requestHeaders, "Accept-Encoding: zstd, br, gzip, deflate, bzip2, xz");
if (!request.expectedETag.empty())
requestHeaders = curl_slist_append(requestHeaders, ("If-None-Match: " + request.expectedETag).c_str());
if (!request.mimeType.empty())
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 42981219d..0a4912f67 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -945,6 +945,27 @@ public:
resolves to a different location from that of the build machine. You
can enable this setting if you are sure you're not going to do that.
)"};
+
+ Setting<bool> useXDGBaseDirectories{
+ this, false, "use-xdg-base-directories",
+ R"(
+ If set to `true`, Nix will conform to the [XDG Base Directory Specification] for files in `$HOME`.
+ The environment variables used to implement this are documented in the [Environment Variables section](@docroot@/installation/env-variables.md).
+
+ [XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+ > **Warning**
+ > This changes the location of some well-known symlinks that Nix creates, which might break tools that rely on the old, non-XDG-conformant locations.
+
+ In particular, the following locations change:
+
+ | Old | New |
+ |-------------------|--------------------------------|
+ | `~/.nix-profile` | `$XDG_STATE_HOME/nix/profile` |
+ | `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
+ | `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
+ )"
+ };
};
diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc
index 73bcd6e81..1479822a9 100644
--- a/src/libstore/http-binary-cache-store.cc
+++ b/src/libstore/http-binary-cache-store.cc
@@ -56,7 +56,7 @@ public:
void init() override
{
// FIXME: do this lazily?
- if (auto cacheInfo = diskCache->cacheExists(cacheUri)) {
+ if (auto cacheInfo = diskCache->upToDateCacheExists(cacheUri)) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {
diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc
index 3e0689534..2645f468b 100644
--- a/src/libstore/nar-info-disk-cache.cc
+++ b/src/libstore/nar-info-disk-cache.cc
@@ -84,11 +84,10 @@ public:
Sync<State> _state;
- NarInfoDiskCacheImpl()
+ NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite")
{
auto state(_state.lock());
- Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite";
createDirs(dirOf(dbPath));
state->db = SQLite(dbPath);
@@ -98,7 +97,7 @@ public:
state->db.exec(schema);
state->insertCache.create(state->db,
- "insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
+ "insert into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?1, ?2, ?3, ?4, ?5) on conflict (url) do update set timestamp = ?2, storeDir = ?3, wantMassQuery = ?4, priority = ?5 returning id;");
state->queryCache.create(state->db,
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?");
@@ -166,6 +165,8 @@ public:
return i->second;
}
+private:
+
std::optional<Cache> queryCacheRaw(State & state, const std::string & uri)
{
auto i = state.caches.find(uri);
@@ -173,15 +174,21 @@ public:
auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
if (!queryCache.next())
return std::nullopt;
- state.caches.emplace(uri,
- Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
+ auto cache = Cache {
+ .id = (int) queryCache.getInt(0),
+ .storeDir = queryCache.getStr(1),
+ .wantMassQuery = queryCache.getInt(2) != 0,
+ .priority = (int) queryCache.getInt(3),
+ };
+ state.caches.emplace(uri, cache);
}
return getCache(state, uri);
}
- void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
+public:
+ int createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
{
- retrySQLite<void>([&]() {
+ return retrySQLite<int>([&]() {
auto state(_state.lock());
SQLiteTxn txn(state->db);
@@ -190,17 +197,29 @@ public:
auto cache(queryCacheRaw(*state, uri));
if (cache)
- return;
+ return cache->id;
- state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec();
- assert(sqlite3_changes(state->db) == 1);
- state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority};
+ Cache ret {
+ .id = -1, // set below
+ .storeDir = storeDir,
+ .wantMassQuery = wantMassQuery,
+ .priority = priority,
+ };
+
+ {
+ auto r(state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority));
+ assert(r.next());
+ ret.id = (int) r.getInt(0);
+ }
+
+ state->caches[uri] = ret;
txn.commit();
+ return ret.id;
});
}
- std::optional<CacheInfo> cacheExists(const std::string & uri) override
+ std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) override
{
return retrySQLite<std::optional<CacheInfo>>([&]() -> std::optional<CacheInfo> {
auto state(_state.lock());
@@ -208,6 +227,7 @@ public:
if (!cache)
return std::nullopt;
return CacheInfo {
+ .id = cache->id,
.wantMassQuery = cache->wantMassQuery,
.priority = cache->priority
};
@@ -371,4 +391,9 @@ ref<NarInfoDiskCache> getNarInfoDiskCache()
return cache;
}
+ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath)
+{
+ return make_ref<NarInfoDiskCacheImpl>(dbPath);
+}
+
}
diff --git a/src/libstore/nar-info-disk-cache.hh b/src/libstore/nar-info-disk-cache.hh
index 2dcaa76a4..4877f56d8 100644
--- a/src/libstore/nar-info-disk-cache.hh
+++ b/src/libstore/nar-info-disk-cache.hh
@@ -13,16 +13,17 @@ public:
virtual ~NarInfoDiskCache() { }
- virtual void createCache(const std::string & uri, const Path & storeDir,
+ virtual int createCache(const std::string & uri, const Path & storeDir,
bool wantMassQuery, int priority) = 0;
struct CacheInfo
{
+ int id;
bool wantMassQuery;
int priority;
};
- virtual std::optional<CacheInfo> cacheExists(const std::string & uri) = 0;
+ virtual std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) = 0;
virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
const std::string & uri, const std::string & hashPart) = 0;
@@ -45,4 +46,6 @@ public:
multiple threads. */
ref<NarInfoDiskCache> getNarInfoDiskCache();
+ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath);
+
}
diff --git a/src/libstore/nix-store.pc.in b/src/libstore/nix-store.pc.in
index 385169a13..dc42d0bca 100644
--- a/src/libstore/nix-store.pc.in
+++ b/src/libstore/nix-store.pc.in
@@ -6,4 +6,4 @@ Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixstore -lnixutil
-Cflags: -I${includedir}/nix -std=c++20
+Cflags: -I${includedir}/nix -std=c++2a
diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc
index b202351ce..c551c5f3e 100644
--- a/src/libstore/profiles.cc
+++ b/src/libstore/profiles.cc
@@ -282,7 +282,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir()
{
- auto profileRoot = getDataDir() + "/nix/profiles";
+ auto profileRoot = createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
}
@@ -290,7 +290,7 @@ Path profilesDir()
Path getDefaultProfile()
{
- Path profileLink = getHome() + "/.nix-profile";
+ Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile =
getuid() == 0
diff --git a/src/libstore/profiles.hh b/src/libstore/profiles.hh
index 73667a798..fbf95b850 100644
--- a/src/libstore/profiles.hh
+++ b/src/libstore/profiles.hh
@@ -72,8 +72,9 @@ std::string optimisticLockProfile(const Path & profile);
profiles. */
Path profilesDir();
-/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create
- it. */
+/* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/
+ nix/profile if XDG Base Directory Support is enabled), and create if doesn't
+ exist */
Path getDefaultProfile();
}
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index 844553ad3..8d76eee99 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -238,7 +238,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
void init() override
{
- if (auto cacheInfo = diskCache->cacheExists(getUri())) {
+ if (auto cacheInfo = diskCache->upToDateCacheExists(getUri())) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {
diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc
index 353dff9fa..871f2f3be 100644
--- a/src/libstore/sqlite.cc
+++ b/src/libstore/sqlite.cc
@@ -41,6 +41,15 @@ SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int ex
throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf));
}
+static void traceSQL(void * x, const char * sql)
+{
+ // wacky delimiters:
+ // so that we're quite unambiguous without escaping anything
+ // notice instead of trace:
+ // so that this can be enabled without getting the firehose in our face.
+ notice("SQL<[%1%]>", sql);
+};
+
SQLite::SQLite(const Path & path, bool create)
{
// useSQLiteWAL also indicates what virtual file system we need. Using
@@ -58,6 +67,11 @@ SQLite::SQLite(const Path & path, bool create)
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
SQLiteError::throw_(db, "setting timeout");
+ if (getEnv("NIX_DEBUG_SQLITE_TRACES") == "1") {
+ // To debug sqlite statements; trace all of them
+ sqlite3_trace(db, &traceSQL, nullptr);
+ }
+
exec("pragma foreign_keys = 1");
}
diff --git a/src/libstore/tests/derivation.cc b/src/libstore/tests/derivation.cc
new file mode 100644
index 000000000..12be8504d
--- /dev/null
+++ b/src/libstore/tests/derivation.cc
@@ -0,0 +1,124 @@
+#include <nlohmann/json.hpp>
+#include <gtest/gtest.h>
+
+#include "derivations.hh"
+
+#include "tests/libstore.hh"
+
+namespace nix {
+
+class DerivationTest : public LibStoreTest
+{
+};
+
+#define TEST_JSON(TYPE, NAME, STR, VAL, ...) \
+ TEST_F(DerivationTest, TYPE ## _ ## NAME ## _to_json) { \
+ using nlohmann::literals::operator "" _json; \
+ ASSERT_EQ( \
+ STR ## _json, \
+ (TYPE { VAL }).toJSON(*store __VA_OPT__(,) __VA_ARGS__)); \
+ }
+
+TEST_JSON(DerivationOutput, inputAddressed,
+ R"({
+ "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+ })",
+ (DerivationOutput::InputAddressed {
+ .path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"),
+ }),
+ "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, caFixed,
+ R"({
+ "hashAlgo": "r:sha256",
+ "hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
+ "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+ })",
+ (DerivationOutput::CAFixed {
+ .hash = {
+ .method = FileIngestionMethod::Recursive,
+ .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
+ },
+ }),
+ "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, caFloating,
+ R"({
+ "hashAlgo": "r:sha256"
+ })",
+ (DerivationOutput::CAFloating {
+ .method = FileIngestionMethod::Recursive,
+ .hashType = htSHA256,
+ }),
+ "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, deferred,
+ R"({ })",
+ DerivationOutput::Deferred { },
+ "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, impure,
+ R"({
+ "hashAlgo": "r:sha256",
+ "impure": true
+ })",
+ (DerivationOutput::Impure {
+ .method = FileIngestionMethod::Recursive,
+ .hashType = htSHA256,
+ }),
+ "drv-name", "output-name")
+
+TEST_JSON(Derivation, impure,
+ R"({
+ "inputSrcs": [
+ "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+ ],
+ "inputDrvs": {
+ "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": [
+ "cat",
+ "dog"
+ ]
+ },
+ "system": "wasm-sel4",
+ "builder": "foo",
+ "args": [
+ "bar",
+ "baz"
+ ],
+ "env": {
+ "BIG_BAD": "WOLF"
+ },
+ "outputs": {}
+ })",
+ ({
+ Derivation drv;
+ drv.inputSrcs = {
+ store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
+ };
+ drv.inputDrvs = {
+ {
+ store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
+ {
+ "cat",
+ "dog",
+ },
+ }
+ };
+ drv.platform = "wasm-sel4";
+ drv.builder = "foo";
+ drv.args = {
+ "bar",
+ "baz",
+ };
+ drv.env = {
+ {
+ "BIG_BAD",
+ "WOLF",
+ },
+ };
+ drv;
+ }))
+
+#undef TEST_JSON
+
+}
diff --git a/src/libstore/tests/nar-info-disk-cache.cc b/src/libstore/tests/nar-info-disk-cache.cc
new file mode 100644
index 000000000..b4bdb8329
--- /dev/null
+++ b/src/libstore/tests/nar-info-disk-cache.cc
@@ -0,0 +1,123 @@
+#include "nar-info-disk-cache.hh"
+
+#include <gtest/gtest.h>
+#include <rapidcheck/gtest.h>
+#include "sqlite.hh"
+#include <sqlite3.h>
+
+
+namespace nix {
+
+TEST(NarInfoDiskCacheImpl, create_and_read) {
+ // This is a large single test to avoid some setup overhead.
+
+ int prio = 12345;
+ bool wantMassQuery = true;
+
+ Path tmpDir = createTempDir();
+ AutoDelete delTmpDir(tmpDir);
+ Path dbPath(tmpDir + "/test-narinfo-disk-cache.sqlite");
+
+ int savedId;
+ int barId;
+ SQLite db;
+ SQLiteStmt getIds;
+
+ {
+ auto cache = getTestNarInfoDiskCache(dbPath);
+
+ // Set up "background noise" and check that different caches receive different ids
+ {
+ auto bc1 = cache->createCache("https://bar", "/nix/storedir", wantMassQuery, prio);
+ auto bc2 = cache->createCache("https://xyz", "/nix/storedir", false, 12);
+ ASSERT_NE(bc1, bc2);
+ barId = bc1;
+ }
+
+ // Check that the fields are saved and returned correctly. This does not test
+ // the select statement yet, because of in-memory caching.
+ savedId = cache->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);;
+ {
+ auto r = cache->upToDateCacheExists("http://foo");
+ ASSERT_TRUE(r);
+ ASSERT_EQ(r->priority, prio);
+ ASSERT_EQ(r->wantMassQuery, wantMassQuery);
+ ASSERT_EQ(savedId, r->id);
+ }
+
+ // We're going to pay special attention to the id field because we had a bug
+ // that changed it.
+ db = SQLite(dbPath);
+ getIds.create(db, "select id from BinaryCaches where url = 'http://foo'");
+
+ {
+ auto q(getIds.use());
+ ASSERT_TRUE(q.next());
+ ASSERT_EQ(savedId, q.getInt(0));
+ ASSERT_FALSE(q.next());
+ }
+
+ // Pretend that the caches are older, but keep one up to date, as "background noise"
+ db.exec("update BinaryCaches set timestamp = timestamp - 1 - 7 * 24 * 3600 where url <> 'https://xyz';");
+
+ // This shows that the in-memory cache works
+ {
+ auto r = cache->upToDateCacheExists("http://foo");
+ ASSERT_TRUE(r);
+ ASSERT_EQ(r->priority, prio);
+ ASSERT_EQ(r->wantMassQuery, wantMassQuery);
+ }
+ }
+
+ {
+ // We can't clear the in-memory cache, so we use a new cache object. This is
+ // more realistic anyway.
+ auto cache2 = getTestNarInfoDiskCache(dbPath);
+
+ {
+ auto r = cache2->upToDateCacheExists("http://foo");
+ ASSERT_FALSE(r);
+ }
+
+ // "Update", same data, check that the id number is reused
+ cache2->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);
+
+ {
+ auto r = cache2->upToDateCacheExists("http://foo");
+ ASSERT_TRUE(r);
+ ASSERT_EQ(r->priority, prio);
+ ASSERT_EQ(r->wantMassQuery, wantMassQuery);
+ ASSERT_EQ(r->id, savedId);
+ }
+
+ {
+ auto q(getIds.use());
+ ASSERT_TRUE(q.next());
+ auto currentId = q.getInt(0);
+ ASSERT_FALSE(q.next());
+ ASSERT_EQ(currentId, savedId);
+ }
+
+ // Check that the fields can be modified, and the id remains the same
+ {
+ auto r0 = cache2->upToDateCacheExists("https://bar");
+ ASSERT_FALSE(r0);
+
+ cache2->createCache("https://bar", "/nix/storedir", !wantMassQuery, prio + 10);
+ auto r = cache2->upToDateCacheExists("https://bar");
+ ASSERT_EQ(r->wantMassQuery, !wantMassQuery);
+ ASSERT_EQ(r->priority, prio + 10);
+ ASSERT_EQ(r->id, barId);
+ }
+
+ // // Force update (no use case yet; we only retrieve cache metadata when stale based on timestamp)
+ // {
+ // cache2->createCache("https://bar", "/nix/storedir", wantMassQuery, prio + 20);
+ // auto r = cache2->upToDateCacheExists("https://bar");
+ // ASSERT_EQ(r->wantMassQuery, wantMassQuery);
+ // ASSERT_EQ(r->priority, prio + 20);
+ // }
+ }
+}
+
+}
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 2930913d6..35686a8aa 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -29,7 +29,15 @@ void Args::removeFlag(const std::string & longName)
void Completions::add(std::string completion, std::string description)
{
- assert(description.find('\n') == std::string::npos);
+ description = trim(description);
+ // ellipsize overflowing content on the back of the description
+ auto end_index = description.find_first_of(".\n");
+ if (end_index != std::string::npos) {
+ auto needs_ellipsis = end_index != description.size() - 1;
+ description.resize(end_index);
+ if (needs_ellipsis)
+ description.append(" [...]");
+ }
insert(Completion {
.completion = completion,
.description = description
diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc
new file mode 100644
index 000000000..f66accb10
--- /dev/null
+++ b/src/libutil/namespaces.cc
@@ -0,0 +1,97 @@
+#if __linux__
+
+#include "namespaces.hh"
+#include "util.hh"
+#include "finally.hh"
+
+#include <sys/mount.h>
+
+namespace nix {
+
+bool userNamespacesSupported()
+{
+ static auto res = [&]() -> bool
+ {
+ if (!pathExists("/proc/self/ns/user")) {
+ debug("'/proc/self/ns/user' does not exist; your kernel was likely built without CONFIG_USER_NS=y");
+ return false;
+ }
+
+ Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
+ if (!pathExists(maxUserNamespaces) ||
+ trim(readFile(maxUserNamespaces)) == "0")
+ {
+ debug("user namespaces appear to be disabled; check '/proc/sys/user/max_user_namespaces'");
+ return false;
+ }
+
+ Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
+ if (pathExists(procSysKernelUnprivilegedUsernsClone)
+ && trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0")
+ {
+ debug("user namespaces appear to be disabled; check '/proc/sys/kernel/unprivileged_userns_clone'");
+ return false;
+ }
+
+ try {
+ Pid pid = startProcess([&]()
+ {
+ _exit(0);
+ }, {
+ .cloneFlags = CLONE_NEWUSER
+ });
+
+ auto r = pid.wait();
+ assert(!r);
+ } catch (SysError & e) {
+ debug("user namespaces do not work on this system: %s", e.msg());
+ return false;
+ }
+
+ return true;
+ }();
+ return res;
+}
+
+bool mountAndPidNamespacesSupported()
+{
+ static auto res = [&]() -> bool
+ {
+ try {
+
+ Pid pid = startProcess([&]()
+ {
+ /* Make sure we don't remount the parent's /proc. */
+ if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
+ _exit(1);
+
+ /* Test whether we can remount /proc. The kernel disallows
+ this if /proc is not fully visible, i.e. if there are
+ filesystems mounted on top of files inside /proc. See
+ https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
+ if (mount("none", "/proc", "proc", 0, 0) == -1)
+ _exit(2);
+
+ _exit(0);
+ }, {
+ .cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0)
+ });
+
+ if (pid.wait()) {
+ debug("PID namespaces do not work on this system: cannot remount /proc");
+ return false;
+ }
+
+ } catch (SysError & e) {
+ debug("mount namespaces do not work on this system: %s", e.msg());
+ return false;
+ }
+
+ return true;
+ }();
+ return res;
+}
+
+}
+
+#endif
diff --git a/src/libutil/namespaces.hh b/src/libutil/namespaces.hh
new file mode 100644
index 000000000..e82379b9c
--- /dev/null
+++ b/src/libutil/namespaces.hh
@@ -0,0 +1,13 @@
+#pragma once
+
+namespace nix {
+
+#if __linux__
+
+bool userNamespacesSupported();
+
+bool mountAndPidNamespacesSupported();
+
+#endif
+
+}
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 40faa4bf2..885bae69c 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -36,6 +36,7 @@
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/mman.h>
#include <cmath>
#endif
@@ -608,6 +609,19 @@ Path getDataDir()
return dataDir ? *dataDir : getHome() + "/.local/share";
}
+Path getStateDir()
+{
+ auto stateDir = getEnv("XDG_STATE_HOME");
+ return stateDir ? *stateDir : getHome() + "/.local/state";
+}
+
+Path createNixStateDir()
+{
+ Path dir = getStateDir() + "/nix";
+ createDirs(dir);
+ return dir;
+}
+
std::optional<Path> getSelfExe()
{
@@ -1051,9 +1065,17 @@ static pid_t doFork(bool allowVfork, std::function<void()> fun)
}
+static int childEntry(void * arg)
+{
+ auto main = (std::function<void()> *) arg;
+ (*main)();
+ return 1;
+}
+
+
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
{
- auto wrapper = [&]() {
+ std::function<void()> wrapper = [&]() {
if (!options.allowVfork)
logger = makeSimpleLogger();
try {
@@ -1073,7 +1095,27 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
_exit(1);
};
- pid_t pid = doFork(options.allowVfork, wrapper);
+ pid_t pid = -1;
+
+ if (options.cloneFlags) {
+ #ifdef __linux__
+ // Not supported, since then we don't know when to free the stack.
+ assert(!(options.cloneFlags & CLONE_VM));
+
+ size_t stackSize = 1 * 1024 * 1024;
+ auto stack = (char *) mmap(0, stackSize,
+ PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED) throw SysError("allocating stack");
+
+ Finally freeStack([&]() { munmap(stack, stackSize); });
+
+ pid = clone(childEntry, stack + stackSize, options.cloneFlags | SIGCHLD, &wrapper);
+ #else
+ throw Error("clone flags are only supported on Linux");
+ #endif
+ } else
+ pid = doFork(options.allowVfork, wrapper);
+
if (pid == -1) throw SysError("unable to fork");
return pid;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 266da0ae3..b5625ecef 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -158,6 +158,12 @@ Path getDataDir();
/* Return the path of the current executable. */
std::optional<Path> getSelfExe();
+/* Return $XDG_STATE_HOME or $HOME/.local/state. */
+Path getStateDir();
+
+/* Create the Nix state directory and return the path to it. */
+Path createNixStateDir();
+
/* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */
Paths createDirs(const Path & path);
@@ -301,6 +307,7 @@ struct ProcessOptions
bool dieWithParent = true;
bool runExitHandlers = false;
bool allowVfork = false;
+ int cloneFlags = 0; // use clone() with the specified flags (Linux only)
};
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions());
diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc
index 263d85eea..338a7d18e 100755
--- a/src/nix-channel/nix-channel.cc
+++ b/src/nix-channel/nix-channel.cc
@@ -164,8 +164,8 @@ static int main_nix_channel(int argc, char ** argv)
{
// Figure out the name of the `.nix-channels' file to use
auto home = getHome();
- channelsList = home + "/.nix-channels";
- nixDefExpr = home + "/.nix-defexpr";
+ channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels";
+ nixDefExpr = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr";
// Figure out the name of the channels profile.
profile = profilesDir() + "/channels";
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index 406e548c0..0daf374de 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -1289,7 +1289,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
throw UsageError("exactly one argument expected");
Path profile = absPath(opArgs.front());
- Path profileLink = getHome() + "/.nix-profile";
+ Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
switchLink(profileLink, profile);
}
@@ -1393,7 +1393,10 @@ static int main_nix_env(int argc, char * * argv)
Globals globals;
globals.instSource.type = srcUnknown;
- globals.instSource.nixExprPath = getHome() + "/.nix-defexpr";
+ {
+ Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr";
+ globals.instSource.nixExprPath = nixExprPath;
+ }
globals.instSource.systemFilter = "*";
if (!pathExists(globals.instSource.nixExprPath)) {
diff --git a/src/nix/app.cc b/src/nix/app.cc
index 08cd0ccd4..5cd65136f 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -1,4 +1,5 @@
#include "installables.hh"
+#include "installable-derived-path.hh"
#include "store-api.hh"
#include "eval-inline.hh"
#include "eval-cache.hh"
@@ -8,30 +9,6 @@
namespace nix {
-struct InstallableDerivedPath : Installable
-{
- ref<Store> store;
- const DerivedPath derivedPath;
-
- InstallableDerivedPath(ref<Store> store, const DerivedPath & derivedPath)
- : store(store)
- , derivedPath(derivedPath)
- {
- }
-
- std::string what() const override { return derivedPath.to_string(*store); }
-
- DerivedPathsWithInfo toDerivedPaths() override
- {
- return {{derivedPath}};
- }
-
- std::optional<StorePath> getStorePath() override
- {
- return std::nullopt;
- }
-};
-
/**
* Return the rewrites that are needed to resolve a string whose context is
* included in `dependencies`.
@@ -146,7 +123,7 @@ App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store)
for (auto & ctxElt : unresolved.context)
installableContext.push_back(
- std::make_shared<InstallableDerivedPath>(store, ctxElt));
+ std::make_shared<InstallableDerivedPath>(store, DerivedPath { ctxElt }));
auto builtContext = Installable::build(evalStore, store, Realise::Outputs, installableContext);
res.program = resolveString(*store, unresolved.program, builtContext);
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 6ae9460f6..dcf9a6f2d 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -1,4 +1,5 @@
#include "command.hh"
+#include "installable-flake.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
index 2ba56ee26..a22bccba1 100644
--- a/src/nix/daemon.cc
+++ b/src/nix/daemon.cc
@@ -34,7 +34,7 @@
using namespace nix;
using namespace nix::daemon;
-struct UserSettings : Config {
+struct AuthorizationSettings : Config {
Setting<Strings> trustedUsers{
this, {"root"}, "trusted-users",
@@ -67,9 +67,9 @@ struct UserSettings : Config {
)"};
};
-UserSettings userSettings;
+AuthorizationSettings authorizationSettings;
-static GlobalConfig::Register rSettings(&userSettings);
+static GlobalConfig::Register rSettings(&authorizationSettings);
#ifndef __linux__
#define SPLICE_F_MOVE 0
@@ -240,8 +240,8 @@ static void daemonLoop()
struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
- Strings trustedUsers = userSettings.trustedUsers;
- Strings allowedUsers = userSettings.allowedUsers;
+ Strings trustedUsers = authorizationSettings.trustedUsers;
+ Strings allowedUsers = authorizationSettings.allowedUsers;
if (matchUser(user, group, trustedUsers))
trusted = Trusted;
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 16bbd8613..9d07a7a85 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -1,5 +1,6 @@
#include "eval.hh"
#include "command.hh"
+#include "installable-flake.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
index 0621d662c..3489cc132 100644
--- a/src/nix/diff-closures.cc
+++ b/src/nix/diff-closures.cc
@@ -106,7 +106,7 @@ void printClosureDiff(
using namespace nix;
-struct CmdDiffClosures : SourceExprCommand
+struct CmdDiffClosures : SourceExprCommand, MixOperateOnOptions
{
std::string _before, _after;
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 76a134b1f..dfe75fbdf 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -3,6 +3,7 @@
#include "eval.hh"
#include "attr-path.hh"
#include "progress-bar.hh"
+#include "editor-for.hh"
#include <unistd.h>
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index ccee074e9..a579213fd 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -11,13 +11,13 @@
using namespace nix;
-struct CmdEval : MixJSON, InstallableCommand
+struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
{
bool raw = false;
std::optional<std::string> apply;
std::optional<Path> writeTo;
- CmdEval() : InstallableCommand(true /* supportReadOnlyMode */)
+ CmdEval() : InstallableCommand()
{
addFlag({
.longName = "raw",
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index c025bc7a6..053a9c9e1 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -1,4 +1,5 @@
#include "command.hh"
+#include "installable-flake.hh"
#include "common-args.hh"
#include "shared.hh"
#include "eval.hh"
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index 32364e720..208542a5c 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -1,4 +1,5 @@
#include "command.hh"
+#include "installable-flake.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
new file mode 100644
index 000000000..679bdea77
--- /dev/null
+++ b/src/nix/repl.cc
@@ -0,0 +1,95 @@
+#include "eval.hh"
+#include "globals.hh"
+#include "command.hh"
+#include "repl.hh"
+
+namespace nix {
+
+struct CmdRepl : InstallablesCommand
+{
+ CmdRepl() {
+ evalSettings.pureEval = false;
+ }
+
+ void prepare() override
+ {
+ if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
+ warn("future versions of Nix will require using `--file` to load a file");
+ if (this->_installables.size() > 1)
+ warn("more than one input file is not currently supported");
+ auto filePath = this->_installables[0].data();
+ file = std::optional(filePath);
+ _installables.front() = _installables.back();
+ _installables.pop_back();
+ }
+ installables = InstallablesCommand::load();
+ }
+
+ std::vector<std::string> files;
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {""};
+ }
+
+ bool useDefaultInstallables() override
+ {
+ return file.has_value() or expr.has_value();
+ }
+
+ bool forceImpureByDefault() override
+ {
+ return true;
+ }
+
+ std::string description() override
+ {
+ return "start an interactive environment for evaluating Nix expressions";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "repl.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+ auto getValues = [&]()->AbstractNixRepl::AnnotatedValues{
+ auto installables = load();
+ AbstractNixRepl::AnnotatedValues values;
+ for (auto & installable: installables){
+ auto what = installable->what();
+ if (file){
+ auto [val, pos] = installable->toValue(*state);
+ auto what = installable->what();
+ state->forceValue(*val, pos);
+ auto autoArgs = getAutoArgs(*state);
+ auto valPost = state->allocValue();
+ state->autoCallFunction(*autoArgs, *val, *valPost);
+ state->forceValue(*valPost, pos);
+ values.push_back( {valPost, what });
+ } else {
+ auto [val, pos] = installable->toValue(*state);
+ values.push_back( {val, what} );
+ }
+ }
+ return values;
+ };
+ auto repl = AbstractNixRepl::create(
+ searchPath,
+ openStore(),
+ state,
+ getValues
+ );
+ repl->autoArgs = getAutoArgs(*repl->state);
+ repl->initEnv();
+ repl->mainLoop();
+ }
+};
+
+static auto rCmdRepl = registerCommand<CmdRepl>("repl");
+
+}
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index af2e676a4..d1a516cad 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -54,56 +54,8 @@ struct CmdShowDerivation : InstallablesCommand
for (auto & drvPath : drvPaths) {
if (!drvPath.isDerivation()) continue;
- json& drvObj = jsonRoot[store->printStorePath(drvPath)];
-
- auto drv = store->readDerivation(drvPath);
-
- {
- json& outputsObj = drvObj["outputs"];
- outputsObj = json::object();
- for (auto & [_outputName, output] : drv.outputs) {
- auto & outputName = _outputName; // work around clang bug
- auto& outputObj = outputsObj[outputName];
- outputObj = json::object();
- std::visit(overloaded {
- [&](const DerivationOutput::InputAddressed & doi) {
- outputObj["path"] = store->printStorePath(doi.path);
- },
- [&](const DerivationOutput::CAFixed & dof) {
- outputObj["path"] = store->printStorePath(dof.path(*store, drv.name, outputName));
- outputObj["hashAlgo"] = dof.hash.printMethodAlgo();
- outputObj["hash"] = dof.hash.hash.to_string(Base16, false);
- },
- [&](const DerivationOutput::CAFloating & dof) {
- outputObj["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType);
- },
- [&](const DerivationOutput::Deferred &) {},
- [&](const DerivationOutput::Impure & doi) {
- outputObj["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType);
- outputObj["impure"] = true;
- },
- }, output.raw());
- }
- }
-
- {
- auto& inputsList = drvObj["inputSrcs"];
- inputsList = json::array();
- for (auto & input : drv.inputSrcs)
- inputsList.emplace_back(store->printStorePath(input));
- }
-
- {
- auto& inputDrvsObj = drvObj["inputDrvs"];
- inputDrvsObj = json::object();
- for (auto & input : drv.inputDrvs)
- inputDrvsObj[store->printStorePath(input.first)] = input.second;
- }
-
- drvObj["system"] = drv.platform;
- drvObj["builder"] = drv.builder;
- drvObj["args"] = drv.args;
- drvObj["env"] = drv.env;
+ jsonRoot[store->printStorePath(drvPath)] =
+ store->readDerivation(drvPath).toJSON(*store);
}
std::cout << jsonRoot.dump(2) << std::endl;
}
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 76125e5e4..a3a9dc698 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -27,7 +27,7 @@ static std::string filterPrintable(const std::string & s)
return res;
}
-struct CmdWhyDepends : SourceExprCommand
+struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
{
std::string _package, _dependency;
bool all = false;