Merge pull request #7609 from obsidiansystems/hide-experimental-settings

Hide experimental settings
This commit is contained in:
John Ericson 2023-03-27 09:19:29 -04:00 committed by GitHub
commit 570829d67e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 223 additions and 101 deletions

View file

@ -305,7 +305,7 @@ connected:
std::set<Realisation> missingRealisations; std::set<Realisation> missingRealisations;
StorePathSet missingPaths; StorePathSet missingPaths;
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) { for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName); auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
@ -337,7 +337,7 @@ connected:
for (auto & realisation : missingRealisations) { for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set // Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty // of missing realisations should be empty
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
store->registerDrvOutput(realisation); store->registerDrvOutput(realisation);
} }

View file

@ -166,7 +166,7 @@ Path lookupFileArg(EvalState & state, std::string_view s)
} }
else if (hasPrefix(s, "flake:")) { else if (hasPrefix(s, "flake:")) {
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false); auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath; auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
return state.store->toRealPath(storePath); return state.store->toRealPath(storePath);

View file

@ -332,7 +332,7 @@ void completeFlakeRefWithFragment(
void completeFlakeRef(ref<Store> store, std::string_view prefix) void completeFlakeRef(ref<Store> store, std::string_view prefix)
{ {
if (!settings.isExperimentalFeatureEnabled(Xp::Flakes)) if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
return; return;
if (prefix == "") if (prefix == "")

View file

@ -320,7 +320,7 @@ LockedFlake lockFlake(
const FlakeRef & topRef, const FlakeRef & topRef,
const LockFlags & lockFlags) const LockFlags & lockFlags)
{ {
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
FlakeCache flakeCache; FlakeCache flakeCache;

View file

@ -469,7 +469,7 @@ expr_simple
new ExprString(std::move(path))}); new ExprString(std::move(path))});
} }
| URI { | URI {
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals); static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals) if (noURLLiterals)
throw ParseError({ throw ParseError({
.msg = hintfmt("URL literals are disabled"), .msg = hintfmt("URL literals are disabled"),
@ -816,7 +816,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
} }
else if (hasPrefix(elem.second, "flake:")) { else if (hasPrefix(elem.second, "flake:")) {
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false); auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", elem.second); debug("fetching flake search path element '%s''", elem.second);
auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath; auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;

View file

@ -1141,13 +1141,13 @@ drvName, Bindings * attrs, Value & v)
if (i->name == state.sContentAddressed) { if (i->name == state.sContentAddressed) {
contentAddressed = state.forceBool(*i->value, noPos, context_below); contentAddressed = state.forceBool(*i->value, noPos, context_below);
if (contentAddressed) if (contentAddressed)
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
} }
else if (i->name == state.sImpure) { else if (i->name == state.sImpure) {
isImpure = state.forceBool(*i->value, noPos, context_below); isImpure = state.forceBool(*i->value, noPos, context_below);
if (isImpure) if (isImpure)
settings.requireExperimentalFeature(Xp::ImpureDerivations); experimentalFeatureSettings.require(Xp::ImpureDerivations);
} }
/* The `args' attribute is special: it supplies the /* The `args' attribute is special: it supplies the
@ -4114,7 +4114,7 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps) if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.experimentalFeature if (!primOp.experimentalFeature
|| settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature)) || experimentalFeatureSettings.isEnabled(*primOp.experimentalFeature))
{ {
addPrimOp({ addPrimOp({
.fun = primOp.fun, .fun = primOp.fun,

View file

@ -190,7 +190,7 @@ static void fetchTree(
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false }); fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
} }

View file

@ -75,21 +75,25 @@ struct FetchSettings : public Config
Path or URI of the global flake registry. Path or URI of the global flake registry.
When empty, disables the global flake registry. When empty, disables the global flake registry.
)"}; )",
{}, true, Xp::Flakes};
Setting<bool> useRegistries{this, true, "use-registries", Setting<bool> useRegistries{this, true, "use-registries",
"Whether to use flake registries to resolve flake references."}; "Whether to use flake registries to resolve flake references.",
{}, true, Xp::Flakes};
Setting<bool> acceptFlakeConfig{this, false, "accept-flake-config", Setting<bool> acceptFlakeConfig{this, false, "accept-flake-config",
"Whether to accept nix configuration from a flake without prompting."}; "Whether to accept nix configuration from a flake without prompting.",
{}, true, Xp::Flakes};
Setting<std::string> commitLockFileSummary{ Setting<std::string> commitLockFileSummary{
this, "", "commit-lockfile-summary", this, "", "commit-lockfile-summary",
R"( R"(
The commit summary to use when committing changed flake lock files. If The commit summary to use when committing changed flake lock files. If
empty, the summary is generated based on the action performed. empty, the summary is generated based on the action performed.
)"}; )",
{}, true, Xp::Flakes};
}; };
// FIXME: don't use a global variable. // FIXME: don't use a global variable.

View file

@ -199,10 +199,10 @@ void DerivationGoal::haveDerivation()
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv); parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
if (!drv->type().hasKnownOutputPaths()) if (!drv->type().hasKnownOutputPaths())
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
if (!drv->type().isPure()) { if (!drv->type().isPure()) {
settings.requireExperimentalFeature(Xp::ImpureDerivations); experimentalFeatureSettings.require(Xp::ImpureDerivations);
for (auto & [outputName, output] : drv->outputs) { for (auto & [outputName, output] : drv->outputs) {
auto randomPath = StorePath::random(outputPathName(drv->name, outputName)); auto randomPath = StorePath::random(outputPathName(drv->name, outputName));
@ -336,7 +336,7 @@ void DerivationGoal::gaveUpOnSubstitution()
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) { for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
/* Ensure that pure, non-fixed-output derivations don't /* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */ depend on impure derivations. */
if (settings.isExperimentalFeatureEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) { if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first); auto inputDrv = worker.evalStore.readDerivation(i.first);
if (!inputDrv.type().isPure()) if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'", throw Error("pure derivation '%s' depends on impure derivation '%s'",
@ -477,7 +477,7 @@ void DerivationGoal::inputsRealised()
ca.fixed ca.fixed
/* Can optionally resolve if fixed, which is good /* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */ for avoiding unnecessary rebuilds. */
? settings.isExperimentalFeatureEnabled(Xp::CaDerivations) ? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
/* Must resolve if floating and there are any inputs /* Must resolve if floating and there are any inputs
drvs. */ drvs. */
: true); : true);
@ -488,7 +488,7 @@ void DerivationGoal::inputsRealised()
}, drvType.raw()); }, drvType.raw());
if (resolveDrv && !fullDrv.inputDrvs.empty()) { if (resolveDrv && !fullDrv.inputDrvs.empty()) {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the /* We are be able to resolve this derivation based on the
now-known results of dependencies. If so, we become a now-known results of dependencies. If so, we become a
@ -1352,7 +1352,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
}; };
} }
auto drvOutput = DrvOutput{info.outputHash, i.first}; auto drvOutput = DrvOutput{info.outputHash, i.first};
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
if (auto real = worker.store.queryRealisation(drvOutput)) { if (auto real = worker.store.queryRealisation(drvOutput)) {
info.known = { info.known = {
.path = real->outPath, .path = real->outPath,

View file

@ -413,7 +413,7 @@ void LocalDerivationGoal::startBuilder()
) )
{ {
#if __linux__ #if __linux__
settings.requireExperimentalFeature(Xp::Cgroups); experimentalFeatureSettings.require(Xp::Cgroups);
auto cgroupFS = getCgroupFS(); auto cgroupFS = getCgroupFS();
if (!cgroupFS) if (!cgroupFS)
@ -1420,7 +1420,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
void LocalDerivationGoal::startDaemon() void LocalDerivationGoal::startDaemon()
{ {
settings.requireExperimentalFeature(Xp::RecursiveNix); experimentalFeatureSettings.require(Xp::RecursiveNix);
Store::Params params; Store::Params params;
params["path-info-cache-size"] = "0"; params["path-info-cache-size"] = "0";
@ -2280,7 +2280,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
bool discardReferences = false; bool discardReferences = false;
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) { if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
settings.requireExperimentalFeature(Xp::DiscardReferences); experimentalFeatureSettings.require(Xp::DiscardReferences);
if (auto output = get(*udr, outputName)) { if (auto output = get(*udr, outputName)) {
if (!output->is_boolean()) if (!output->is_boolean())
throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string()); throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());
@ -2700,7 +2700,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
}, },
.outPath = newInfo.path .outPath = newInfo.path
}; };
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
&& drv->type().isPure()) && drv->type().isPure())
{ {
signRealisation(thisRealisation); signRealisation(thisRealisation);

View file

@ -231,10 +231,10 @@ struct ClientSettings
try { try {
if (name == "ssh-auth-sock") // obsolete if (name == "ssh-auth-sock") // obsolete
; ;
else if (name == settings.experimentalFeatures.name) { else if (name == experimentalFeatureSettings.experimentalFeatures.name) {
// We dont want to forward the experimental features to // We dont want to forward the experimental features to
// the daemon, as that could cause some pretty weird stuff // the daemon, as that could cause some pretty weird stuff
if (parseFeatures(tokenizeString<StringSet>(value)) != settings.experimentalFeatures.get()) if (parseFeatures(tokenizeString<StringSet>(value)) != experimentalFeatureSettings.experimentalFeatures.get())
debug("Ignoring the client-specified experimental features"); debug("Ignoring the client-specified experimental features");
} else if (name == settings.pluginFiles.name) { } else if (name == settings.pluginFiles.name) {
if (tokenizeString<Paths>(value) != settings.pluginFiles.get()) if (tokenizeString<Paths>(value) != settings.pluginFiles.get())

View file

@ -221,7 +221,7 @@ static DerivationOutput parseDerivationOutput(const Store & store,
} }
const auto hashType = parseHashType(hashAlgo); const auto hashType = parseHashType(hashAlgo);
if (hash == "impure") { if (hash == "impure") {
settings.requireExperimentalFeature(Xp::ImpureDerivations); experimentalFeatureSettings.require(Xp::ImpureDerivations);
assert(pathS == ""); assert(pathS == "");
return DerivationOutput::Impure { return DerivationOutput::Impure {
.method = std::move(method), .method = std::move(method),
@ -236,7 +236,7 @@ static DerivationOutput parseDerivationOutput(const Store & store,
}, },
}; };
} else { } else {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
assert(pathS == ""); assert(pathS == "");
return DerivationOutput::CAFloating { return DerivationOutput::CAFloating {
.method = std::move(method), .method = std::move(method),

View file

@ -105,7 +105,7 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
auto drvHashes = auto drvHashes =
staticOutputHashes(store, store.readDerivation(p.drvPath)); staticOutputHashes(store, store.readDerivation(p.drvPath));
for (auto& [outputName, outputPath] : p.outputs) { for (auto& [outputName, outputPath] : p.outputs) {
if (settings.isExperimentalFeatureEnabled( if (experimentalFeatureSettings.isEnabled(
Xp::CaDerivations)) { Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName); auto drvOutput = get(drvHashes, outputName);
if (!drvOutput) if (!drvOutput)

View file

@ -166,18 +166,6 @@ StringSet Settings::getDefaultExtraPlatforms()
return extraPlatforms; return extraPlatforms;
} }
bool Settings::isExperimentalFeatureEnabled(const ExperimentalFeature & feature)
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void Settings::requireExperimentalFeature(const ExperimentalFeature & feature)
{
if (!isExperimentalFeatureEnabled(feature))
throw MissingExperimentalFeature(feature);
}
bool Settings::isWSL1() bool Settings::isWSL1()
{ {
struct utsname utsbuf; struct utsname utsbuf;

View file

@ -3,7 +3,6 @@
#include "types.hh" #include "types.hh"
#include "config.hh" #include "config.hh"
#include "util.hh" #include "util.hh"
#include "experimental-features.hh"
#include <map> #include <map>
#include <limits> #include <limits>
@ -932,13 +931,6 @@ public:
are loaded as plugins (non-recursively). are loaded as plugins (non-recursively).
)"}; )"};
Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
"Experimental Nix features to enable."};
bool isExperimentalFeatureEnabled(const ExperimentalFeature &);
void requireExperimentalFeature(const ExperimentalFeature &);
Setting<size_t> narBufferSize{this, 32 * 1024 * 1024, "nar-buffer-size", Setting<size_t> narBufferSize{this, 32 * 1024 * 1024, "nar-buffer-size",
"Maximum size of NARs before spilling them to disk."}; "Maximum size of NARs before spilling them to disk."};

View file

@ -336,7 +336,7 @@ LocalStore::LocalStore(const Params & params)
else openDB(*state, false); else openDB(*state, false);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock); migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
} }
@ -366,7 +366,7 @@ LocalStore::LocalStore(const Params & params)
state->stmts->QueryPathFromHashPart.create(state->db, state->stmts->QueryPathFromHashPart.create(state->db,
"select path from ValidPaths where path >= ? limit 1;"); "select path from ValidPaths where path >= ? limit 1;");
state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths"); state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths");
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
state->stmts->RegisterRealisedOutput.create(state->db, state->stmts->RegisterRealisedOutput.create(state->db,
R"( R"(
insert into Realisations (drvPath, outputName, outputPath, signatures) insert into Realisations (drvPath, outputName, outputPath, signatures)
@ -754,7 +754,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs) void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
{ {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info)) if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
registerDrvOutput(info); registerDrvOutput(info);
else else
@ -763,7 +763,7 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
void LocalStore::registerDrvOutput(const Realisation & info) void LocalStore::registerDrvOutput(const Realisation & info)
{ {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
retrySQLite<void>([&]() { retrySQLite<void>([&]() {
auto state(_state.lock()); auto state(_state.lock());
if (auto oldR = queryRealisation_(*state, info.id)) { if (auto oldR = queryRealisation_(*state, info.id)) {
@ -1052,7 +1052,7 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
return outputs; return outputs;
}); });
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
return outputs; return outputs;
auto drv = readInvalidDerivation(path); auto drv = readInvalidDerivation(path);

View file

@ -129,7 +129,7 @@ struct AutoUserLock : UserLock
useUserNamespace = false; useUserNamespace = false;
#endif #endif
settings.requireExperimentalFeature(Xp::AutoAllocateUids); experimentalFeatureSettings.require(Xp::AutoAllocateUids);
assert(settings.startId > 0); assert(settings.startId > 0);
assert(settings.uidCount % maxIdsPerBuild == 0); assert(settings.uidCount % maxIdsPerBuild == 0);
assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits<uid_t>::max()); assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits<uid_t>::max());

View file

@ -326,7 +326,7 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
throw Error( throw Error(
"the derivation '%s' doesn't have an output named '%s'", "the derivation '%s' doesn't have an output named '%s'",
store.printStorePath(bfd.drvPath), output); store.printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
DrvOutput outputId { *outputHash, output }; DrvOutput outputId { *outputHash, output };
auto realisation = store.queryRealisation(outputId); auto realisation = store.queryRealisation(outputId);
if (!realisation) if (!realisation)

View file

@ -265,7 +265,7 @@ void RemoteStore::setOptions(Connection & conn)
overrides.erase(settings.buildCores.name); overrides.erase(settings.buildCores.name);
overrides.erase(settings.useSubstitutes.name); overrides.erase(settings.useSubstitutes.name);
overrides.erase(loggerSettings.showTrace.name); overrides.erase(loggerSettings.showTrace.name);
overrides.erase(settings.experimentalFeatures.name); overrides.erase(experimentalFeatureSettings.experimentalFeatures.name);
overrides.erase(settings.pluginFiles.name); overrides.erase(settings.pluginFiles.name);
conn.to << overrides.size(); conn.to << overrides.size();
for (auto & i : overrides) for (auto & i : overrides)
@ -876,7 +876,7 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
"the derivation '%s' doesn't have an output named '%s'", "the derivation '%s' doesn't have an output named '%s'",
printStorePath(bfd.drvPath), output); printStorePath(bfd.drvPath), output);
auto outputId = DrvOutput{ *outputHash, output }; auto outputId = DrvOutput{ *outputHash, output };
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto realisation = auto realisation =
queryRealisation(outputId); queryRealisation(outputId);
if (!realisation) if (!realisation)

View file

@ -445,10 +445,10 @@ StringSet StoreConfig::getDefaultSystemFeatures()
{ {
auto res = settings.systemFeatures.get(); auto res = settings.systemFeatures.get();
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
res.insert("ca-derivations"); res.insert("ca-derivations");
if (settings.isExperimentalFeatureEnabled(Xp::RecursiveNix)) if (experimentalFeatureSettings.isEnabled(Xp::RecursiveNix))
res.insert("recursive-nix"); res.insert("recursive-nix");
return res; return res;
@ -1017,7 +1017,7 @@ std::map<StorePath, StorePath> copyPaths(
for (auto & path : paths) { for (auto & path : paths) {
storePaths.insert(path.path()); storePaths.insert(path.path());
if (auto realisation = std::get_if<Realisation>(&path.raw)) { if (auto realisation = std::get_if<Realisation>(&path.raw)) {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
toplevelRealisations.insert(*realisation); toplevelRealisations.insert(*realisation);
} }
} }
@ -1250,7 +1250,7 @@ std::optional<StorePath> Store::getBuildDerivationPath(const StorePath & path)
} }
} }
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations) || !isValidPath(path)) if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations) || !isValidPath(path))
return path; return path;
auto drv = readDerivation(path); auto drv = readDerivation(path);

View file

@ -52,7 +52,7 @@ std::shared_ptr<Completions> completions;
std::string completionMarker = "___COMPLETE___"; std::string completionMarker = "___COMPLETE___";
std::optional<std::string> needsCompletion(std::string_view s) static std::optional<std::string> needsCompletion(std::string_view s)
{ {
if (!completions) return {}; if (!completions) return {};
auto i = s.find(completionMarker); auto i = s.find(completionMarker);
@ -120,6 +120,12 @@ void Args::parseCmdline(const Strings & _cmdline)
if (!argsSeen) if (!argsSeen)
initialFlagsProcessed(); initialFlagsProcessed();
/* Now that we are done parsing, make sure that any experimental
* feature required by the flags is enabled */
for (auto & f : flagExperimentalFeatures)
experimentalFeatureSettings.require(f);
} }
bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
@ -128,12 +134,18 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
auto process = [&](const std::string & name, const Flag & flag) -> bool { auto process = [&](const std::string & name, const Flag & flag) -> bool {
++pos; ++pos;
if (auto & f = flag.experimentalFeature)
flagExperimentalFeatures.insert(*f);
std::vector<std::string> args; std::vector<std::string> args;
bool anyCompleted = false; bool anyCompleted = false;
for (size_t n = 0 ; n < flag.handler.arity; ++n) { for (size_t n = 0 ; n < flag.handler.arity; ++n) {
if (pos == end) { if (pos == end) {
if (flag.handler.arity == ArityAny || anyCompleted) break; if (flag.handler.arity == ArityAny || anyCompleted) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); throw UsageError(
"flag '%s' requires %d argument(s), but only %d were given",
name, flag.handler.arity, n);
} }
if (auto prefix = needsCompletion(*pos)) { if (auto prefix = needsCompletion(*pos)) {
anyCompleted = true; anyCompleted = true;
@ -152,8 +164,12 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
for (auto & [name, flag] : longFlags) { for (auto & [name, flag] : longFlags) {
if (!hiddenCategories.count(flag->category) if (!hiddenCategories.count(flag->category)
&& hasPrefix(name, std::string(*prefix, 2))) && hasPrefix(name, std::string(*prefix, 2)))
{
if (auto & f = flag->experimentalFeature)
flagExperimentalFeatures.insert(*f);
completions->add("--" + name, flag->description); completions->add("--" + name, flag->description);
} }
}
return false; return false;
} }
auto i = longFlags.find(std::string(*pos, 2)); auto i = longFlags.find(std::string(*pos, 2));
@ -172,6 +188,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (prefix == "-") { if (prefix == "-") {
completions->add("--"); completions->add("--");
for (auto & [flagName, flag] : shortFlags) for (auto & [flagName, flag] : shortFlags)
if (experimentalFeatureSettings.isEnabled(flag->experimentalFeature))
completions->add(std::string("-") + flagName, flag->description); completions->add(std::string("-") + flagName, flag->description);
} }
} }
@ -219,6 +236,8 @@ nlohmann::json Args::toJSON()
auto flags = nlohmann::json::object(); auto flags = nlohmann::json::object();
for (auto & [name, flag] : longFlags) { for (auto & [name, flag] : longFlags) {
/* Skip experimental flags when listing flags. */
if (!experimentalFeatureSettings.isEnabled(flag->experimentalFeature)) continue;
auto j = nlohmann::json::object(); auto j = nlohmann::json::object();
if (flag->aliases.count(name)) continue; if (flag->aliases.count(name)) continue;
if (flag->shortName) if (flag->shortName)

View file

@ -117,6 +117,8 @@ protected:
Handler handler; Handler handler;
std::function<void(size_t, std::string_view)> completer; std::function<void(size_t, std::string_view)> completer;
std::optional<ExperimentalFeature> experimentalFeature;
static Flag mkHashTypeFlag(std::string && longName, HashType * ht); static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht); static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht);
}; };
@ -188,6 +190,16 @@ public:
friend class MultiCommand; friend class MultiCommand;
MultiCommand * parent = nullptr; MultiCommand * parent = nullptr;
private:
/**
* Experimental features needed when parsing args. These are checked
* after flag parsing is completed in order to support enabling
* experimental features coming after the flag that needs the
* experimental feature.
*/
std::set<ExperimentalFeature> flagExperimentalFeatures;
}; };
/* A command is an argument parser that can be executed by calling its /* A command is an argument parser that can be executed by calling its
@ -253,8 +265,6 @@ enum CompletionType {
}; };
extern CompletionType completionType; extern CompletionType completionType;
std::optional<std::string> needsCompletion(std::string_view s);
void completePath(size_t, std::string_view prefix); void completePath(size_t, std::string_view prefix);
void completeDir(size_t, std::string_view prefix); void completeDir(size_t, std::string_view prefix);

View file

@ -70,10 +70,17 @@ void AbstractConfig::reapplyUnknownSettings()
set(s.first, s.second); set(s.first, s.second);
} }
// Whether we should process the option. Excludes aliases, which are handled elsewhere, and disabled features.
static bool applicable(const Config::SettingData & sd)
{
return !sd.isAlias
&& experimentalFeatureSettings.isEnabled(sd.setting->experimentalFeature);
}
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly) void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{ {
for (auto & opt : _settings) for (auto & opt : _settings)
if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden)) if (applicable(opt.second) && (!overriddenOnly || opt.second.setting->overridden))
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description}); res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
} }
@ -147,9 +154,8 @@ nlohmann::json Config::toJSON()
{ {
auto res = nlohmann::json::object(); auto res = nlohmann::json::object();
for (auto & s : _settings) for (auto & s : _settings)
if (!s.second.isAlias) { if (applicable(s.second))
res.emplace(s.first, s.second.setting->toJSON()); res.emplace(s.first, s.second.setting->toJSON());
}
return res; return res;
} }
@ -157,24 +163,31 @@ std::string Config::toKeyValue()
{ {
auto res = std::string(); auto res = std::string();
for (auto & s : _settings) for (auto & s : _settings)
if (!s.second.isAlias) { if (applicable(s.second))
res += fmt("%s = %s\n", s.first, s.second.setting->to_string()); res += fmt("%s = %s\n", s.first, s.second.setting->to_string());
}
return res; return res;
} }
void Config::convertToArgs(Args & args, const std::string & category) void Config::convertToArgs(Args & args, const std::string & category)
{ {
for (auto & s : _settings) for (auto & s : _settings) {
/* We do include args for settings gated on disabled
experimental-features. The args themselves however will also be
gated on any experimental feature the underlying setting is. */
if (!s.second.isAlias) if (!s.second.isAlias)
s.second.setting->convertToArg(args, category); s.second.setting->convertToArg(args, category);
}
} }
AbstractSetting::AbstractSetting( AbstractSetting::AbstractSetting(
const std::string & name, const std::string & name,
const std::string & description, const std::string & description,
const std::set<std::string> & aliases) const std::set<std::string> & aliases,
: name(name), description(stripIndentation(description)), aliases(aliases) std::optional<ExperimentalFeature> experimentalFeature)
: name(name)
, description(stripIndentation(description))
, aliases(aliases)
, experimentalFeature(experimentalFeature)
{ {
} }
@ -210,6 +223,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
.category = category, .category = category,
.labels = {"value"}, .labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s); }}, .handler = {[this](std::string s) { overridden = true; set(s); }},
.experimentalFeature = experimentalFeature,
}); });
if (isAppendable()) if (isAppendable())
@ -219,6 +233,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
.category = category, .category = category,
.labels = {"value"}, .labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s, true); }}, .handler = {[this](std::string s) { overridden = true; set(s, true); }},
.experimentalFeature = experimentalFeature,
}); });
} }
@ -270,13 +285,15 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
.longName = name, .longName = name,
.description = fmt("Enable the `%s` setting.", name), .description = fmt("Enable the `%s` setting.", name),
.category = category, .category = category,
.handler = {[this]() { override(true); }} .handler = {[this]() { override(true); }},
.experimentalFeature = experimentalFeature,
}); });
args.addFlag({ args.addFlag({
.longName = "no-" + name, .longName = "no-" + name,
.description = fmt("Disable the `%s` setting.", name), .description = fmt("Disable the `%s` setting.", name),
.category = category, .category = category,
.handler = {[this]() { override(false); }} .handler = {[this]() { override(false); }},
.experimentalFeature = experimentalFeature,
}); });
} }
@ -444,4 +461,30 @@ GlobalConfig::Register::Register(Config * config)
configRegistrations->emplace_back(config); configRegistrations->emplace_back(config);
} }
ExperimentalFeatureSettings experimentalFeatureSettings;
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void ExperimentalFeatureSettings::require(const ExperimentalFeature & feature) const
{
if (!isEnabled(feature))
throw MissingExperimentalFeature(feature);
}
bool ExperimentalFeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const
{
return !feature || isEnabled(*feature);
}
void ExperimentalFeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const
{
if (feature) require(*feature);
}
} }

