#include "config.hh" #include "apply-config-options.hh" #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" #include "config-impl.hh" #include namespace nix { Config::Config(StringMap initials) : AbstractConfig(std::move(initials)) { } bool Config::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options) { bool append = false; auto i = _settings.find(name); if (i == _settings.end()) { if (name.starts_with("extra-")) { i = _settings.find(std::string(name, 6)); if (i == _settings.end() || !i->second.setting->isAppendable()) return false; append = true; } else return false; } i->second.setting->set(value, append, options); i->second.setting->overridden = true; return true; } void Config::addSetting(AbstractSetting * setting) { _settings.emplace(setting->name, Config::SettingData{false, setting}); for (const auto & alias : setting->aliases) _settings.emplace(alias, Config::SettingData{true, setting}); bool set = false; if (auto i = unknownSettings.find(setting->name); i != unknownSettings.end()) { setting->set(std::move(i->second)); setting->overridden = true; unknownSettings.erase(i); set = true; } for (auto & alias : setting->aliases) { if (auto i = unknownSettings.find(alias); i != unknownSettings.end()) { if (set) warn("setting '%s' is set, but it's an alias of '%s' which is also set", alias, setting->name); else { setting->set(std::move(i->second)); setting->overridden = true; unknownSettings.erase(i); set = true; } } } } AbstractConfig::AbstractConfig(StringMap initials) : unknownSettings(std::move(initials)) { } void AbstractConfig::warnUnknownSettings() { for (const auto & s : unknownSettings) warn("unknown setting '%s'", s.first); } void AbstractConfig::reapplyUnknownSettings() { auto unknownSettings2 = std::move(unknownSettings); unknownSettings = {}; for (auto & s : unknownSettings2) set(s.first, s.second); } void Config::getSettings(std::map & res, bool overriddenOnly) { for (const auto & opt : _settings) if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden)) res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description}); } static void applyConfigInner(const std::string & contents, const ApplyConfigOptions & options, std::vector> & parsedContents) { unsigned int pos = 0; while (pos < contents.size()) { std::string line; while (pos < contents.size() && contents[pos] != '\n') line += contents[pos++]; pos++; if (auto hash = line.find('#'); hash != line.npos) line = std::string(line, 0, hash); auto tokens = tokenizeString>(line); if (tokens.empty()) continue; if (tokens.size() < 2) throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); auto include = false; auto ignoreMissing = false; if (tokens[0] == "include") include = true; else if (tokens[0] == "!include") { include = true; ignoreMissing = true; } if (include) { if (tokens.size() != 2) { throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); } if (!options.path) { throw UsageError("can only include configuration '%1%' from files", tokens[1]); } auto pathToInclude = absPath(tildePath(tokens[1], options.home), dirOf(*options.path)); if (pathExists(pathToInclude)) { auto includeOptions = ApplyConfigOptions { .path = pathToInclude, .home = options.home, }; try { std::string includedContents = readFile(pathToInclude); applyConfigInner(includedContents, includeOptions, parsedContents); } catch (SysError &) { // TODO: Do we actually want to ignore this? Or is it better to fail? } } else if (!ignoreMissing) { throw Error("file '%1%' included from '%2%' not found", pathToInclude, *options.path); } continue; } if (tokens[1] != "=") throw UsageError("illegal configuration line '%1%' in '%2%'", line, options.relativeDisplay()); std::string name = std::move(tokens[0]); auto i = tokens.begin(); advance(i, 2); parsedContents.push_back({ std::move(name), concatStringsSep(" ", Strings(i, tokens.end())), }); }; } void AbstractConfig::applyConfig(const std::string & contents, const ApplyConfigOptions & options) { std::vector> parsedContents; applyConfigInner(contents, options, parsedContents); // First apply experimental-feature related settings for (const auto & [name, value] : parsedContents) if (name == "experimental-features" || name == "extra-experimental-features") set(name, value, options); // Then apply other settings for (const auto & [name, value] : parsedContents) if (name != "experimental-features" && name != "extra-experimental-features") set(name, value, options); } void Config::resetOverridden() { for (auto & s : _settings) s.second.setting->overridden = false; } nlohmann::json Config::toJSON() { auto res = nlohmann::json::object(); for (const auto & s : _settings) if (!s.second.isAlias) res.emplace(s.first, s.second.setting->toJSON()); return res; } std::string Config::toKeyValue() { std::string res; for (const auto & s : _settings) if (s.second.isAlias) res += fmt("%s = %s\n", s.first, s.second.setting->to_string()); return res; } void Config::convertToArgs(Args & args, const std::string & category) { for (auto & s : _settings) { if (!s.second.isAlias) s.second.setting->convertToArg(args, category); } } AbstractSetting::AbstractSetting( const std::string & name, const std::string & description, const std::set & aliases, std::optional experimentalFeature) : name(name) , description(stripIndentation(description)) , aliases(aliases) , experimentalFeature(std::move(experimentalFeature)) { } AbstractSetting::~AbstractSetting() { // Check against a gcc miscompilation causing our constructor // not to run (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80431). assert(created == 123); } nlohmann::json AbstractSetting::toJSON() { return nlohmann::json(toJSONObject()); } std::map AbstractSetting::toJSONObject() const { std::map obj; obj.emplace("description", description); obj.emplace("aliases", aliases); if (experimentalFeature) obj.emplace("experimentalFeature", *experimentalFeature); else obj.emplace("experimentalFeature", nullptr); return obj; } void AbstractSetting::convertToArg(Args & args, const std::string & category) { } bool AbstractSetting::isOverridden() const { return overridden; } template<> std::string BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { return str; } template<> std::string BaseSetting::to_string() const { return value; } template<> std::optional BaseSetting>::parse(const std::string & str, const ApplyConfigOptions & options) const { if (str == "") return std::nullopt; else return { str }; } template<> std::string BaseSetting>::to_string() const { return value ? *value : ""; } template<> bool BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { if (str == "true" || str == "yes" || str == "1") return true; else if (str == "false" || str == "no" || str == "0") return false; else throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str); } template<> std::string BaseSetting::to_string() const { return value ? "true" : "false"; } template<> void BaseSetting::convertToArg(Args & args, const std::string & category) { args.addFlag({ .longName = name, .description = fmt("Enable the `%s` setting.", name), .category = category, .handler = {[this] { override(true); }}, .experimentalFeature = experimentalFeature, }); args.addFlag({ .longName = "no-" + name, .description = fmt("Disable the `%s` setting.", name), .category = category, .handler = {[this] { override(false); }}, .experimentalFeature = experimentalFeature, }); } template<> Strings BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { return tokenizeString(str); } template<> void BaseSetting::appendOrSet(Strings newValue, bool append, const ApplyConfigOptions & options) { if (!append) value.clear(); value.insert(value.end(), std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end())); } template<> std::string BaseSetting::to_string() const { return concatStringsSep(" ", value); } template<> StringSet BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { return tokenizeString(str); } template<> void BaseSetting::appendOrSet(StringSet newValue, bool append, const ApplyConfigOptions & options) { if (!append) value.clear(); value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end())); } template<> std::string BaseSetting::to_string() const { return concatStringsSep(" ", value); } template<> ExperimentalFeatures BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { ExperimentalFeatures res{}; for (auto & s : tokenizeString(str)) { if (auto thisXpFeature = parseExperimentalFeature(s); thisXpFeature) res = res | thisXpFeature.value(); else warn("unknown experimental feature '%s'", s); } return res; } template<> void BaseSetting::appendOrSet(ExperimentalFeatures newValue, bool append, const ApplyConfigOptions & options) { if (append) value = value | newValue; else value = newValue; } template<> std::string BaseSetting::to_string() const { StringSet stringifiedXpFeatures; for (size_t tag = 0; tag < sizeof(ExperimentalFeatures) * CHAR_BIT; tag++) if ((value & ExperimentalFeature(tag)) != ExperimentalFeatures{}) stringifiedXpFeatures.insert(std::string(showExperimentalFeature(ExperimentalFeature(tag)))); return concatStringsSep(" ", stringifiedXpFeatures); } template<> DeprecatedFeatures BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { DeprecatedFeatures res{}; for (auto & s : tokenizeString(str)) { if (auto thisDpFeature = parseDeprecatedFeature(s); thisDpFeature) res = res | thisDpFeature.value(); else warn("unknown deprecated feature '%s'", s); } return res; } template<> void BaseSetting::appendOrSet(DeprecatedFeatures newValue, bool append, const ApplyConfigOptions & options) { if (append) value = value | newValue; else value = newValue; } template<> std::string BaseSetting::to_string() const { StringSet stringifiedDpFeatures; for (size_t tag = 0; tag < sizeof(DeprecatedFeatures) * CHAR_BIT; tag++) if ((value & DeprecatedFeature(tag)) != DeprecatedFeatures{}) stringifiedDpFeatures.insert(std::string(showDeprecatedFeature(DeprecatedFeature(tag)))); return concatStringsSep(" ", stringifiedDpFeatures); } template<> StringMap BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { StringMap res; for (const auto & s : tokenizeString(str)) { if (auto eq = s.find_first_of('='); s.npos != eq) res.emplace(std::string(s, 0, eq), std::string(s, eq + 1)); // else ignored } return res; } template<> void BaseSetting::appendOrSet(StringMap newValue, bool append, const ApplyConfigOptions & options) { if (!append) value.clear(); value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end())); } template<> std::string BaseSetting::to_string() const { return std::transform_reduce(value.cbegin(), value.cend(), std::string{}, [](const auto & l, const auto &r) { return l + " " + r; }, [](const auto & kvpair){ return kvpair.first + "=" + kvpair.second; }); } template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; template class BaseSetting; static Path parsePath(const AbstractSetting & s, const std::string & str, const ApplyConfigOptions & options) { if (str == "") { throw UsageError("setting '%s' is a path and paths cannot be empty", s.name); } else { auto tildeResolvedPath = tildePath(str, options.home); if (options.path) { return absPath(tildeResolvedPath, dirOf(*options.path)); } else { return canonPath(tildeResolvedPath); } } } template<> Path PathsSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { return parsePath(*this, str, options); } template<> std::optional PathsSetting>::parse(const std::string & str, const ApplyConfigOptions & options) const { if (str == "") return std::nullopt; else return parsePath(*this, str, options); } template<> Paths PathsSetting::parse(const std::string & str, const ApplyConfigOptions & options) const { auto strings = tokenizeString(str); Paths parsed; for (auto str : strings) { parsed.push_back(parsePath(*this, str, options)); } return parsed; } template class PathsSetting; template class PathsSetting>; template class PathsSetting; bool GlobalConfig::set(const std::string & name, const std::string & value, const ApplyConfigOptions & options) { for (auto & config : *configRegistrations) if (config->set(name, value, options)) return true; unknownSettings.emplace(name, value); return false; } void GlobalConfig::getSettings(std::map & res, bool overriddenOnly) { for (auto & config : *configRegistrations) config->getSettings(res, overriddenOnly); } void GlobalConfig::resetOverridden() { for (auto & config : *configRegistrations) config->resetOverridden(); } nlohmann::json GlobalConfig::toJSON() { auto res = nlohmann::json::object(); for (const auto & config : *configRegistrations) res.update(config->toJSON()); return res; } std::string GlobalConfig::toKeyValue() { std::string res; std::map settings; globalConfig.getSettings(settings); for (const auto & s : settings) res += fmt("%s = %s\n", s.first, s.second.value); return res; } void GlobalConfig::convertToArgs(Args & args, const std::string & category) { for (auto & config : *configRegistrations) config->convertToArgs(args, category); } GlobalConfig globalConfig; GlobalConfig::ConfigRegistrations * GlobalConfig::configRegistrations; GlobalConfig::Register::Register(Config * config) { if (!configRegistrations) configRegistrations = new ConfigRegistrations; configRegistrations->emplace_back(config); } FeatureSettings experimentalFeatureSettings; FeatureSettings& featureSettings = experimentalFeatureSettings; static GlobalConfig::Register rSettings(&experimentalFeatureSettings); bool FeatureSettings::isEnabled(const ExperimentalFeature & feature) const { auto & f = experimentalFeatures.get(); return (f & feature) != ExperimentalFeatures{}; } void FeatureSettings::require(const ExperimentalFeature & feature) const { if (!isEnabled(feature)) throw MissingExperimentalFeature(feature); } bool FeatureSettings::isEnabled(const std::optional & feature) const { return !feature || isEnabled(*feature); } void FeatureSettings::require(const std::optional & feature) const { if (feature) require(*feature); } bool FeatureSettings::isEnabled(const DeprecatedFeature & feature) const { auto & f = deprecatedFeatures.get(); return (f & feature) != DeprecatedFeatures{}; } void FeatureSettings::require(const DeprecatedFeature & feature) const { if (!isEnabled(feature)) throw MissingDeprecatedFeature(feature); } bool FeatureSettings::isEnabled(const std::optional & feature) const { return !feature || isEnabled(*feature); } void FeatureSettings::require(const std::optional & feature) const { if (feature) require(*feature); } }