aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md1
-rw-r--r--local.mk4
-rw-r--r--maintainers/README.md3
-rw-r--r--mk/patterns.mk4
-rw-r--r--src/libcmd/installables.cc22
-rw-r--r--src/libcmd/repl.cc4
-rw-r--r--src/libexpr/eval.cc19
-rw-r--r--src/libexpr/flake/flake.cc38
-rw-r--r--src/libexpr/flake/flake.hh6
-rw-r--r--src/libexpr/local.mk2
-rw-r--r--src/libexpr/primops.cc4
-rw-r--r--src/libstore/build-result.hh1
-rw-r--r--src/libstore/filetransfer.cc5
-rw-r--r--src/libstore/local-store.cc48
-rw-r--r--src/libstore/local-store.hh3
-rw-r--r--src/libstore/nar-accessor.cc1
-rw-r--r--src/libstore/store-api.cc48
-rw-r--r--src/libstore/store-api.hh2
-rw-r--r--src/libutil/args.cc17
-rw-r--r--src/libutil/args.hh2
-rw-r--r--src/libutil/logging.cc5
-rw-r--r--src/nix-store/nix-store.cc12
-rw-r--r--src/nix/doctor.cc8
-rw-r--r--src/nix/main.cc9
-rw-r--r--src/nix/repl.cc8
-rw-r--r--src/nix/upgrade-nix.cc8
-rw-r--r--tests/experimental-features.sh21
-rw-r--r--tests/flakes/flakes.sh13
-rw-r--r--tests/lang.sh7
29 files changed, 241 insertions, 84 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 60742cf6a..4488c7b7d 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -23,6 +23,7 @@ Maintainers: tick if completed or explain if not relevant
- unit tests - `src/*/tests`
- integration tests - `tests/nixos/*`
- [ ] documentation in the manual
+ - [ ] documentation in the internal API docs
- [ ] code and comments are self-explanatory
- [ ] commit message explains why the change was made
- [ ] new feature or incompatible change: updated release notes
diff --git a/local.mk b/local.mk
index 6a7074e8e..6951c179e 100644
--- a/local.mk
+++ b/local.mk
@@ -1,6 +1,8 @@
clean-files += Makefile.config
-GLOBAL_CXXFLAGS += -Wno-deprecated-declarations
+GLOBAL_CXXFLAGS += -Wno-deprecated-declarations -Werror=switch
+# Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers.
+ERROR_SWITCH_ENUM = -Werror=switch-enum
$(foreach i, config.h $(wildcard src/lib*/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
diff --git a/maintainers/README.md b/maintainers/README.md
index 476a5f51e..36d40a0ab 100644
--- a/maintainers/README.md
+++ b/maintainers/README.md
@@ -69,6 +69,7 @@ Issues on the board progress through the following states:
2. [security](https://github.com/NixOS/nix/labels/security)
3. [regression](https://github.com/NixOS/nix/labels/regression)
4. [bug](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc)
+ 5. [tests of existing functionality](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Atests+-label%3Afeature+sort%3Areactions-%2B1-desc)
- [oldest pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc)
- [most popular pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Areactions-%2B1-desc)
@@ -91,7 +92,7 @@ Issues on the board progress through the following states:
Contributors who took the time to implement concrete change proposals should not wait indefinitely.
- - Prioritise fixing bugs over documentation, improvements or new features
+ - Prioritise fixing bugs and testing over documentation, improvements or new features
The team values stability and accessibility higher than raw functionality.
diff --git a/mk/patterns.mk b/mk/patterns.mk
index 86a724806..c81150260 100644
--- a/mk/patterns.mk
+++ b/mk/patterns.mk
@@ -1,10 +1,10 @@
$(buildprefix)%.o: %.cc
@mkdir -p "$(dir $@)"
- $(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP
+ $(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) $(ERROR_SWITCH_ENUM) -MMD -MF $(call filename-to-dep, $@) -MP
$(buildprefix)%.o: %.cpp
@mkdir -p "$(dir $@)"
- $(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP
+ $(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) $(ERROR_SWITCH_ENUM) -MMD -MF $(call filename-to-dep, $@) -MP
$(buildprefix)%.o: %.c
@mkdir -p "$(dir $@)"
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 67549b280..32ae46d9f 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -103,6 +103,28 @@ MixFlakeOptions::MixFlakeOptions()
});
addFlag({
+ .longName = "reference-lock-file",
+ .description = "Read the given lock file instead of `flake.lock` within the top-level flake.",
+ .category = category,
+ .labels = {"flake-lock-path"},
+ .handler = {[&](std::string lockFilePath) {
+ lockFlags.referenceLockFilePath = lockFilePath;
+ }},
+ .completer = completePath
+ });
+
+ addFlag({
+ .longName = "output-lock-file",
+ .description = "Write the given lock file instead of `flake.lock` within the top-level flake.",
+ .category = category,
+ .labels = {"flake-lock-path"},
+ .handler = {[&](std::string lockFilePath) {
+ lockFlags.outputLockFilePath = lockFilePath;
+ }},
+ .completer = completePath
+ });
+
+ addFlag({
.longName = "inputs-from",
.description = "Use the inputs of the specified flake as registry entries.",
.category = category,
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index e3afb1531..57848a5d3 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -252,7 +252,9 @@ void NixRepl::mainLoop()
el_hist_size = 1000;
#endif
read_history(historyFile.c_str());
+ auto oldRepl = curRepl;
curRepl = this;
+ Finally restoreRepl([&] { curRepl = oldRepl; });
#ifndef READLINE
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
@@ -1024,6 +1026,8 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
str << v.fpoint;
break;
+ case nThunk:
+ case nExternal:
default:
str << ANSI_RED "«unknown»" ANSI_NORMAL;
break;
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 584bbc879..7f2065656 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -173,7 +173,17 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
case tFloat:
str << fpoint;
break;
+ case tBlackhole:
+ // Although we know for sure that it's going to be an infinite recursion
+ // when this value is accessed _in the current context_, it's likely
+ // that the user will misinterpret a simpler «infinite recursion» output
+ // as a definitive statement about the value, while in fact it may be
+ // a valid value after `builtins.trace` and perhaps some other steps
+ // have completed.
+ str << "«potential infinite recursion»";
+ break;
default:
+ printError("Nix evaluator internal error: Value::print(): invalid value type %1%", internalType);
abort();
}
}
@@ -229,6 +239,9 @@ std::string_view showType(ValueType type)
std::string showType(const Value & v)
{
+ // Allow selecting a subset of enum values
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (v.internalType) {
case tString: return v.string.context ? "a string with context" : "a string";
case tPrimOp:
@@ -242,16 +255,21 @@ std::string showType(const Value & v)
default:
return std::string(showType(v.type()));
}
+ #pragma GCC diagnostic pop
}
PosIdx Value::determinePos(const PosIdx pos) const
{
+ // Allow selecting a subset of enum values
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (internalType) {
case tAttrs: return attrs->pos;
case tLambda: return lambda.fun->pos;
case tApp: return app.left->determinePos(pos);
default: return pos;
}
+ #pragma GCC diagnostic pop
}
bool Value::isTrivial() const
@@ -2327,6 +2345,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nFloat:
return v1.fpoint == v2.fpoint;
+ case nThunk: // Must not be left by forceValue
default:
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 81e94848a..ac396236f 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -125,6 +125,9 @@ static FlakeInput parseFlakeInput(EvalState & state,
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
+ // Allow selecting a subset of enum values
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
case nString:
attrs.emplace(state.symbols[attr.name], attr.value->string.s);
@@ -139,6 +142,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value));
}
+ #pragma GCC diagnostic pop
}
} catch (Error & e) {
e.addTrace(
@@ -334,10 +338,14 @@ LockedFlake lockFlake(
}
try {
+ if (!fetchSettings.allowDirty && lockFlags.referenceLockFilePath) {
+ throw Error("reference lock file was provided, but the `allow-dirty` setting is set to false");
+ }
// FIXME: symlink attack
auto oldLockFile = LockFile::read(
- flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
+ lockFlags.referenceLockFilePath.value_or(
+ flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock"));
debug("old lock file: %s", oldLockFile);
@@ -619,13 +627,20 @@ LockedFlake lockFlake(
debug("new lock file: %s", newLockFile);
+ auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
+ auto sourcePath = topRef.input.getSourcePath();
+ auto outputLockFilePath = sourcePath ? std::optional{*sourcePath + "/" + relPath} : std::nullopt;
+ if (lockFlags.outputLockFilePath) {
+ outputLockFilePath = lockFlags.outputLockFilePath;
+ }
+
/* Check whether we need to / can write the new lock file. */
- if (!(newLockFile == oldLockFile)) {
+ if (newLockFile != oldLockFile || lockFlags.outputLockFilePath) {
auto diff = LockFile::diff(oldLockFile, newLockFile);
if (lockFlags.writeLockFile) {
- if (auto sourcePath = topRef.input.getSourcePath()) {
+ if (outputLockFilePath) {
if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
@@ -633,25 +648,24 @@ LockedFlake lockFlake(
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
- auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
-
- auto path = *sourcePath + "/" + relPath;
-
- bool lockFileExists = pathExists(path);
+ bool lockFileExists = pathExists(*outputLockFilePath);
if (lockFileExists) {
auto s = chomp(diff);
if (s.empty())
- warn("updating lock file '%s'", path);
+ warn("updating lock file '%s'", *outputLockFilePath);
else
- warn("updating lock file '%s':\n%s", path, s);
+ warn("updating lock file '%s':\n%s", *outputLockFilePath, s);
} else
- warn("creating lock file '%s'", path);
+ warn("creating lock file '%s'", *outputLockFilePath);
- newLockFile.write(path);
+ newLockFile.write(*outputLockFilePath);
std::optional<std::string> commitMessage = std::nullopt;
if (lockFlags.commitLockFile) {
+ if (lockFlags.outputLockFilePath) {
+ throw Error("--commit-lock-file and --output-lock-file are currently incompatible");
+ }
std::string cm;
cm = fetchSettings.commitLockFileSummary.get();
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index 3cb39d766..b6f710288 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -118,6 +118,12 @@ struct LockFlags
/* Whether to commit changes to flake.lock. */
bool commitLockFile = false;
+ /* The path to a lock file to read instead of the `flake.lock` file in the top-level flake */
+ std::optional<std::string> referenceLockFilePath;
+
+ /* The path to a lock file to write to instead of the `flake.lock` file in the top-level flake */
+ std::optional<Path> outputLockFilePath;
+
/* Flake inputs to be overridden. */
std::map<InputPath, FlakeRef> inputOverrides;
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 2171e769b..d243b9cec 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -46,3 +46,5 @@ $(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh
$(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh
+
+src/libexpr/primops/fromTOML.o: ERROR_SWITCH_ENUM =
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 72faeada8..f1bce2fb6 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -577,6 +577,9 @@ struct CompareValues
return v1->integer < v2->fpoint;
if (v1->type() != v2->type())
state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>();
+ // Allow selecting a subset of enum values
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (v1->type()) {
case nInt:
return v1->integer < v2->integer;
@@ -599,6 +602,7 @@ struct CompareValues
}
default:
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
+ #pragma GCC diagnostic pop
}
} catch (Error & e) {
if (!errorCtx.empty())
diff --git a/src/libstore/build-result.hh b/src/libstore/build-result.hh
index e50ddbb8c..a12c599d9 100644
--- a/src/libstore/build-result.hh
+++ b/src/libstore/build-result.hh
@@ -53,6 +53,7 @@ struct BuildResult
case LogLimitExceeded: return "LogLimitExceeded";
case NotDeterministic: return "NotDeterministic";
case ResolvesToAlreadyValid: return "ResolvesToAlreadyValid";
+ case NoSubstituters: return "NoSubstituters";
default: return "Unknown";
};
}();
diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc
index 1ba399a29..2346accbe 100644
--- a/src/libstore/filetransfer.cc
+++ b/src/libstore/filetransfer.cc
@@ -407,6 +407,10 @@ struct curlFileTransfer : public FileTransfer
err = Misc;
} else {
// Don't bother retrying on certain cURL errors either
+
+ // Allow selecting a subset of enum values
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wswitch-enum"
switch (code) {
case CURLE_FAILED_INIT:
case CURLE_URL_MALFORMAT:
@@ -427,6 +431,7 @@ struct curlFileTransfer : public FileTransfer
default: // Shut up warnings
break;
}
+ #pragma GCC diagnostic pop
}
attempt++;
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index dbba0c91f..f58d90895 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1134,54 +1134,6 @@ StorePathSet LocalStore::querySubstitutablePaths(const StorePathSet & paths)
}
-// FIXME: move this, it's not specific to LocalStore.
-void LocalStore::querySubstitutablePathInfos(const StorePathCAMap & paths, SubstitutablePathInfos & infos)
-{
- if (!settings.useSubstitutes) return;
- for (auto & sub : getDefaultSubstituters()) {
- for (auto & path : paths) {
- if (infos.count(path.first))
- // Choose first succeeding substituter.
- continue;
-
- auto subPath(path.first);
-
- // Recompute store path so that we can use a different store root.
- if (path.second) {
- subPath = makeFixedOutputPathFromCA(path.first.name(), *path.second);
- if (sub->storeDir == storeDir)
- assert(subPath == path.first);
- if (subPath != path.first)
- debug("replaced path '%s' with '%s' for substituter '%s'", printStorePath(path.first), sub->printStorePath(subPath), sub->getUri());
- } else if (sub->storeDir != storeDir) continue;
-
- debug("checking substituter '%s' for path '%s'", sub->getUri(), sub->printStorePath(subPath));
- try {
- auto info = sub->queryPathInfo(subPath);
-
- if (sub->storeDir != storeDir && !(info->isContentAddressed(*sub) && info->references.empty()))
- continue;
-
- auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
- std::shared_ptr<const ValidPathInfo>(info));
- infos.insert_or_assign(path.first, SubstitutablePathInfo{
- info->deriver,
- info->references,
- narInfo ? narInfo->fileSize : 0,
- info->narSize});
- } catch (InvalidPath &) {
- } catch (SubstituterDisabled &) {
- } catch (Error & e) {
- if (settings.tryFallback)
- logError(e.info());
- else
- throw;
- }
- }
- }
-}
-
-
void LocalStore::registerValidPath(const ValidPathInfo & info)
{
registerValidPaths({{info.path, info}});
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 2eaf451bf..1b668b6fd 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -134,9 +134,6 @@ public:
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
- void querySubstitutablePathInfos(const StorePathCAMap & paths,
- SubstitutablePathInfos & infos) override;
-
bool pathInfoIsUntrusted(const ValidPathInfo &) override;
bool realisationIsUntrusted(const Realisation & ) override;
diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc
index 9a0003588..f0dfcb19b 100644
--- a/src/libstore/nar-accessor.cc
+++ b/src/libstore/nar-accessor.cc
@@ -275,6 +275,7 @@ json listNar(ref<FSAccessor> accessor, const Path & path, bool recurse)
obj["type"] = "symlink";
obj["target"] = accessor->readLink(path);
break;
+ case FSAccessor::Type::tMissing:
default:
throw Error("path '%s' does not exist in NAR", path);
}
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 60e87918a..8fc3ed46a 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -507,6 +507,54 @@ StorePathSet Store::queryDerivationOutputs(const StorePath & path)
return outputPaths;
}
+
+void Store::querySubstitutablePathInfos(const StorePathCAMap & paths, SubstitutablePathInfos & infos)
+{
+ if (!settings.useSubstitutes) return;
+ for (auto & sub : getDefaultSubstituters()) {
+ for (auto & path : paths) {
+ if (infos.count(path.first))
+ // Choose first succeeding substituter.
+ continue;
+
+ auto subPath(path.first);
+
+ // Recompute store path so that we can use a different store root.
+ if (path.second) {
+ subPath = makeFixedOutputPathFromCA(path.first.name(), *path.second);
+ if (sub->storeDir == storeDir)
+ assert(subPath == path.first);
+ if (subPath != path.first)
+ debug("replaced path '%s' with '%s' for substituter '%s'", printStorePath(path.first), sub->printStorePath(subPath), sub->getUri());
+ } else if (sub->storeDir != storeDir) continue;
+
+ debug("checking substituter '%s' for path '%s'", sub->getUri(), sub->printStorePath(subPath));
+ try {
+ auto info = sub->queryPathInfo(subPath);
+
+ if (sub->storeDir != storeDir && !(info->isContentAddressed(*sub) && info->references.empty()))
+ continue;
+
+ auto narInfo = std::dynamic_pointer_cast<const NarInfo>(
+ std::shared_ptr<const ValidPathInfo>(info));
+ infos.insert_or_assign(path.first, SubstitutablePathInfo{
+ info->deriver,
+ info->references,
+ narInfo ? narInfo->fileSize : 0,
+ info->narSize});
+ } catch (InvalidPath &) {
+ } catch (SubstituterDisabled &) {
+ } catch (Error & e) {
+ if (settings.tryFallback)
+ logError(e.info());
+ else
+ throw;
+ }
+ }
+ }
+}
+
+
bool Store::isValidPath(const StorePath & storePath)
{
{
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 58d7b848e..4d1047380 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -456,7 +456,7 @@ public:
* resulting ‘infos’ map.
*/
virtual void querySubstitutablePathInfos(const StorePathCAMap & paths,
- SubstitutablePathInfos & infos) { return; };
+ SubstitutablePathInfos & infos);
/**
* Import a path into the store.
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index fc009592c..081dbeb28 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -236,8 +236,6 @@ nlohmann::json Args::toJSON()
auto flags = nlohmann::json::object();
for (auto & [name, flag] : longFlags) {
- /* Skip experimental flags when listing flags. */
- if (!experimentalFeatureSettings.isEnabled(flag->experimentalFeature)) continue;
auto j = nlohmann::json::object();
if (flag->aliases.count(name)) continue;
if (flag->shortName)
@@ -249,6 +247,11 @@ nlohmann::json Args::toJSON()
j["arity"] = flag->handler.arity;
if (!flag->labels.empty())
j["labels"] = flag->labels;
+ // TODO With C++23 use `std::optional::tranform`
+ if (auto & xp = flag->experimentalFeature)
+ j["experimental-feature"] = showExperimentalFeature(*xp);
+ else
+ j["experimental-feature"] = nullptr;
flags[name] = std::move(j);
}
@@ -345,6 +348,11 @@ Strings argvToStrings(int argc, char * * argv)
return args;
}
+std::optional<ExperimentalFeature> Command::experimentalFeature ()
+{
+ return { Xp::NixCommand };
+}
+
MultiCommand::MultiCommand(const Commands & commands_)
: commands(commands_)
{
@@ -408,6 +416,11 @@ nlohmann::json MultiCommand::toJSON()
cat["id"] = command->category();
cat["description"] = trim(categories[command->category()]);
j["category"] = std::move(cat);
+ // TODO With C++23 use `std::optional::tranform`
+ if (auto xp = command->experimentalFeature())
+ cat["experimental-feature"] = showExperimentalFeature(*xp);
+ else
+ cat["experimental-feature"] = nullptr;
cmds[name] = std::move(j);
}
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index 07d0d8eae..d90129796 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -236,6 +236,8 @@ struct Command : virtual public Args
static constexpr Category catDefault = 0;
+ virtual std::optional<ExperimentalFeature> experimentalFeature ();
+
virtual Category category() { return catDefault; }
};
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index 7cac75ce1..5a2dd99af 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -65,9 +65,10 @@ public:
switch (lvl) {
case lvlError: c = '3'; break;
case lvlWarn: c = '4'; break;
- case lvlInfo: c = '5'; break;
+ case lvlNotice: case lvlInfo: c = '5'; break;
case lvlTalkative: case lvlChatty: c = '6'; break;
- default: c = '7';
+ case lvlDebug: case lvlVomit: c = '7';
+ default: c = '7'; break; // should not happen, and missing enum case is reported by -Werror=switch-enum
}
prefix = std::string("<") + c + ">";
}
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index a62cb874f..7035e6a7b 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -277,17 +277,17 @@ static void printTree(const StorePath & path,
static void opQuery(Strings opFlags, Strings opArgs)
{
enum QueryType
- { qDefault, qOutputs, qRequisites, qReferences, qReferrers
+ { qOutputs, qRequisites, qReferences, qReferrers
, qReferrersClosure, qDeriver, qBinding, qHash, qSize
, qTree, qGraph, qGraphML, qResolve, qRoots };
- QueryType query = qDefault;
+ std::optional<QueryType> query;
bool useOutput = false;
bool includeOutputs = false;
bool forceRealise = false;
std::string bindingName;
for (auto & i : opFlags) {
- QueryType prev = query;
+ std::optional<QueryType> prev = query;
if (i == "--outputs") query = qOutputs;
else if (i == "--requisites" || i == "-R") query = qRequisites;
else if (i == "--references") query = qReferences;
@@ -312,15 +312,15 @@ static void opQuery(Strings opFlags, Strings opArgs)
else if (i == "--force-realise" || i == "--force-realize" || i == "-f") forceRealise = true;
else if (i == "--include-outputs") includeOutputs = true;
else throw UsageError("unknown flag '%1%'", i);
- if (prev != qDefault && prev != query)
+ if (prev && prev != query)
throw UsageError("query type '%1%' conflicts with earlier flag", i);
}
- if (query == qDefault) query = qOutputs;
+ if (!query) query = qOutputs;
RunPager pager;
- switch (query) {
+ switch (*query) {
case qOutputs: {
for (auto & i : opArgs) {
diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc
index 7da4549a1..354b03cf6 100644
--- a/src/nix/doctor.cc
+++ b/src/nix/doctor.cc
@@ -39,6 +39,14 @@ struct CmdDoctor : StoreCommand
{
bool success = true;
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::string description() override
{
return "check your system for potential problems and print a PASS or FAIL for each check";
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 54c920b4e..4d4164333 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -83,6 +83,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Print full build logs on standard error.",
.category = loggingCategory,
.handler = {[&]() { logger->setPrintBuildLogs(true); }},
+ .experimentalFeature = Xp::NixCommand,
});
addFlag({
@@ -98,6 +99,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Disable substituters and consider all previously downloaded files up-to-date.",
.category = miscCategory,
.handler = {[&]() { useNet = false; }},
+ .experimentalFeature = Xp::NixCommand,
});
addFlag({
@@ -105,6 +107,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.description = "Consider all previously downloaded files out-of-date.",
.category = miscCategory,
.handler = {[&]() { refresh = true; }},
+ .experimentalFeature = Xp::NixCommand,
});
}
@@ -420,10 +423,8 @@ void mainWrapped(int argc, char * * argv)
if (!args.command)
throw UsageError("no subcommand specified");
- if (args.command->first != "repl"
- && args.command->first != "doctor"
- && args.command->first != "upgrade-nix")
- experimentalFeatureSettings.require(Xp::NixCommand);
+ experimentalFeatureSettings.require(
+ args.command->second->experimentalFeature());
if (args.useNet && !haveInternet()) {
warn("you don't have Internet access; disabling some network-dependent features");
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 7aa8774e9..bb14f3f99 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -12,6 +12,14 @@ struct CmdRepl : RawInstallablesCommand
evalSettings.pureEval = false;
}
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::vector<std::string> files;
Strings getDefaultFlakeAttrPaths() override
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 17796d6b8..2295d86d0 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -32,6 +32,14 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
});
}
+ /**
+ * This command is stable before the others
+ */
+ std::optional<ExperimentalFeature> experimentalFeature() override
+ {
+ return std::nullopt;
+ }
+
std::string description() override
{
return "upgrade Nix to the stable version declared in Nixpkgs";
diff --git a/tests/experimental-features.sh b/tests/experimental-features.sh
index 3be77d5cc..a4d55f5f4 100644
--- a/tests/experimental-features.sh
+++ b/tests/experimental-features.sh
@@ -15,9 +15,26 @@ function both_ways {
# Simple case, the configuration effects the running command
both_ways show-config
-# Complicated case, earlier args effect later args
+# Skipping for now, because we actually *do* want these to show up in
+# the manual, just be marked experimental. Will reenable once the manual
+# generation takes advantage of the JSON metadata on this.
-both_ways store gc --help
+# both_ways store gc --help
expect 1 nix --experimental-features 'nix-command' show-config --flake-registry 'https://no'
nix --experimental-features 'nix-command flakes' show-config --flake-registry 'https://no'
+
+# Double check these are stable
+nix --experimental-features '' --help
+nix --experimental-features '' doctor --help
+nix --experimental-features '' repl --help
+nix --experimental-features '' upgrade-nix --help
+
+# These 3 arguments are currently given to all commands, which is wrong (as not
+# all care). To deal with fixing later, we simply make them require the
+# nix-command experimental features --- it so happens that the commands we wish
+# stabilizing to do not need them anyways.
+for arg in '--print-build-logs' '--offline' '--refresh'; do
+ nix --experimental-features 'nix-command' "$arg" --help
+ ! nix --experimental-features '' "$arg" --help
+done
diff --git a/tests/flakes/flakes.sh b/tests/flakes/flakes.sh
index 5c922d7c5..f2e216435 100644
--- a/tests/flakes/flakes.sh
+++ b/tests/flakes/flakes.sh
@@ -96,7 +96,9 @@ json=$(nix flake metadata flake1 --json | jq .)
hash1=$(echo "$json" | jq -r .revision)
echo -n '# foo' >> $flake1Dir/flake.nix
+flake1OriginalCommit=$(git -C $flake1Dir rev-parse HEAD)
git -C $flake1Dir commit -a -m 'Foo'
+flake1NewCommit=$(git -C $flake1Dir rev-parse HEAD)
hash2=$(nix flake metadata flake1 --json --refresh | jq -r .revision)
[[ $hash1 != $hash2 ]]
@@ -491,3 +493,14 @@ nix store delete $(nix store add-path $badFlakeDir)
[[ $(nix-instantiate --eval flake:git+file://$flake3Dir -A x) = 123 ]]
[[ $(nix-instantiate -I flake3=flake:flake3 --eval '<flake3>' -A x) = 123 ]]
[[ $(NIX_PATH=flake3=flake:flake3 nix-instantiate --eval '<flake3>' -A x) = 123 ]]
+
+# Test alternate lockfile paths.
+nix flake lock $flake2Dir --output-lock-file $TEST_ROOT/flake2.lock
+cmp $flake2Dir/flake.lock $TEST_ROOT/flake2.lock >/dev/null # lockfiles should be identical, since we're referencing flake2's original one
+
+nix flake lock $flake2Dir --output-lock-file $TEST_ROOT/flake2-overridden.lock --override-input flake1 git+file://$flake1Dir?rev=$flake1OriginalCommit
+expectStderr 1 cmp $flake2Dir/flake.lock $TEST_ROOT/flake2-overridden.lock
+nix flake metadata $flake2Dir --reference-lock-file $TEST_ROOT/flake2-overridden.lock | grepQuiet $flake1OriginalCommit
+
+# reference-lock-file can only be used if allow-dirty is set.
+expectStderr 1 nix flake metadata $flake2Dir --no-allow-dirty --reference-lock-file $TEST_ROOT/flake2-overridden.lock
diff --git a/tests/lang.sh b/tests/lang.sh
index cdb4174eb..8170cb39d 100644
--- a/tests/lang.sh
+++ b/tests/lang.sh
@@ -5,12 +5,19 @@ export NIX_REMOTE=dummy://
export NIX_STORE_DIR=/nix/store
nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>&1 | grepQuiet Hello
+nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>/dev/null | grepQuiet 123
nix-instantiate --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1
nix-instantiate --trace-verbose --eval -E 'builtins.traceVerbose "Hello" 123' 2>&1 | grepQuiet Hello
nix-instantiate --eval -E 'builtins.traceVerbose "Hello" 123' 2>&1 | grepQuietInverse Hello
nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 | grepQuietInverse Hello
expectStderr 1 nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw "Foo")' | grepQuiet Hello
+nix-instantiate --eval -E 'let x = builtins.trace { x = x; } true; in x' \
+ 2>&1 | grepQuiet -E 'trace: { x = «potential infinite recursion»; }'
+
+nix-instantiate --eval -E 'let x = { repeating = x; tracing = builtins.trace x true; }; in x.tracing'\
+ 2>&1 | grepQuiet -F 'trace: { repeating = «repeated»; tracing = «potential infinite recursion»; }'
+
set +x
fail=0