From 328e8eef78044215cd1efe3f6be533716b0b1493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= Date: Mon, 11 Dec 2023 19:01:00 +0100 Subject: [PATCH] Change default location for configuration file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit – under Linux and macOS, use the “dynare” subdirectory of the configuration directory specified by the XDG specification – under Windows, use the “dynare” subdirectory of the Application Data folder The old location is kept for backward compatibility, with a warning. --- src/Configuration.cc | 129 ++++++++++++++++++++++++++++--------------- src/Configuration.hh | 9 ++- src/DynareMain.cc | 2 +- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/src/Configuration.cc b/src/Configuration.cc index 94aa5049..1703d115 100644 --- a/src/Configuration.cc +++ b/src/Configuration.cc @@ -21,7 +21,12 @@ #include #include +#ifdef _WIN32 +# include +#endif + #include "Configuration.hh" +#include "DataTree.hh" // For strsplit() #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" @@ -109,64 +114,59 @@ Configuration::Configuration(bool parallel_arg, bool parallel_test_arg, } void -Configuration::getConfigFileInfo(const filesystem::path& conffile_option) +Configuration::getConfigFileInfo(const filesystem::path& conffile_option, + WarningConsolidation& warnings) { using namespace boost; - ifstream configFile; - if (conffile_option.empty()) + filesystem::path config_file {conffile_option}; + + if (config_file.empty()) { - filesystem::path defaultConfigFile; - // Test OS and try to open default file -#if defined(_WIN32) || defined(__CYGWIN32__) - if (auto appdata = getenv("APPDATA"); appdata) - defaultConfigFile = filesystem::path {appdata} / "dynare.ini"; - else - { - if (parallel || parallel_test) - cerr << "ERROR: "; - else - cerr << "WARNING: "; - cerr << "APPDATA environment variable not found." << endl; + config_file = findConfigFile("dynare.ini"); - if (parallel || parallel_test) - exit(EXIT_FAILURE); - } + if (config_file.empty()) // Try old default location (Dynare ⩽ 5) for backward compatibility + { + filesystem::path old_default_config_file; +#ifdef _WIN32 + array appdata; + if (SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_DONT_VERIFY, nullptr, + SHGFP_TYPE_CURRENT, appdata.data()) + == S_OK) + old_default_config_file = filesystem::path {appdata.data()} / "dynare.ini"; #else - if (auto home = getenv("HOME"); home) - defaultConfigFile = filesystem::path {home} / ".dynare"; - else - { - if (parallel || parallel_test) - cerr << "ERROR: "; - else - cerr << "WARNING: "; - cerr << "HOME environment variable not found." << endl; - if (parallel || parallel_test) - exit(EXIT_FAILURE); - } + if (auto home = getenv("HOME"); home) + old_default_config_file = filesystem::path {home} / ".dynare"; #endif - configFile.open(defaultConfigFile, fstream::in); - if (!configFile.is_open()) - { - if (parallel || parallel_test) + if (!old_default_config_file.empty() && exists(old_default_config_file)) { - cerr << "ERROR: Could not open the default config file (" - << defaultConfigFile.string() << ")" << endl; - exit(EXIT_FAILURE); + warnings << "WARNING: the location " << old_default_config_file.string() + << " for the configuration file is obsolete; please see the reference" + << " manual for the new location." << endl; + config_file = old_default_config_file; } - else - return; } } - else + + if (config_file.empty()) { - configFile.open(conffile_option, fstream::in); - if (!configFile.is_open()) + if (parallel || parallel_test) { - cerr << "ERROR: Couldn't open file " << conffile_option.string() << endl; + cerr << "ERROR: the parallel or parallel_test option was passed but no configuration " + << "file was found" << endl; exit(EXIT_FAILURE); } + else + return; + } + + ifstream configFile; + configFile.open(config_file, fstream::in); + + if (!configFile.is_open()) + { + cerr << "ERROR: Couldn't open configuration file " << config_file.string() << endl; + exit(EXIT_FAILURE); } string name, computerName, port, userName, password, remoteDrive, remoteDirectory, programPath, @@ -812,3 +812,46 @@ Configuration::writeEndParallel(ostream& output) const << " closeSlave(options_.parallel,options_.parallel_info.RemoteTmpFolder);" << endl << "end" << endl; } + +filesystem::path +Configuration::findConfigFile(const string& filename) +{ +#ifdef _WIN32 + array appdata; + if (SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_DONT_VERIFY, nullptr, SHGFP_TYPE_CURRENT, + appdata.data()) + == S_OK) + { + filesystem::path candidate {filesystem::path {appdata.data()} / "dynare" / filename}; + if (exists(candidate)) + return candidate; + } +#else + filesystem::path xdg_config_home; + if (auto xdg_config_home_env = getenv("XDG_CONFIG_HOME"); xdg_config_home_env) + xdg_config_home = xdg_config_home_env; + if (auto home = getenv("HOME"); xdg_config_home.empty() && home) + xdg_config_home = filesystem::path {home} / ".config"; + + if (!xdg_config_home.empty()) + { + filesystem::path candidate {xdg_config_home / "dynare" / filename}; + if (exists(candidate)) + return candidate; + } + + string xdg_config_dirs; + if (auto xdg_config_dirs_env = getenv("XDG_CONFIG_DIRS"); xdg_config_dirs_env) + xdg_config_dirs = xdg_config_dirs_env; + if (xdg_config_dirs.empty()) + xdg_config_dirs = "/etc/xdg"; + for (const auto& dir : DataTree::strsplit(xdg_config_dirs, ':')) + { + filesystem::path candidate {filesystem::path {dir} / "dynare" / filename}; + if (exists(candidate)) + return candidate; + } +#endif + + return {}; +} diff --git a/src/Configuration.hh b/src/Configuration.hh index 5b6014c2..4f4fea72 100644 --- a/src/Configuration.hh +++ b/src/Configuration.hh @@ -114,10 +114,17 @@ private: const string& programPath, const string& programConfig, const string& matlabOctavePath, bool singleCompThread, int numberOfThreadsPerJob, const string& operatingSystem); + /* Given a filename (e.g. dynare.ini), looks for it in the configuration directory: + – if under Linux or macOS, look into the “dynare” subdirectory of the XDG + configuration directories (following the default values and the precedence order specified in + the XDG specification) + – if under Windows, look into %APPDATA%\dynare\ + The returned path will be empty if the file is not found. */ + [[nodiscard]] static filesystem::path findConfigFile(const string& filename); public: //! Parse config file - void getConfigFileInfo(const filesystem::path& conffile_option); + void getConfigFileInfo(const filesystem::path& conffile_option, WarningConsolidation& warnings); //! Check Pass void checkPass(WarningConsolidation& warnings) const; //! Check Pass diff --git a/src/DynareMain.cc b/src/DynareMain.cc index 8bf68792..4e9ff55a 100644 --- a/src/DynareMain.cc +++ b/src/DynareMain.cc @@ -463,7 +463,7 @@ main(int argc, char** argv) // Process config file Configuration config {parallel, parallel_test, parallel_follower_open_mode, parallel_use_psexec, cluster_name}; - config.getConfigFileInfo(conffile); + config.getConfigFileInfo(conffile, warnings); config.checkPass(warnings); config.transformPass();