View file

@ -1,12 +1,13 @@
#pragma once
#include <cassert> #include <cassert>
#include <map> #include <map>
#include <set> #include <set>
#include "types.hh"
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#pragma once #include "types.hh"
#include "experimental-features.hh"
namespace nix { namespace nix {
@ -194,12 +195,15 @@ public:
bool overridden = false; bool overridden = false;
std::optional<ExperimentalFeature> experimentalFeature;
protected: protected:
AbstractSetting( AbstractSetting(
const std::string & name, const std::string & name,
const std::string & description, const std::string & description,
const std::set<std::string> & aliases); const std::set<std::string> & aliases,
std::optional<ExperimentalFeature> experimentalFeature = std::nullopt);
virtual ~AbstractSetting() virtual ~AbstractSetting()
{ {
@ -240,8 +244,9 @@ public:
const bool documentDefault, const bool documentDefault,
const std::string & name, const std::string & name,
const std::string & description, const std::string & description,
const std::set<std::string> & aliases = {}) const std::set<std::string> & aliases = {},
: AbstractSetting(name, description, aliases) std::optional<ExperimentalFeature> experimentalFeature = std::nullopt)
: AbstractSetting(name, description, aliases, experimentalFeature)
, value(def) , value(def)
, defaultValue(def) , defaultValue(def)
, documentDefault(documentDefault) , documentDefault(documentDefault)
@ -296,8 +301,9 @@ public:
const std::string & name, const std::string & name,
const std::string & description, const std::string & description,
const std::set<std::string> & aliases = {}, const std::set<std::string> & aliases = {},
const bool documentDefault = true) const bool documentDefault = true,
: BaseSetting<T>(def, documentDefault, name, description, aliases) std::optional<ExperimentalFeature> experimentalFeature = std::nullopt)
: BaseSetting<T>(def, documentDefault, name, description, aliases, experimentalFeature)
{ {
options->addSetting(this); options->addSetting(this);
} }
@ -357,4 +363,37 @@ struct GlobalConfig : public AbstractConfig
extern GlobalConfig globalConfig; extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {
Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
"Experimental Nix features to enable."};
/**
* Check whether the given experimental feature is enabled.
*/
bool isEnabled(const ExperimentalFeature &) const;
/**
* Require an experimental feature be enabled, throwing an error if it is
* not.
*/
void require(const ExperimentalFeature &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function returns true in that case.
*/
bool isEnabled(const std::optional<ExperimentalFeature> &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function does nothing in that case.
*/
void require(const std::optional<ExperimentalFeature> &) const;
};
// FIXME: don't use a global variable.
extern ExperimentalFeatureSettings experimentalFeatureSettings;
} }

