#pragma once ///@file #include #include #include #include #include "types.hh" #include "experimental-features.hh" #include "deprecated-features.hh" namespace nix { /** * The Config class provides Lix runtime configurations. * * What is a Configuration? * A collection of uniquely named Settings. * * What is a Setting? * Each property that you can set in a configuration corresponds to a * `Setting`. A setting records value and description of a property * with a default and optional aliases. * * A valid configuration consists of settings that are registered to a * `Config` object instance: * * Config config; * Setting systemSetting{&config, "x86_64-linux", "system", "the current system"}; * * The above creates a `Config` object and registers a setting called "system" * via the variable `systemSetting` with it. The setting defaults to the string * "x86_64-linux", it's description is "the current system". All of the * registered settings can then be accessed as shown below: * * std::map settings; * config.getSettings(settings); * config["system"].description == "the current system" * config["system"].value == "x86_64-linux" * * * The above retrieves all currently known settings from the `Config` object * and adds them to the `settings` map. */ class Args; class AbstractSetting; class AbstractConfig { protected: StringMap unknownSettings; AbstractConfig(StringMap initials = {}); public: /** * Sets the value referenced by `name` to `value`. Returns true if the * setting is known, false otherwise. */ virtual bool set(const std::string & name, const std::string & value) = 0; struct SettingInfo { std::string value; std::string description; }; /** * Adds the currently known settings to the given result map `res`. * - res: map to store settings in * - overriddenOnly: when set to true only overridden settings will be added to `res` */ virtual void getSettings(std::map & res, bool overriddenOnly = false) = 0; /** * Parses the configuration in `contents` and applies it * - contents: configuration contents to be parsed and applied * - path: location of the configuration file */ void applyConfig(const std::string & contents, const std::string & path = ""); /** * Resets the `overridden` flag of all Settings */ virtual void resetOverridden() = 0; /** * Outputs all settings to JSON * - out: JSONObject to write the configuration to */ virtual nlohmann::json toJSON() = 0; /** * Outputs all settings in a key-value pair format suitable to be used as * `nix.conf` */ virtual std::string toKeyValue() = 0; /** * Converts settings to `Args` to be used on the command line interface * - args: args to write to * - category: category of the settings */ virtual void convertToArgs(Args & args, const std::string & category) = 0; /** * Logs a warning for each unregistered setting */ void warnUnknownSettings(); /** * Re-applies all previously attempted changes to unknown settings */ void reapplyUnknownSettings(); }; /** * A class to simplify providing configuration settings. The typical * use is to inherit Config and add Setting members: * * class MyClass : private Config * { * Setting foo{this, 123, "foo", "the number of foos to use"}; * Setting bar{this, "blabla", "bar", "the name of the bar"}; * * MyClass() : Config(readConfigFile("/etc/my-app.conf")) * { * std::cout << foo << "\n"; // will print 123 unless overridden * } * }; */ class Config : public AbstractConfig { friend class AbstractSetting; public: struct SettingData { bool isAlias; AbstractSetting * setting; }; using Settings = std::map; private: Settings _settings; public: Config(StringMap initials = {}); bool set(const std::string & name, const std::string & value) override; void addSetting(AbstractSetting * setting); void getSettings(std::map & res, bool overriddenOnly = false) override; void resetOverridden() override; nlohmann::json toJSON() override; std::string toKeyValue() override; void convertToArgs(Args & args, const std::string & category) override; }; class AbstractSetting { friend class Config; public: struct deprecated_t { explicit deprecated_t() = default; }; const std::string name; const std::string description; const std::set aliases; int created = 123; bool overridden = false; std::optional experimentalFeature; protected: AbstractSetting( const std::string & name, const std::string & description, const std::set & aliases, std::optional experimentalFeature = std::nullopt); virtual ~AbstractSetting(); virtual void set(const std::string & value, bool append = false) = 0; /** * Whether the type is appendable; i.e. whether the `append` * parameter to `set()` is allowed to be `true`. */ virtual bool isAppendable() = 0; virtual std::string to_string() const = 0; nlohmann::json toJSON(); virtual std::map toJSONObject() const; virtual void convertToArg(Args & args, const std::string & category); bool isOverridden() const; }; /** * A setting of type T. */ template class BaseSetting : public AbstractSetting { protected: T value; const T defaultValue; const bool documentDefault; const bool deprecated; /** * Parse the string into a `T`. * * Used by `set()`. */ virtual T parse(const std::string & str) const; /** * Append or overwrite `value` with `newValue`. * * Some types to do not support appending in which case `append` * should never be passed. The default handles this case. * * @param append Whether to append or overwrite. */ virtual void appendOrSet(T newValue, bool append); public: BaseSetting(const T & def, const bool documentDefault, const std::string & name, const std::string & description, const std::set & aliases = {}, std::optional experimentalFeature = std::nullopt, bool deprecated = false) : AbstractSetting(name, description, aliases, experimentalFeature) , value(def) , defaultValue(def) , documentDefault(documentDefault) , deprecated(deprecated) { } operator const T &() const { return value; } operator T &() { return value; } const T & get() const { return value; } template bool operator ==(const U & v2) const { return value == v2; } template bool operator !=(const U & v2) const { return value != v2; } template void operator =(const U & v) { assign(v); } virtual void assign(const T & v) { value = v; } template void setDefault(const U & v) { if (!overridden) value = v; } /** * Require any experimental feature the setting depends on * * Uses `parse()` to get the value from `str`, and `appendOrSet()` * to set it. */ void set(const std::string & str, bool append = false) override final; /** * C++ trick; This is template-specialized to compile-time indicate whether * the type is appendable. */ struct trait; /** * Always defined based on the C++ magic * with `trait` above. */ bool isAppendable() override final; virtual void override(const T & v) { overridden = true; value = v; } std::string to_string() const override; void convertToArg(Args & args, const std::string & category) override; std::map toJSONObject() const override; }; template std::ostream & operator <<(std::ostream & str, const BaseSetting & opt) { return str << static_cast(opt); } template bool operator ==(const T & v1, const BaseSetting & v2) { return v1 == static_cast(v2); } template class Setting : public BaseSetting { public: Setting(Config * options, const T & def, const std::string & name, const std::string & description, const std::set & aliases = {}, const bool documentDefault = true, std::optional experimentalFeature = std::nullopt, bool deprecated = false) : BaseSetting(def, documentDefault, name, description, aliases, std::move(experimentalFeature), deprecated) { options->addSetting(this); } Setting(AbstractSetting::deprecated_t, Config * options, const T & def, const std::string & name, const std::string & description, const std::set & aliases = {}, const bool documentDefault = true, std::optional experimentalFeature = std::nullopt) : Setting(options, def, name, description, aliases, documentDefault, std::move(experimentalFeature), true) { } void operator =(const T & v) { this->assign(v); } }; /** * A special setting for Paths. * These are automatically canonicalised (e.g. "/foo//bar/" becomes "/foo/bar"). * The empty string is not permitted when a path is required. */ template class PathsSetting : public BaseSetting { public: PathsSetting(Config * options, const T & def, const std::string & name, const std::string & description, const std::set & aliases = {}, const bool documentDefault = true, std::optional experimentalFeature = std::nullopt) : BaseSetting(def, documentDefault, name, description, aliases, std::move(experimentalFeature)) { options->addSetting(this); } T parse(const std::string & str) const override; void operator =(const T & v) { this->assign(v); } }; struct GlobalConfig : public AbstractConfig { typedef std::vector ConfigRegistrations; static ConfigRegistrations * configRegistrations; bool set(const std::string & name, const std::string & value) override; void getSettings(std::map & res, bool overriddenOnly = false) override; void resetOverridden() override; nlohmann::json toJSON() override; std::string toKeyValue() override; void convertToArgs(Args & args, const std::string & category) override; struct Register { Register(Config * config); }; }; extern GlobalConfig globalConfig; struct FeatureSettings : Config { Setting experimentalFeatures{ this, {}, "experimental-features", R"( Experimental features that are enabled. Example: ``` experimental-features = nix-command flakes ``` The following experimental features are available: {{#include @generated@/command-ref/experimental-features-shortlist.md}} Experimental features are [further documented in the manual](@docroot@/contributing/experimental-features.md). )"}; /** * 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 &) 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 &) const; Setting deprecatedFeatures{ this, {}, "deprecated-features", R"( Deprecated features that are allowed. Example: ``` deprecated-features = url-literals ``` 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 &) 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 &) const; }; // FIXME: don't use a global variable. extern FeatureSettings& featureSettings; // Aliases to `featureSettings` for not having to change the name in the code everywhere using ExperimentalFeatureSettings = FeatureSettings; extern FeatureSettings experimentalFeatureSettings; }