aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/generate-features-shortlist.nix (renamed from doc/manual/generate-xp-features-shortlist.nix)7
-rw-r--r--doc/manual/generate-features.nix18
-rw-r--r--doc/manual/generate-xp-features.nix13
-rw-r--r--doc/manual/meson.build6
-rw-r--r--doc/manual/src/SUMMARY.md1
-rw-r--r--doc/manual/src/command-ref/meson.build25
-rw-r--r--doc/manual/src/contributing/deprecated-features.md37
-rw-r--r--doc/manual/src/contributing/meson.build17
-rw-r--r--src/libexpr/eval.cc17
-rw-r--r--src/libexpr/eval.hh15
-rw-r--r--src/libexpr/parser/parser.cc8
-rw-r--r--src/libexpr/parser/state.hh2
-rw-r--r--src/libutil/config-impl.hh6
-rw-r--r--src/libutil/config.cc62
-rw-r--r--src/libutil/config.hh43
-rw-r--r--src/libutil/deprecated-features-json.hh29
-rw-r--r--src/libutil/deprecated-features.cc108
-rw-r--r--src/libutil/deprecated-features.hh69
-rw-r--r--src/libutil/meson.build3
-rw-r--r--src/nix/main.cc6
-rw-r--r--tests/unit/libexpr-support/tests/libexpr.hh4
-rw-r--r--tests/unit/libexpr/trivial.cc26
22 files changed, 468 insertions, 54 deletions
diff --git a/doc/manual/generate-xp-features-shortlist.nix b/doc/manual/generate-features-shortlist.nix
index ea8bf8d49..055698d64 100644
--- a/doc/manual/generate-xp-features-shortlist.nix
+++ b/doc/manual/generate-features-shortlist.nix
@@ -1,9 +1,14 @@
+# Usually "experimental" or "deprecated"
+kind:
+# "xp" or "dp"
+kindShort:
+
with builtins;
with import ./utils.nix;
let
showExperimentalFeature = name: doc: ''
- - [`${name}`](@docroot@/contributing/experimental-features.md#xp-feature-${name})
+ - [`${name}`](@docroot@/contributing/${kind}-features.md#${kindShort}-feature-${name})
'';
in
xps: indent " " (concatStrings (attrValues (mapAttrs showExperimentalFeature xps)))
diff --git a/doc/manual/generate-features.nix b/doc/manual/generate-features.nix
new file mode 100644
index 000000000..4a12ccdce
--- /dev/null
+++ b/doc/manual/generate-features.nix
@@ -0,0 +1,18 @@
+# Usually "experimental" or "deprecated"
+_kind:
+# "xp" or "dp"
+kindShort:
+
+with builtins;
+with import ./utils.nix;
+
+let
+ showFeature =
+ name: doc:
+ squash ''
+ ## [`${name}`]{#${kindShort}-feature-${name}}
+
+ ${doc}
+ '';
+in
+xps: (concatStringsSep "\n" (attrValues (mapAttrs showFeature xps)))
diff --git a/doc/manual/generate-xp-features.nix b/doc/manual/generate-xp-features.nix
deleted file mode 100644
index c56ddeea1..000000000
--- a/doc/manual/generate-xp-features.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-with builtins;
-with import ./utils.nix;
-
-let
- showExperimentalFeature =
- name: doc:
- squash ''
- ## [`${name}`]{#xp-feature-${name}}
-
- ${doc}
- '';
-in
-xps: (concatStringsSep "\n" (attrValues (mapAttrs showExperimentalFeature xps)))
diff --git a/doc/manual/meson.build b/doc/manual/meson.build
index 673c86149..1bdbba77e 100644
--- a/doc/manual/meson.build
+++ b/doc/manual/meson.build
@@ -72,9 +72,9 @@ generate_manual_deps = files(
# Generates builtins.md and builtin-constants.md.
subdir('src/language')
-# Generates new-cli pages, experimental-features-shortlist.md, and conf-file.md.
+# Generates new-cli pages, {experimental,deprecated}-features-shortlist.md, and conf-file.md.
subdir('src/command-ref')
-# Generates experimental-feature-descriptions.md.
+# Generates {experimental,deprecated}-feature-descriptions.md.
subdir('src/contributing')
# Generates rl-next-generated.md.
subdir('src/release-notes')
@@ -106,6 +106,8 @@ manual = custom_target(
nix3_cli_files,
experimental_features_shortlist_md,
experimental_feature_descriptions_md,
+ deprecated_features_shortlist_md,
+ deprecated_feature_descriptions_md,
conf_file_md,
builtins_md,
builtin_constants_md,
diff --git a/doc/manual/src/SUMMARY.md b/doc/manual/src/SUMMARY.md
index 4fac7cbca..03dc7e198 100644
--- a/doc/manual/src/SUMMARY.md
+++ b/doc/manual/src/SUMMARY.md
@@ -192,6 +192,7 @@
- [Hacking](contributing/hacking.md)
- [Testing](contributing/testing.md)
- [Experimental Features](contributing/experimental-features.md)
+ - [Deprecated Features](contributing/deprecated-features.md)
- [CLI guideline](contributing/cli-guideline.md)
- [C++ style guide](contributing/cxx.md)
- [Release Notes](release-notes/release-notes.md)
diff --git a/doc/manual/src/command-ref/meson.build b/doc/manual/src/command-ref/meson.build
index 03d5f0a9c..ef6f9d2e0 100644
--- a/doc/manual/src/command-ref/meson.build
+++ b/doc/manual/src/command-ref/meson.build
@@ -7,10 +7,10 @@ xp_features_json = custom_target(
experimental_features_shortlist_md = custom_target(
command : nix_eval_for_docs + [
'--expr',
- 'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
+ 'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
],
input : [
- '../../generate-xp-features-shortlist.nix',
+ '../../generate-features-shortlist.nix',
xp_features_json,
],
capture : true,
@@ -18,6 +18,26 @@ experimental_features_shortlist_md = custom_target(
env : nix_env_for_docs,
)
+dp_features_json = custom_target(
+ command : [nix, '__dump-dp-features'],
+ capture : true,
+ output : 'dp-features.json',
+)
+
+deprecated_features_shortlist_md = custom_target(
+ command : nix_eval_for_docs + [
+ '--expr',
+ 'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
+ ],
+ input : [
+ '../../generate-features-shortlist.nix',
+ dp_features_json,
+ ],
+ capture : true,
+ output : 'deprecated-features-shortlist.md',
+ env : nix_env_for_docs,
+)
+
# Intermediate step for manpage generation.
# This splorks the output of generate-manpage.nix as JSON,
# which gets written as a directory tree below.
@@ -60,6 +80,7 @@ conf_file_md = custom_target(
'../../utils.nix',
conf_file_json,
experimental_features_shortlist_md,
+ deprecated_features_shortlist_md,
],
output : 'conf-file.md',
env : nix_env_for_docs,
diff --git a/doc/manual/src/contributing/deprecated-features.md b/doc/manual/src/contributing/deprecated-features.md
new file mode 100644
index 000000000..7536944f7
--- /dev/null
+++ b/doc/manual/src/contributing/deprecated-features.md
@@ -0,0 +1,37 @@
+This section describes the notion of *deprecated features*, and how it fits into the big picture of the development of Lix.
+
+# What are deprecated features?
+
+Deprecated features are disabled by default, with the intent to eventually remove them.
+Users must explicitly enable them to keep using them, by toggling the associated [deprecated feature flags](@docroot@/command-ref/conf-file.md#conf-deprecated-features).
+This allows backwards compatibility and a graceful transition away from undesired features.
+
+# Which features can be deprecated?
+
+Undesired features should be soft-deprecated by yielding a warning when used for a significant amount of time before the can be deprecated.
+Legacy obsolete feature with little to no usage may go through this process faster.
+Deprecated features should have a migration path to a preferred alternative.
+
+# Lifecycle of a deprecated feature
+
+This description is not normative, but a feature removal may roughly happen like this:
+
+1. Add a warning when the feature is being used.
+2. Disable the feature by default, putting it behind a deprecated feature flag.
+ - If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely.
+ For example, `--extra-experimental-features=no-url-literals` becomes `--extra-deprecated-features=url-literals`.
+3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.
+ - Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
+ - At least one NixOS release cycle should be the minimum
+4. Finally remove the feature entirely, only keeping the error message for those still using it.
+
+# Relation to language versioning
+
+Obviously, removing anything breaks backwards compatibility.
+In an ideal world, we'd have SemVer controls over the language and its features, cleanly allowing us to make breaking changes.
+See https://wiki.lix.systems/books/lix-contributors/page/language-versioning and [RFC 137](https://github.com/nixos/rfcs/pull/137) for efforts on that.
+However, we do not live in such an ideal world, and currently this goal is so far away, that "just disable it with some back-compat for a couple of years" is the most realistic solution, especially for comparatively minor changes.
+
+# Currently available deprecated features
+
+{{#include @generated@/contributing/deprecated-feature-descriptions.md}}
diff --git a/doc/manual/src/contributing/meson.build b/doc/manual/src/contributing/meson.build
index 2929578c8..8c76a6206 100644
--- a/doc/manual/src/contributing/meson.build
+++ b/doc/manual/src/contributing/meson.build
@@ -4,12 +4,25 @@
experimental_feature_descriptions_md = custom_target(
command : nix_eval_for_docs + [
'--expr',
- 'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
+ 'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
],
input : [
- '../../generate-xp-features.nix',
+ '../../generate-features.nix',
xp_features_json,
],
capture : true,
output : 'experimental-feature-descriptions.md',
)
+
+deprecated_feature_descriptions_md = custom_target(
+ command : nix_eval_for_docs + [
+ '--expr',
+ 'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
+ ],
+ input : [
+ '../../generate-features.nix',
+ dp_features_json,
+ ],
+ capture : true,
+ output : 'deprecated-feature-descriptions.md',
+)
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) {
diff --git a/tests/unit/libexpr-support/tests/libexpr.hh b/tests/unit/libexpr-support/tests/libexpr.hh
index 745aa168d..642632baa 100644
--- a/tests/unit/libexpr-support/tests/libexpr.hh
+++ b/tests/unit/libexpr-support/tests/libexpr.hh
@@ -26,9 +26,9 @@ namespace nix {
, state({}, store)
{
}
- Value eval(std::string input, bool forceValue = true, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings) {
+ Value eval(std::string input, bool forceValue = true, const FeatureSettings & fSettings = featureSettings) {
Value v;
- Expr & e = state.parseExprFromString(input, state.rootPath(CanonPath::root), xpSettings);
+ Expr & e = state.parseExprFromString(input, state.rootPath(CanonPath::root), fSettings);
state.eval(e, v);
if (forceValue)
state.forceValue(v, noPos);
diff --git a/tests/unit/libexpr/trivial.cc b/tests/unit/libexpr/trivial.cc
index c984657fd..46f9b7499 100644
--- a/tests/unit/libexpr/trivial.cc
+++ b/tests/unit/libexpr/trivial.cc
@@ -214,36 +214,36 @@ namespace nix {
// pipes are gated behind an experimental feature flag
TEST_F(TrivialExpressionTest, pipeDisabled) {
ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' |> add ''b''"), Error);
- ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' <| add ''b''"), Error);
+ ASSERT_THROW(eval("let add = l: r: l + r; in add ''a'' <| ''b''"), Error);
}
TEST_F(TrivialExpressionTest, pipeRight) {
- ExperimentalFeatureSettings mockXpSettings;
- mockXpSettings.set("experimental-features", "pipe-operator");
+ FeatureSettings mockFeatureSettings;
+ mockFeatureSettings.set("experimental-features", "pipe-operator");
- auto v = eval("let add = l: r: l + r; in ''a'' |> add ''b''", true, mockXpSettings);
+ auto v = eval("let add = l: r: l + r; in ''a'' |> add ''b''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("ba"));
- v = eval("let add = l: r: l + r; in ''a'' |> add ''b'' |> add ''c''", true, mockXpSettings);
+ v = eval("let add = l: r: l + r; in ''a'' |> add ''b'' |> add ''c''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("cba"));
}
TEST_F(TrivialExpressionTest, pipeLeft) {
- ExperimentalFeatureSettings mockXpSettings;
- mockXpSettings.set("experimental-features", "pipe-operator");
+ FeatureSettings mockFeatureSettings;
+ mockFeatureSettings.set("experimental-features", "pipe-operator");
- auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b''", true, mockXpSettings);
+ auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("ab"));
- v = eval("let add = l: r: l + r; in add ''a'' <| add ''b'' <| ''c''", true, mockXpSettings);
+ v = eval("let add = l: r: l + r; in add ''a'' <| add ''b'' <| ''c''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("abc"));
}
TEST_F(TrivialExpressionTest, pipeMixed) {
- ExperimentalFeatureSettings mockXpSettings;
- mockXpSettings.set("experimental-features", "pipe-operator");
+ FeatureSettings mockFeatureSettings;
+ mockFeatureSettings.set("experimental-features", "pipe-operator");
- auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b'' |> add ''c''", true, mockXpSettings);
+ auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b'' |> add ''c''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("acb"));
- v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockXpSettings);
+ v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings);
ASSERT_THAT(v, IsStringEq("ac"));
}
} /* namespace nix */