View file

@ -450,7 +450,6 @@ template<class C> Strings quoteStrings(const C & c)
return res; return res;
} }
/* Remove trailing whitespace from a string. FIXME: return /* Remove trailing whitespace from a string. FIXME: return
std::string_view. */ std::string_view. */
std::string chomp(std::string_view s); std::string chomp(std::string_view s);

View file

@ -440,7 +440,7 @@ static void main_nix_build(int argc, char * * argv)
shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash"; shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash";
} }
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto resolvedDrv = drv.tryResolve(*store); auto resolvedDrv = drv.tryResolve(*store);
assert(resolvedDrv && "Successfully resolved the derivation"); assert(resolvedDrv && "Successfully resolved the derivation");
drv = *resolvedDrv; drv = *resolvedDrv;

View file

@ -208,7 +208,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
drv.name += "-env"; drv.name += "-env";
drv.env.emplace("name", drv.name); drv.env.emplace("name", drv.name);
drv.inputSrcs.insert(std::move(getEnvShPath)); drv.inputSrcs.insert(std::move(getEnvShPath));
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) { for (auto & output : drv.outputs) {
output.second = DerivationOutput::Deferred {}, output.second = DerivationOutput::Deferred {},
drv.env[output.first] = hashPlaceholder(output.first); drv.env[output.first] = hashPlaceholder(output.first);

View file

@ -1335,7 +1335,7 @@ struct CmdFlake : NixMultiCommand
{ {
if (!command) if (!command)
throw UsageError("'nix flake' requires a sub-command."); throw UsageError("'nix flake' requires a sub-command.");
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
command->second->run(); command->second->run();
} }
}; };

