diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libexpr/eval.cc | 17 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 15 | ||||
-rw-r--r-- | src/libexpr/parser/parser.cc | 8 | ||||
-rw-r--r-- | src/libexpr/parser/state.hh | 2 | ||||
-rw-r--r-- | src/libutil/config-impl.hh | 6 | ||||
-rw-r--r-- | src/libutil/config.cc | 62 | ||||
-rw-r--r-- | src/libutil/config.hh | 43 | ||||
-rw-r--r-- | src/libutil/deprecated-features-json.hh | 29 | ||||
-rw-r--r-- | src/libutil/deprecated-features.cc | 108 | ||||
-rw-r--r-- | src/libutil/deprecated-features.hh | 69 | ||||
-rw-r--r-- | src/libutil/meson.build | 3 | ||||
-rw-r--r-- | src/nix/main.cc | 6 |
12 files changed, 349 insertions, 19 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 702b9b6ac..fcc28d1ca 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2710,20 +2710,29 @@ Expr & EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<Sta } -Expr & EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings) +Expr & EvalState::parseExprFromString( + std::string s_, + const SourcePath & basePath, + std::shared_ptr<StaticEnv> & staticEnv, + const FeatureSettings & featureSettings +) { // NOTE this method (and parseStdin) must take care to *fully copy* their input // into their respective Pos::Origin until the parser stops overwriting its input // data. auto s = make_ref<std::string>(s_); s_.append("\0\0", 2); - return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, xpSettings); + return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, featureSettings); } -Expr & EvalState::parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings) +Expr & EvalState::parseExprFromString( + std::string s, + const SourcePath & basePath, + const FeatureSettings & featureSettings +) { - return parseExprFromString(std::move(s), basePath, staticBaseEnv, xpSettings); + return parseExprFromString(std::move(s), basePath, staticBaseEnv, featureSettings); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index ff45efc08..eab1f22ef 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -344,8 +344,17 @@ public: /** * Parse a Nix expression from the specified string. */ - Expr & parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); - Expr & parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); + Expr & parseExprFromString( + std::string s, + const SourcePath & basePath, + std::shared_ptr<StaticEnv> & staticEnv, + const FeatureSettings & xpSettings = featureSettings + ); + Expr & parseExprFromString( + std::string s, + const SourcePath & basePath, + const FeatureSettings & xpSettings = featureSettings + ); Expr & parseStdin(); @@ -569,7 +578,7 @@ private: Pos::Origin origin, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, - const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); + const FeatureSettings & xpSettings = featureSettings); /** * Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack. diff --git a/src/libexpr/parser/parser.cc b/src/libexpr/parser/parser.cc index 6d496d141..e45776ca6 100644 --- a/src/libexpr/parser/parser.cc +++ b/src/libexpr/parser/parser.cc @@ -115,7 +115,7 @@ struct ExprState std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false) { - if (!state.xpSettings.isEnabled(Xp::PipeOperator)) + if (!state.featureSettings.isEnabled(Xp::PipeOperator)) throw ParseError({ .msg = HintFmt("Pipe operator is disabled"), .pos = state.positions[pos] @@ -656,7 +656,7 @@ template<> struct BuildAST<grammar::expr::path> : p::maybe_nothing {}; template<> struct BuildAST<grammar::expr::uri> { static void apply(const auto & in, ExprState & s, State & ps) { - bool noURLLiterals = ps.xpSettings.isEnabled(Xp::NoUrlLiterals); + bool noURLLiterals = ps.featureSettings.isEnabled(Xp::NoUrlLiterals); if (noURLLiterals) throw ParseError({ .msg = HintFmt("URL literals are disabled"), @@ -858,7 +858,7 @@ Expr * EvalState::parse( Pos::Origin origin, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, - const ExperimentalFeatureSettings & xpSettings) + const FeatureSettings & featureSettings) { parser::State s = { symbols, @@ -866,7 +866,7 @@ Expr * EvalState::parse( basePath, positions.addOrigin(origin, length), exprSymbols, - xpSettings + featureSettings, }; parser::ExprState x; diff --git a/src/libexpr/parser/state.hh b/src/libexpr/parser/state.hh index 30803a37e..9dddd28e1 100644 --- a/src/libexpr/parser/state.hh +++ b/src/libexpr/parser/state.hh @@ -19,7 +19,7 @@ struct State SourcePath basePath; PosTable::Origin origin; const Expr::AstSymbols & s; - const ExperimentalFeatureSettings & xpSettings; + const FeatureSettings & featureSettings; void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos); void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos); diff --git a/src/libutil/config-impl.hh b/src/libutil/config-impl.hh index 8e3a1e408..bc88b5504 100644 --- a/src/libutil/config-impl.hh +++ b/src/libutil/config-impl.hh @@ -34,6 +34,10 @@ template<> struct BaseSetting<std::set<ExperimentalFeature>>::trait { static constexpr bool appendable = true; }; +template<> struct BaseSetting<std::set<DeprecatedFeature>>::trait +{ + static constexpr bool appendable = true; +}; template<typename T> struct BaseSetting<T>::trait @@ -51,6 +55,7 @@ template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append) template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append); template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append); template<> void BaseSetting<std::set<ExperimentalFeature>>::appendOrSet(std::set<ExperimentalFeature> newValue, bool append); +template<> void BaseSetting<std::set<DeprecatedFeature>>::appendOrSet(std::set<DeprecatedFeature> newValue, bool append); template<typename T> void BaseSetting<T>::appendOrSet(T newValue, bool append) @@ -116,6 +121,7 @@ DECLARE_CONFIG_SERIALISER(Strings) DECLARE_CONFIG_SERIALISER(StringSet) DECLARE_CONFIG_SERIALISER(StringMap) DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>) +DECLARE_CONFIG_SERIALISER(std::set<DeprecatedFeature>) template<typename T> T BaseSetting<T>::parse(const std::string & str) const diff --git a/src/libutil/config.cc b/src/libutil/config.cc index 8180886ce..3371e0bb3 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -2,6 +2,7 @@ #include "args.hh" #include "abstract-setting-to-json.hh" #include "experimental-features.hh" +#include "deprecated-features.hh" #include "file-system.hh" #include "logging.hh" #include "strings.hh" @@ -355,6 +356,32 @@ template<> std::string BaseSetting<std::set<ExperimentalFeature>>::to_string() c return concatStringsSep(" ", stringifiedXpFeatures); } +template<> std::set<DeprecatedFeature> BaseSetting<std::set<DeprecatedFeature>>::parse(const std::string & str) const +{ + std::set<DeprecatedFeature> res; + for (auto & s : tokenizeString<StringSet>(str)) { + if (auto thisDpFeature = parseDeprecatedFeature(s); thisDpFeature) + res.insert(thisDpFeature.value()); + else + warn("unknown deprecated feature '%s'", s); + } + return res; +} + +template<> void BaseSetting<std::set<DeprecatedFeature>>::appendOrSet(std::set<DeprecatedFeature> newValue, bool append) +{ + if (!append) value.clear(); + value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end())); +} + +template<> std::string BaseSetting<std::set<DeprecatedFeature>>::to_string() const +{ + StringSet stringifiedDpFeatures; + for (const auto & feature : value) + stringifiedDpFeatures.insert(std::string(showDeprecatedFeature(feature))); + return concatStringsSep(" ", stringifiedDpFeatures); +} + template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) const { StringMap res; @@ -391,6 +418,7 @@ template class BaseSetting<Strings>; template class BaseSetting<StringSet>; template class BaseSetting<StringMap>; template class BaseSetting<std::set<ExperimentalFeature>>; +template class BaseSetting<std::set<DeprecatedFeature>>; static Path parsePath(const AbstractSetting & s, const std::string & str) { @@ -525,28 +553,52 @@ GlobalConfig::Register::Register(Config * config) configRegistrations->emplace_back(config); } -ExperimentalFeatureSettings experimentalFeatureSettings; +FeatureSettings experimentalFeatureSettings; + +FeatureSettings& featureSettings = experimentalFeatureSettings; static GlobalConfig::Register rSettings(&experimentalFeatureSettings); -bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const +bool FeatureSettings::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 +void FeatureSettings::require(const ExperimentalFeature & feature) const { if (!isEnabled(feature)) throw MissingExperimentalFeature(feature); } -bool ExperimentalFeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const +bool FeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const +{ + return !feature || isEnabled(*feature); +} + +void FeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const +{ + if (feature) require(*feature); +} + +bool FeatureSettings::isEnabled(const DeprecatedFeature & feature) const +{ + auto & f = deprecatedFeatures.get(); + return std::find(f.begin(), f.end(), feature) != f.end(); +} + +void FeatureSettings::require(const DeprecatedFeature & feature) const +{ + if (!isEnabled(feature)) + throw MissingDeprecatedFeature(feature); +} + +bool FeatureSettings::isEnabled(const std::optional<DeprecatedFeature> & feature) const { return !feature || isEnabled(*feature); } -void ExperimentalFeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const +void FeatureSettings::require(const std::optional<DeprecatedFeature> & feature) const { if (feature) require(*feature); } diff --git a/src/libutil/config.hh b/src/libutil/config.hh index d7bf9cdc9..a0ad125fb 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -9,6 +9,7 @@ #include "types.hh" #include "experimental-features.hh" +#include "deprecated-features.hh" namespace nix { @@ -441,7 +442,7 @@ struct GlobalConfig : public AbstractConfig extern GlobalConfig globalConfig; -struct ExperimentalFeatureSettings : Config { +struct FeatureSettings : Config { Setting<std::set<ExperimentalFeature>> experimentalFeatures{ this, {}, "experimental-features", @@ -483,9 +484,47 @@ struct ExperimentalFeatureSettings : Config { * disabled, and so the function does nothing in that case. */ void require(const std::optional<ExperimentalFeature> &) const; + + Setting<std::set<DeprecatedFeature>> deprecatedFeatures{ + this, {}, "deprecated-features", + R"( + Deprecated features that are allowed. (Currently there are none.) + + The following deprecated feature features can be re-activated: + + {{#include @generated@/command-ref/deprecated-features-shortlist.md}} + + Deprecated features are [further documented in the manual](@docroot@/contributing/deprecated-features.md). + )"}; + + /** + * Check whether the given deprecated feature is enabled. + */ + bool isEnabled(const DeprecatedFeature &) const; + + /** + * Require an deprecated feature be enabled, throwing an error if it is + * not. + */ + void require(const DeprecatedFeature &) 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<DeprecatedFeature> &) 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<DeprecatedFeature> &) const; }; // FIXME: don't use a global variable. -extern ExperimentalFeatureSettings experimentalFeatureSettings; +extern FeatureSettings& featureSettings; +// Aliases to `featureSettings` for not having to change the name in the code everywhere +using ExperimentalFeatureSettings = FeatureSettings; +extern FeatureSettings experimentalFeatureSettings; } diff --git a/src/libutil/deprecated-features-json.hh b/src/libutil/deprecated-features-json.hh new file mode 100644 index 000000000..787be40dc --- /dev/null +++ b/src/libutil/deprecated-features-json.hh @@ -0,0 +1,29 @@ +#pragma once +///@file + +#include "deprecated-features.hh" +#include "json-utils.hh" + +namespace nix { + +/** + * Compute the documentation of all deprecated features. + * + * See `doc/manual` for how this information is used. + */ +nlohmann::json documentDeprecatedFeatures(); + +/** + * Semi-magic conversion to and from json. + * See the nlohmann/json readme for more details. + */ +void to_json(nlohmann::json &, const DeprecatedFeature &); +void from_json(const nlohmann::json &, DeprecatedFeature &); + +/** + * It is always rendered as a string + */ +template<> +struct json_avoids_null<DeprecatedFeature> : std::true_type {}; + +}; diff --git a/src/libutil/deprecated-features.cc b/src/libutil/deprecated-features.cc new file mode 100644 index 000000000..7c59d8598 --- /dev/null +++ b/src/libutil/deprecated-features.cc @@ -0,0 +1,108 @@ +#include "deprecated-features.hh" +// Required for instances of to_json and from_json for DeprecatedFeature +#include "deprecated-features-json.hh" +#include "strings.hh" + +#include "nlohmann/json.hpp" + +namespace nix { + +struct DeprecatedFeatureDetails +{ + DeprecatedFeature tag; + std::string_view name; + std::string_view description; +}; + +/** + * If two different PRs both add a deprecated feature, and we just + * used a number for this, we *woudln't* get merge conflict and the + * counter will be incremented once instead of twice, causing a build + * failure. + * + * By instead defining this instead as 1 + the bottom deprecated + * feature, we either have no issue at all if few features are not added + * at the end of the list, or a proper merge conflict if they are. + */ +constexpr size_t numDepFeatures = 0; + +constexpr std::array<DeprecatedFeatureDetails, numDepFeatures> depFeatureDetails = {{ +}}; + +static_assert( + []() constexpr { + for (auto [index, feature] : enumerate(depFeatureDetails)) + if (index != (size_t)feature.tag) + return false; + return true; + }(), + "array order does not match enum tag order"); + +const std::optional<DeprecatedFeature> parseDeprecatedFeature(const std::string_view & name) +{ + using ReverseDepMap = std::map<std::string_view, DeprecatedFeature>; + + static std::unique_ptr<ReverseDepMap> reverseDepMap = []() { + auto reverseDepMap = std::make_unique<ReverseDepMap>(); + for (auto & depFeature : depFeatureDetails) + (*reverseDepMap)[depFeature.name] = depFeature.tag; + return reverseDepMap; + }(); + + if (auto feature = get(*reverseDepMap, name)) + return *feature; + else + return std::nullopt; +} + +std::string_view showDeprecatedFeature(const DeprecatedFeature tag) +{ + assert((size_t)tag < depFeatureDetails.size()); + return depFeatureDetails[(size_t)tag].name; +} + +nlohmann::json documentDeprecatedFeatures() +{ + StringMap res; + for (auto & depFeature : depFeatureDetails) + res[std::string { depFeature.name }] = + trim(stripIndentation(depFeature.description)); + return (nlohmann::json) res; +} + +std::set<DeprecatedFeature> parseDeprecatedFeatures(const std::set<std::string> & rawFeatures) +{ + std::set<DeprecatedFeature> res; + for (auto & rawFeature : rawFeatures) + if (auto feature = parseDeprecatedFeature(rawFeature)) + res.insert(*feature); + return res; +} + +MissingDeprecatedFeature::MissingDeprecatedFeature(DeprecatedFeature feature) + : Error("Lix feature '%1%' is deprecated and should not be used anymore; use '--extra-deprecated-features %1%' to disable this error", showDeprecatedFeature(feature)) + , missingFeature(feature) +{} + +std::ostream & operator <<(std::ostream & str, const DeprecatedFeature & feature) +{ + return str << showDeprecatedFeature(feature); +} + +void to_json(nlohmann::json & j, const DeprecatedFeature & feature) +{ + j = showDeprecatedFeature(feature); +} + +void from_json(const nlohmann::json & j, DeprecatedFeature & feature) +{ + const std::string input = j; + const auto parsed = parseDeprecatedFeature(input); + + if (parsed.has_value()) + feature = *parsed; + else + throw Error("Unknown deprecated feature '%s' in JSON input", input); +} + +} diff --git a/src/libutil/deprecated-features.hh b/src/libutil/deprecated-features.hh new file mode 100644 index 000000000..86a9b8a5a --- /dev/null +++ b/src/libutil/deprecated-features.hh @@ -0,0 +1,69 @@ +#pragma once +///@file + +#include "error.hh" +#include "types.hh" + +namespace nix { + +/** + * The list of available deprecated features. + * + * If you update this, don’t forget to also change the map defining + * their string representation and documentation in the corresponding + * `.cc` file as well. + * + * Reminder: New deprecated features should start out with a warning without throwing an error. + * See the developer documentation for details. + */ +enum struct DeprecatedFeature +{ +}; + +/** + * Just because writing `DeprecatedFeature::UrlLiterals` is way too long + */ +using Dep = DeprecatedFeature; + +/** + * Parse a deprecated feature (enum value) from its name. Deprecated + * feature flag names are hyphenated and do not contain spaces. + */ +const std::optional<DeprecatedFeature> parseDeprecatedFeature( + const std::string_view & name); + +/** + * Show the name of a deprecated feature. This is the opposite of + * parseDeprecatedFeature(). + */ +std::string_view showDeprecatedFeature(const DeprecatedFeature); + +/** + * Shorthand for `str << showDeprecatedFeature(feature)`. + */ +std::ostream & operator<<( + std::ostream & str, + const DeprecatedFeature & feature); + +/** + * Parse a set of strings to the corresponding set of deprecated + * features, ignoring (but warning for) any unknown feature. + */ +std::set<DeprecatedFeature> parseDeprecatedFeatures(const std::set<std::string> &); + +/** + * A deprecated feature used for some + * operation, but was not enabled. + */ +class MissingDeprecatedFeature : public Error +{ +public: + /** + * The deprecated feature that was required but not enabled. + */ + DeprecatedFeature missingFeature; + + MissingDeprecatedFeature(DeprecatedFeature missingFeature); +}; + +} diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 41336874b..3e10e4b63 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -7,6 +7,7 @@ libutil_sources = files( 'compute-levels.cc', 'config.cc', 'current-process.cc', + 'deprecated-features.cc', 'english.cc', 'environment-variables.cc', 'error.cc', @@ -64,6 +65,8 @@ libutil_headers = files( 'config-impl.hh', 'config.hh', 'current-process.hh', + 'deprecated-features.hh', + 'deprecated-features-json.hh', 'english.hh', 'environment-variables.hh', 'error.hh', diff --git a/src/nix/main.cc b/src/nix/main.cc index 9cbe303ac..5356a0d04 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -14,6 +14,7 @@ #include "loggers.hh" #include "markdown.hh" #include "experimental-features-json.hh" +#include "deprecated-features-json.hh" #include <sys/types.h> #include <sys/socket.h> @@ -422,6 +423,11 @@ void mainWrapped(int argc, char * * argv) return; } + if (argc == 2 && std::string(argv[1]) == "__dump-dp-features") { + logger->cout(documentDeprecatedFeatures().dump()); + return; + } + Finally printCompletions([&]() { if (args.completions) { |