From 5ca56a4f8ad12fa0f67fa8cb468540b0034717b9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 13 Jan 2022 23:10:09 +0100 Subject: [PATCH] New option "config" for specifying additional config directories. --- apps/essimporter/main.cpp | 1 + apps/navmeshtool/main.cpp | 1 + apps/opencs/editor.cpp | 1 + apps/openmw/main.cpp | 3 +- apps/openmw_test_suite/mwworld/test_store.cpp | 1 + components/files/configurationmanager.cpp | 127 +++++++++++++----- components/files/configurationmanager.hpp | 18 ++- files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 9 files changed, 112 insertions(+), 42 deletions(-) diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index e81368c607..bc5d34bc03 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -25,6 +25,7 @@ int main(int argc, char** argv) ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") ; p_desc.add("mwsave", 1).add("output", 1); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 8e46170e63..ce3cb10b20 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -84,6 +84,7 @@ namespace NavMeshTool ("process-interior-cells", bpo::value()->implicit_value(true) ->default_value(false), "build navmesh for interior cells") ; + Files::ConfigurationManager::addCommonOptions(result); return result; } diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 7d5230f894..3db201874a 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -102,6 +102,7 @@ std::pair > CS::Editor::readConfi ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") ("script-blacklist-use", boost::program_options::value()->implicit_value(true) ->default_value(true), "enable script blacklisting"); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 6b92fa78f5..456fe23b60 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -40,6 +40,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat typedef std::vector StringsVector; bpo::options_description desc = OpenMW::makeOptionsDescription(); + Files::ConfigurationManager::addCommonOptions(desc); bpo::variables_map variables; @@ -61,9 +62,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - Files::mergeComposingVariables(variables, composingVariables, desc); setupLogging(cfgMgr.getLogPath().string(), "OpenMW"); diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index 10003cfdfd..bf1a40f7d9 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -58,6 +58,7 @@ struct ContentFileTest : public ::testing::Test ("content", boost::program_options::value>()->default_value(std::vector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), "")); + Files::ConfigurationManager::addCommonOptions(desc); boost::program_options::notify(variables); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index c2cd44960f..739c7f7b43 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -20,6 +20,7 @@ static const char* const applicationName = "openmw"; #endif const char* const localToken = "?local?"; +const char* const userConfigToken = "?userconfig?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; @@ -50,6 +51,7 @@ ConfigurationManager::~ConfigurationManager() void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); + mTokensMapping.insert(std::make_pair(userConfigToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } @@ -57,31 +59,92 @@ void ConfigurationManager::setupTokensMapping() void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet) { + using ParsedConfigFile = bpo::basic_parsed_options; bool silent = mSilent; mSilent = quiet; - - // User config has the highest priority. - auto composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getUserConfigPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - // read either local or global config depending on type of installation - composingVariables = separateComposingVariables(variables, description); - bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); - mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); - if (!loaded) + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); + if (config) + mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); + else + { + mActiveConfigPaths.push_back(mFixedPath.getGlobalConfigPath()); + config = loadConfig(mFixedPath.getGlobalConfigPath(), description); + } + if (!config) { - composingVariables = separateComposingVariables(variables, description); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + if (!quiet) + Log(Debug::Error) << "Neither local config nor global config are available."; + mSilent = silent; + return; + } + + std::stack extraConfigDirs; + addExtraConfigDirs(extraConfigDirs, variables); + addExtraConfigDirs(extraConfigDirs, *config); + + std::vector parsedOptions{*std::move(config)}; + std::set alreadyParsedPaths; // needed to prevent infinite loop in case of a circular link + alreadyParsedPaths.insert(boost::filesystem::path(mActiveConfigPaths.front())); + + while (!extraConfigDirs.empty()) + { + boost::filesystem::path path = extraConfigDirs.top(); + extraConfigDirs.pop(); + if (alreadyParsedPaths.count(path) > 0) + { + if (!quiet) + Log(Debug::Warning) << "Repeated config dir: " << path; + continue; + } + alreadyParsedPaths.insert(path); + mActiveConfigPaths.push_back(path); + config = loadConfig(path, description); + if (!config) + continue; + addExtraConfigDirs(extraConfigDirs, *config); + parsedOptions.push_back(*std::move(config)); + } + + for (auto it = parsedOptions.rbegin(); it != parsedOptions.rend(); ++it) + { + auto composingVariables = separateComposingVariables(variables, description); + boost::program_options::store(std::move(*it), variables); mergeComposingVariables(variables, composingVariables, description); - boost::program_options::notify(variables); } + mLogPath = mActiveConfigPaths.back(); mSilent = silent; } +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const bpo::basic_parsed_options& options) const +{ + boost::program_options::variables_map variables; + boost::program_options::store(options, variables); + boost::program_options::notify(variables); + addExtraConfigDirs(dirs, variables); +} + +void ConfigurationManager::addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const +{ + auto configIt = variables.find("config"); + if (configIt == variables.end()) + return; + Files::PathContainer newDirs = asPathContainer(configIt->second.as()); + processPaths(newDirs); + for (auto it = newDirs.rbegin(); it != newDirs.rend(); ++it) + dirs.push(*it); +} + +void ConfigurationManager::addCommonOptions(boost::program_options::options_description& description) +{ + description.add_options() + ("config", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "config") + ->multitoken()->composing(), "additional config directories"); +} + boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) { @@ -107,7 +170,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost if (description.find_nothrow("replace", false)) { auto replace = second["replace"]; - if (!replace.defaulted() && !replace.empty()) + if (!replace.empty()) { std::vector replaceVector = replace.as>(); replacedVariables.insert(replaceVector.begin(), replaceVector.end()); @@ -132,7 +195,7 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost continue; } - if (second[name].defaulted() || second[name].empty()) + if (second[name].empty()) continue; boost::any& firstValue = firstPosition->second.value(); @@ -142,7 +205,6 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost { auto& firstPathContainer = boost::any_cast(firstValue); const auto& secondPathContainer = boost::any_cast(secondValue); - firstPathContainer.insert(firstPathContainer.end(), secondPathContainer.begin(), secondPathContainer.end()); } else if (firstValue.type() == typeid(std::vector)) @@ -165,10 +227,10 @@ void mergeComposingVariables(boost::program_options::variables_map& first, boost Log(Debug::Error) << "Unexpected composing variable type. Curse boost and their blasted arcane templates."; } } - + boost::program_options::notify(first); } -void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) +void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) const { std::string path; for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) @@ -181,7 +243,7 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::string::size_type pos = path.find('?', 1); if (pos != std::string::npos && pos != 0) { - TokensMappingContainer::iterator tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); + auto tokenIt = mTokensMapping.find(path.substr(0, pos + 1)); if (tokenIt != mTokensMapping.end()) { boost::filesystem::path tempPath(((mFixedPath).*(tokenIt->second))()); @@ -224,8 +286,8 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre std::bind(&boost::filesystem::path::empty, std::placeholders::_1)), dataDirs.end()); } -bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, +std::optional> ConfigurationManager::loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description) { boost::filesystem::path cfgFile(path); @@ -238,20 +300,11 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) - { - parseConfig(configFileStream, variables, description); - - return true; - } - else - { - if (!mSilent) - Log(Debug::Error) << "Loading failed."; - - return false; - } + return Files::parse_config_file(configFileStream, description, true); + else if (!mSilent) + Log(Debug::Error) << "Loading failed."; } - return false; + return std::nullopt; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const @@ -344,4 +397,4 @@ PathContainer asPathContainer(const MaybeQuotedPathContainer& MaybeQuotedPathCon return PathContainer(MaybeQuotedPathContainer.begin(), MaybeQuotedPathContainer.end()); } -} /* namespace Cfg */ +} /* namespace Files */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 4b641c12fd..07a9300305 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include +#include +#include #include @@ -25,7 +27,7 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); - void processPaths(Files::PathContainer& dataDirs, bool create = false); + void processPaths(Files::PathContainer& dataDirs, bool create = false) const; ///< \param create Try creating the directory, if it does not exist. /**< Fixed paths */ @@ -37,24 +39,34 @@ struct ConfigurationManager const boost::filesystem::path& getUserDataPath() const; const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; + const std::vector& getActiveConfigPaths() const { return mActiveConfigPaths; } const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; const boost::filesystem::path& getScreenshotPath() const; + static void addCommonOptions(boost::program_options::options_description& description); + private: typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; typedef std::map TokensMappingContainer; - bool loadConfig(const boost::filesystem::path& path, - boost::program_options::variables_map& variables, + std::optional> loadConfig( + const boost::filesystem::path& path, boost::program_options::options_description& description); + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::variables_map& variables) const; + void addExtraConfigDirs(std::stack& dirs, + const boost::program_options::basic_parsed_options& options) const; + void setupTokensMapping(); + std::vector mActiveConfigPaths; + FixedPathType mFixedPath; boost::filesystem::path mLogPath; diff --git a/files/openmw.cfg b/files/openmw.cfg index f524911489..7bd70c3e5e 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum script-blacklist=MockChangeScript diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index bed9b9b10a..79ab9e6156 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,6 +4,7 @@ content=builtin.omwscripts data-local="?userdata?data" +config="?userconfig?" resources=./resources script-blacklist=Museum script-blacklist=MockChangeScript