View file

@ -54,12 +54,11 @@ static bool haveInternet()
std::string programPath; std::string programPath;
struct HelpRequested { };
struct NixArgs : virtual MultiCommand, virtual MixCommonArgs struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{ {
bool useNet = true; bool useNet = true;
bool refresh = false; bool refresh = false;
bool helpRequested = false;
bool showVersion = false; bool showVersion = false;
NixArgs() : MultiCommand(RegisterCommand::getCommandsFor({})), MixCommonArgs("nix") NixArgs() : MultiCommand(RegisterCommand::getCommandsFor({})), MixCommonArgs("nix")
@ -74,7 +73,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.longName = "help", .longName = "help",
.description = "Show usage information.", .description = "Show usage information.",
.category = miscCategory, .category = miscCategory,
.handler = {[&]() { throw HelpRequested(); }}, .handler = {[this]() { this->helpRequested = true; }},
}); });
addFlag({ addFlag({
@ -297,7 +296,10 @@ void mainWrapped(int argc, char * * argv)
} }
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") { if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
settings.experimentalFeatures = {Xp::Flakes, Xp::FetchClosure}; experimentalFeatureSettings.experimentalFeatures = {
Xp::Flakes,
Xp::FetchClosure,
};
evalSettings.pureEval = false; evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://")); EvalState state({}, openStore("dummy://"));
auto res = nlohmann::json::object(); auto res = nlohmann::json::object();
@ -334,7 +336,11 @@ void mainWrapped(int argc, char * * argv)
try { try {
args.parseCmdline(argvToStrings(argc, argv)); args.parseCmdline(argvToStrings(argc, argv));
} catch (HelpRequested &) { } catch (UsageError &) {
if (!args.helpRequested && !completions) throw;
}
if (args.helpRequested) {
std::vector<std::string> subcommand; std::vector<std::string> subcommand;
MultiCommand * command = &args; MultiCommand * command = &args;
while (command) { while (command) {
@ -346,8 +352,6 @@ void mainWrapped(int argc, char * * argv)
} }
showHelp(subcommand, args); showHelp(subcommand, args);
return; return;
} catch (UsageError &) {
if (!completions) throw;
} }
if (completions) { if (completions) {
@ -366,7 +370,7 @@ void mainWrapped(int argc, char * * argv)
if (args.command->first != "repl" if (args.command->first != "repl"
&& args.command->first != "doctor" && args.command->first != "doctor"
&& args.command->first != "upgrade-nix") && args.command->first != "upgrade-nix")
settings.requireExperimentalFeature(Xp::NixCommand); experimentalFeatureSettings.require(Xp::NixCommand);
if (args.useNet && !haveInternet()) { if (args.useNet && !haveInternet()) {
warn("you don't have Internet access; disabling some network-dependent features"); warn("you don't have Internet access; disabling some network-dependent features");

View file

@ -45,7 +45,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON
void run(ref<Store> store, BuiltPaths && paths) override void run(ref<Store> store, BuiltPaths && paths) override
{ {
settings.requireExperimentalFeature(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
RealisedPath::Set realisations; RealisedPath::Set realisations;
for (auto & builtPath : paths) { for (auto & builtPath : paths) {

View file

@ -224,7 +224,7 @@ struct CmdRegistry : virtual NixMultiCommand
void run() override void run() override
{ {
settings.requireExperimentalFeature(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
if (!command) if (!command)
throw UsageError("'nix registry' requires a sub-command."); throw UsageError("'nix registry' requires a sub-command.");
command->second->run(); command->second->run();

View file

@ -38,7 +38,7 @@ struct CmdRepl : RawInstallablesCommand
void applyDefaultInstallables(std::vector<std::string> & rawInstallables) override void applyDefaultInstallables(std::vector<std::string> & rawInstallables) override
{ {
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && rawInstallables.size() >= 1) { if (!experimentalFeatureSettings.isEnabled(Xp::ReplFlake) && !(file) && rawInstallables.size() >= 1) {
warn("future versions of Nix will require using `--file` to load a file"); warn("future versions of Nix will require using `--file` to load a file");
if (rawInstallables.size() > 1) if (rawInstallables.size() > 1)
warn("more than one input file is not currently supported"); warn("more than one input file is not currently supported");

View file

@ -0,0 +1,23 @@
source common.sh
# Without flakes, flake options should not show up
# With flakes, flake options should show up
function both_ways {
nix --experimental-features 'nix-command' "$@" | grepQuietInverse flake
nix --experimental-features 'nix-command flakes' "$@" | grepQuiet flake
# Also, the order should not matter
nix "$@" --experimental-features 'nix-command' | grepQuietInverse flake
nix "$@" --experimental-features 'nix-command flakes' | grepQuiet flake
}
# Simple case, the configuration effects the running command
both_ways show-config
# Complicated case, earlier args effect later args
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'

View file

@ -18,6 +18,7 @@ nix_tests = \
gc.sh \ gc.sh \
remote-store.sh \ remote-store.sh \
lang.sh \ lang.sh \
experimental-features.sh \
fetchMercurial.sh \ fetchMercurial.sh \
gc-auto.sh \ gc-auto.sh \
user-envs.sh \ user-envs.sh \