aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorregnat <rg@regnat.ovh>2021-10-25 15:53:01 +0200
committerregnat <rg@regnat.ovh>2021-10-26 07:02:31 +0200
commitaf99941279b80c962ec9cae3e5fa32976a3f5744 (patch)
tree034ffb01457354b07e9b6dac02892e1b5d8fca09 /src
parent4a2b7cc68c7d11ec126bc412ffea838e629345af (diff)
Make experimental-features a proper type
Rather than having them plain strings scattered through the whole codebase, create an enum containing all the known experimental features. This means that - Nix can now `warn` when an unkwown experimental feature is passed (making it much nicer to spot typos and spot deprecated features) - It’s now easy to remove a feature altogether (once the feature isn’t experimental anymore or is dropped) by just removing the field for the enum and letting the compiler point us to all the now invalid usages of it.
Diffstat (limited to 'src')
-rw-r--r--src/build-remote/build-remote.cc5
-rw-r--r--src/libcmd/installables.cc2
-rw-r--r--src/libexpr/eval.cc2
-rw-r--r--src/libexpr/eval.hh3
-rw-r--r--src/libexpr/flake/flake.cc4
-rw-r--r--src/libexpr/parser.y2
-rw-r--r--src/libexpr/primops.cc2
-rw-r--r--src/libexpr/primops/fetchTree.cc10
-rw-r--r--src/libstore/build/derivation-goal.cc6
-rw-r--r--src/libstore/build/entry-points.cc2
-rw-r--r--src/libstore/build/local-derivation-goal.cc6
-rw-r--r--src/libstore/daemon.cc2
-rw-r--r--src/libstore/derivations.cc2
-rw-r--r--src/libstore/derived-path.cc2
-rw-r--r--src/libstore/globals.cc15
-rw-r--r--src/libstore/globals.hh16
-rw-r--r--src/libstore/local-store.cc10
-rw-r--r--src/libstore/store-api.cc6
-rw-r--r--src/libutil/config.cc27
-rw-r--r--src/libutil/experimental-features.cc55
-rw-r--r--src/libutil/experimental-features.hh56
-rwxr-xr-xsrc/nix-build/nix-build.cc2
-rw-r--r--src/nix/develop.cc2
-rw-r--r--src/nix/flake.cc2
-rw-r--r--src/nix/main.cc2
-rw-r--r--src/nix/realisation.cc2
-rw-r--r--src/nix/sigs.cc2
27 files changed, 187 insertions, 60 deletions
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index 0559aeaf4..a6722a1f8 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -18,6 +18,7 @@
#include "derivations.hh"
#include "local-store.hh"
#include "legacy.hh"
+#include "experimental-features.hh"
using namespace nix;
using std::cin;
@@ -295,7 +296,7 @@ connected:
std::set<Realisation> missingRealisations;
StorePathSet missingPaths;
- if (settings.isExperimentalFeatureEnabled("ca-derivations") && !derivationHasKnownOutputPaths(drv.type())) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
@@ -327,7 +328,7 @@ connected:
for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
store->registerDrvOutput(realisation);
}
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 0f0fcf39e..5758b52ad 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -714,7 +714,7 @@ BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPa
"the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(
- "ca-derivations")) {
+ Xp::CaDerivations)) {
auto outputId =
DrvOutput{outputHashes.at(output), output};
auto realisation =
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 3ae05a8d8..db1e7e56d 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -466,7 +466,7 @@ EvalState::~EvalState()
void EvalState::requireExperimentalFeatureOnEvaluation(
- const std::string & feature,
+ const ExperimentalFeature & feature,
const std::string_view fName,
const Pos & pos)
{
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 7cc16ef0a..69119599a 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -5,6 +5,7 @@
#include "nixexpr.hh"
#include "symbol-table.hh"
#include "config.hh"
+#include "experimental-features.hh"
#include <map>
#include <optional>
@@ -141,7 +142,7 @@ public:
~EvalState();
void requireExperimentalFeatureOnEvaluation(
- const std::string & feature,
+ const ExperimentalFeature &,
const std::string_view fName,
const Pos & pos
);
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 43bfc3644..c9d848495 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -297,7 +297,7 @@ LockedFlake lockFlake(
const FlakeRef & topRef,
const LockFlags & lockFlags)
{
- settings.requireExperimentalFeature("flakes");
+ settings.requireExperimentalFeature(Xp::Flakes);
FlakeCache flakeCache;
@@ -687,7 +687,7 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- state.requireExperimentalFeatureOnEvaluation("flakes", "builtins.getFlake", pos);
+ state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
auto flakeRefS = state.forceStringNoCtx(*args[0], pos);
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 8a0a79c96..380afa6b7 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -418,7 +418,7 @@ expr_simple
new ExprString(data->symbols.create(path)));
}
| URI {
- static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals");
+ static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = hintfmt("URL literals are disabled"),
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 4e0eda7f3..6b3cafec8 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -985,7 +985,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
}
if (i->name == state.sContentAddressed) {
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
contentAddressed = state.forceBool(*i->value, pos);
}
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index b00a79b7f..dacd78fd3 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -176,7 +176,7 @@ static void fetchTree(
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- settings.requireExperimentalFeature("flakes");
+ settings.requireExperimentalFeature(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
}
@@ -290,7 +290,7 @@ static RegisterPrimOp primop_fetchTarball({
The fetched tarball is cached for a certain amount of time (1 hour
by default) in `~/.cache/nix/tarballs/`. You can change the cache
timeout either on the command line with `--option tarball-ttl number
- of seconds` or in the Nix configuration file with this option: `
+ of seconds` or in the Nix configuration file with this option: `
number of seconds to cache `.
Note that when obtaining the hash with ` nix-prefetch-url ` the
@@ -393,7 +393,7 @@ static RegisterPrimOp primop_fetchGit({
```
> **Note**
- >
+ >
> It is nice to always specify the branch which a revision
> belongs to. Without the branch being specified, the fetcher
> might fail if the default branch changes. Additionally, it can
@@ -430,12 +430,12 @@ static RegisterPrimOp primop_fetchGit({
```
> **Note**
- >
+ >
> Nix will refetch the branch in accordance with
> the option `tarball-ttl`.
> **Note**
- >
+ >
> This behavior is disabled in *Pure evaluation mode*.
)",
.fun = prim_fetchGit,
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 0907120db..67cf8b067 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -204,7 +204,7 @@ void DerivationGoal::haveDerivation()
trace("have derivation");
if (drv->type() == DerivationType::CAFloating)
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
retrySubstitution = false;
@@ -453,7 +453,7 @@ void DerivationGoal::inputsRealised()
if (useDerivation) {
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
- if (settings.isExperimentalFeatureEnabled("ca-derivations") &&
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) &&
((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type()))
|| fullDrv.type() == DerivationType::DeferredInputAddressed)) {
/* We are be able to resolve this derivation based on the
@@ -1273,7 +1273,7 @@ void DerivationGoal::checkPathValidity()
: PathStatus::Corrupt,
};
}
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
if (auto real = worker.store.queryRealisation(drvOutput)) {
info.known = {
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index 2b77e4354..065efc855 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -74,7 +74,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
outputId,
Realisation{ outputId, *staticOutput.second}
);
- if (settings.isExperimentalFeatureEnabled("ca-derivations") && !derivationHasKnownOutputPaths(drv.type())) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
auto realisation = this->queryRealisation(outputId);
if (realisation)
result.builtOutputs.insert_or_assign(
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 8d245f84a..fab6c3a08 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -1259,7 +1259,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
for (auto & [outputName, outputPath] : outputs)
if (wantOutput(outputName, bfd.outputs)) {
newPaths.insert(outputPath);
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
auto thisRealisation = next->queryRealisation(
DrvOutput{drvHashes.at(outputName), outputName}
);
@@ -1320,7 +1320,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
void LocalDerivationGoal::startDaemon()
{
- settings.requireExperimentalFeature("recursive-nix");
+ settings.requireExperimentalFeature(Xp::RecursiveNix);
Store::Params params;
params["path-info-cache-size"] = "0";
@@ -2561,7 +2561,7 @@ void LocalDerivationGoal::registerOutputs()
that for floating CA derivations, which otherwise couldn't be cached,
but it's fine to do in all cases. */
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
for (auto& [outputName, newInfo] : infos) {
auto thisRealisation = Realisation{
.id = DrvOutput{initialOutputs.at(outputName).outputHash,
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 164a9b2be..4e8a3c417 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -230,7 +230,7 @@ struct ClientSettings
else if (name == settings.experimentalFeatures.name) {
// We don’t want to forward the experimental features to
// the daemon, as that could cause some pretty weird stuff
- if (tokenizeString<Strings>(value) != settings.experimentalFeatures.get())
+ if (parseFeatures(tokenizeString<StringSet>(value)) != settings.experimentalFeatures.get())
debug("Ignoring the client-specified experimental features");
}
else if (trusted
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index ef8765841..b926bb711 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -187,7 +187,7 @@ static DerivationOutput parseDerivationOutput(const Store & store,
},
};
} else {
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
assert(pathS == "");
return DerivationOutput {
.output = DerivationOutputCAFloating {
diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc
index e55af21e9..3d188e981 100644
--- a/src/libstore/derived-path.cc
+++ b/src/libstore/derived-path.cc
@@ -100,7 +100,7 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
staticOutputHashes(store, store.readDerivation(p.drvPath));
for (auto& [outputName, outputPath] : p.outputs) {
if (settings.isExperimentalFeatureEnabled(
- "ca-derivations")) {
+ Xp::CaDerivations)) {
auto thisRealisation = store.queryRealisation(
DrvOutput{drvHashes.at(outputName), outputName});
assert(thisRealisation); // We’ve built it, so we must h
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 4903d0922..9f1a88130 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -160,21 +160,16 @@ StringSet Settings::getDefaultExtraPlatforms()
return extraPlatforms;
}
-bool Settings::isExperimentalFeatureEnabled(const std::string & name)
+bool Settings::isExperimentalFeatureEnabled(const ExperimentalFeature & feature)
{
auto & f = experimentalFeatures.get();
- return std::find(f.begin(), f.end(), name) != f.end();
+ return std::find(f.begin(), f.end(), feature) != f.end();
}
-MissingExperimentalFeature::MissingExperimentalFeature(std::string feature)
- : Error("experimental Nix feature '%1%' is disabled; use '--extra-experimental-features %1%' to override", feature)
- , missingFeature(feature)
- {}
-
-void Settings::requireExperimentalFeature(const std::string & name)
+void Settings::requireExperimentalFeature(const ExperimentalFeature & feature)
{
- if (!isExperimentalFeatureEnabled(name))
- throw MissingExperimentalFeature(name);
+ if (!isExperimentalFeatureEnabled(feature))
+ throw MissingExperimentalFeature(feature);
}
bool Settings::isWSL1()
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 8784d5faf..165639261 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -3,6 +3,7 @@
#include "types.hh"
#include "config.hh"
#include "util.hh"
+#include "experimental-features.hh"
#include <map>
#include <limits>
@@ -45,15 +46,6 @@ struct PluginFilesSetting : public BaseSetting<Paths>
void set(const std::string & str, bool append = false) override;
};
-class MissingExperimentalFeature: public Error
-{
-public:
- std::string missingFeature;
-
- MissingExperimentalFeature(std::string feature);
- virtual const char* sname() const override { return "MissingExperimentalFeature"; }
-};
-
class Settings : public Config {
unsigned int getDefaultCores();
@@ -925,12 +917,12 @@ public:
value.
)"};
- Setting<Strings> experimentalFeatures{this, {}, "experimental-features",
+ Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
"Experimental Nix features to enable."};
- bool isExperimentalFeatureEnabled(const std::string & name);
+ bool isExperimentalFeatureEnabled(const ExperimentalFeature &);
- void requireExperimentalFeature(const std::string & name);
+ void requireExperimentalFeature(const ExperimentalFeature &);
Setting<bool> allowDirty{this, true, "allow-dirty",
"Whether to allow dirty Git/Mercurial trees."};
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 5b2490472..1e3b5482e 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -309,7 +309,7 @@ LocalStore::LocalStore(const Params & params)
else openDB(*state, false);
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
}
@@ -339,7 +339,7 @@ LocalStore::LocalStore(const Params & params)
state->stmts->QueryPathFromHashPart.create(state->db,
"select path from ValidPaths where path >= ? limit 1;");
state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths");
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
state->stmts->RegisterRealisedOutput.create(state->db,
R"(
insert into Realisations (drvPath, outputName, outputPath, signatures)
@@ -708,7 +708,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
{
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
registerDrvOutput(info);
else
@@ -717,7 +717,7 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
void LocalStore::registerDrvOutput(const Realisation & info)
{
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
retrySQLite<void>([&]() {
auto state(_state.lock());
if (auto oldR = queryRealisation_(*state, info.id)) {
@@ -1003,7 +1003,7 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
return outputs;
});
- if (!settings.isExperimentalFeatureEnabled("ca-derivations"))
+ if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations))
return outputs;
auto drv = readInvalidDerivation(path);
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index b5ff3dccf..3338cdc1b 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -355,7 +355,7 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
StringSet StoreConfig::getDefaultSystemFeatures()
{
auto res = settings.systemFeatures.get();
- if (settings.isExperimentalFeatureEnabled("ca-derivations"))
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations))
res.insert("ca-derivations");
return res;
}
@@ -860,7 +860,7 @@ std::map<StorePath, StorePath> copyPaths(
for (auto & path : paths) {
storePaths.insert(path.path());
if (auto realisation = std::get_if<Realisation>(&path.raw)) {
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
toplevelRealisations.insert(*realisation);
}
}
@@ -892,7 +892,7 @@ std::map<StorePath, StorePath> copyPaths(
// Don't fail if the remote doesn't support CA derivations is it might
// not be within our control to change that, and we might still want
// to at least copy the output paths.
- if (e.missingFeature == "ca-derivations")
+ if (e.missingFeature == Xp::CaDerivations)
ignoreException();
else
throw;
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index c247c7dae..026db6574 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -1,6 +1,7 @@
#include "config.hh"
#include "args.hh"
#include "abstract-setting-to-json.hh"
+#include "experimental-features.hh"
#include <nlohmann/json.hpp>
@@ -313,6 +314,31 @@ template<> std::string BaseSetting<StringSet>::to_string() const
return concatStringsSep(" ", value);
}
+template<> void BaseSetting<std::set<ExperimentalFeature>>::set(const std::string & str, bool append)
+{
+ if (!append) value.clear();
+ for (auto & s : tokenizeString<StringSet>(str)) {
+ auto thisXpFeature = parseExperimentalFeature(s);
+ if (thisXpFeature)
+ value.insert(thisXpFeature.value());
+ else
+ warn("Unknown experimental feature %s", s);
+ }
+}
+
+template<> bool BaseSetting<std::set<ExperimentalFeature>>::isAppendable()
+{
+ return true;
+}
+
+template<> std::string BaseSetting<std::set<ExperimentalFeature>>::to_string() const
+{
+ StringSet stringifiedXpFeatures;
+ for (auto & feature : value)
+ stringifiedXpFeatures.insert(std::string(showExperimentalFeature(feature)));
+ return concatStringsSep(" ", stringifiedXpFeatures);
+}
+
template<> void BaseSetting<StringMap>::set(const std::string & str, bool append)
{
if (!append) value.clear();
@@ -348,6 +374,7 @@ template class BaseSetting<std::string>;
template class BaseSetting<Strings>;
template class BaseSetting<StringSet>;
template class BaseSetting<StringMap>;
+template class BaseSetting<std::set<ExperimentalFeature>>;
void PathSetting::set(const std::string & str, bool append)
{
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
new file mode 100644
index 000000000..d1235d8a4
--- /dev/null
+++ b/src/libutil/experimental-features.cc
@@ -0,0 +1,55 @@
+#include "experimental-features.hh"
+#include "nlohmann/json.hpp"
+
+namespace nix {
+
+std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
+ { Xp::CaDerivations, "ca-derivations" },
+ { Xp::Flakes, "flakes" },
+ { Xp::NixCommand, "nix-command" },
+ { Xp::RecursiveNix, "recursive-nix" },
+ { Xp::NoUrlLiterals, "no-url-literals" },
+};
+
+const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
+{
+ using ReverseXpMap = std::map<std::string_view, ExperimentalFeature>;
+ static ReverseXpMap * reverseXpMap;
+ if (!reverseXpMap) {
+ reverseXpMap = new ReverseXpMap{};
+ for (auto & [feature, name] : stringifiedXpFeatures)
+ (*reverseXpMap)[name] = feature;
+ }
+
+ auto featureIter = reverseXpMap->find(name);
+ if (featureIter == reverseXpMap->end())
+ return std::nullopt;
+ return {featureIter->second};
+}
+
+std::string_view showExperimentalFeature(const ExperimentalFeature feature)
+{
+ return stringifiedXpFeatures.at(feature);
+}
+
+std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
+{
+ std::set<ExperimentalFeature> res;
+ for (auto & rawFeature : rawFeatures) {
+ if (auto feature = parseExperimentalFeature(rawFeature))
+ res.insert(*feature);
+ }
+ return res;
+}
+
+MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature)
+ : Error("experimental Nix feature '%1%' is disabled; use '--extra-experimental-features %1%' to override", showExperimentalFeature(feature))
+ , missingFeature(feature)
+ {}
+
+std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & feature)
+{
+ return str << showExperimentalFeature(feature);
+}
+
+}
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
new file mode 100644
index 000000000..291a58e32
--- /dev/null
+++ b/src/libutil/experimental-features.hh
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "comparator.hh"
+#include "error.hh"
+#include "nlohmann/json_fwd.hpp"
+#include "types.hh"
+
+namespace nix {
+
+/**
+ * The list of available experimental features.
+ *
+ * If you update this, don’t forget to also change the map defining their
+ * string representation in the corresponding `.cc` file.
+ **/
+enum struct ExperimentalFeature
+{
+ CaDerivations,
+ Flakes,
+ NixCommand,
+ RecursiveNix,
+ NoUrlLiterals
+};
+
+/**
+ * Just because writing `ExperimentalFeature::CaDerivations` is way too long
+ */
+using Xp = ExperimentalFeature;
+
+const std::optional<ExperimentalFeature> parseExperimentalFeature(
+ const std::string_view & name);
+std::string_view showExperimentalFeature(const ExperimentalFeature);
+
+std::ostream & operator<<(
+ std::ostream & str,
+ const ExperimentalFeature & feature);
+
+/**
+ * Parse a set of strings to the corresponding set of experimental features,
+ * ignoring (but warning for) any unkwown feature.
+ */
+std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> &);
+
+class MissingExperimentalFeature : public Error
+{
+public:
+ ExperimentalFeature missingFeature;
+
+ MissingExperimentalFeature(ExperimentalFeature);
+ virtual const char * sname() const override
+ {
+ return "MissingExperimentalFeature";
+ }
+};
+
+}
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index 77594f046..73d93480e 100755
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -401,7 +401,7 @@ static void main_nix_build(int argc, char * * argv)
if (dryRun) return;
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
auto resolvedDrv = drv.tryResolve(*store);
assert(resolvedDrv && "Successfully resolved the derivation");
drv = *resolvedDrv;
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index f22335023..5aad53919 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -195,7 +195,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
'buildDerivation', but that's privileged. */
drv.name += "-env";
drv.inputSrcs.insert(std::move(getEnvShPath));
- if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
+ if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) {
output.second = {
.output = DerivationOutputDeferred{},
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 7e4d23f6e..68bb76742 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -1138,7 +1138,7 @@ struct CmdFlake : NixMultiCommand
{
if (!command)
throw UsageError("'nix flake' requires a sub-command.");
- settings.requireExperimentalFeature("flakes");
+ settings.requireExperimentalFeature(Xp::Flakes);
command->second->prepare();
command->second->run();
}
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 2c3976689..1e033f4f2 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -337,7 +337,7 @@ void mainWrapped(int argc, char * * argv)
if (args.command->first != "repl"
&& args.command->first != "doctor"
&& args.command->first != "upgrade-nix")
- settings.requireExperimentalFeature("nix-command");
+ settings.requireExperimentalFeature(Xp::NixCommand);
if (args.useNet && !haveInternet()) {
warn("you don't have Internet access; disabling some network-dependent features");
diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc
index dfa8ff449..c9a7157cd 100644
--- a/src/nix/realisation.cc
+++ b/src/nix/realisation.cc
@@ -46,7 +46,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON
void run(ref<Store> store, BuiltPaths && paths) override
{
- settings.requireExperimentalFeature("ca-derivations");
+ settings.requireExperimentalFeature(Xp::CaDerivations);
RealisedPath::Set realisations;
for (auto & builtPath : paths) {
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 43e0d9148..6a238efbe 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -219,7 +219,7 @@ struct CmdKey : NixMultiCommand
{
if (!command)
throw UsageError("'nix flake' requires a sub-command.");
- settings.requireExperimentalFeature("flakes");
+ settings.requireExperimentalFeature(Xp::Flakes);
command->second->prepare();
command->second->run();
}