diff options
author | piegames <git@piegames.de> | 2024-07-13 05:24:41 +0200 |
---|---|---|
committer | piegames <git@piegames.de> | 2024-08-17 19:47:51 +0200 |
commit | 49d61b2e4bf338042364c85d3c2ead0b33963e65 (patch) | |
tree | 09ffba6841df5a3990aa2d1c6bb9e19e0e355b14 /src/libutil | |
parent | 1c080a8239f1be5a61d9fb2121ca958542ec183f (diff) |
libexpr: Introduce Deprecated features
They are like experimental features, but opt-in instead of opt-out. They
will allow us to gracefully remove language features. See #437
Change-Id: I9ca04cc48e6926750c4d622c2b229b25cc142c42
Diffstat (limited to 'src/libutil')
-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 |
7 files changed, 313 insertions, 7 deletions
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', |