Use xdg schemas for config saving/reading (minified) (#2714)

Co-authored-by: Danny Farrell <dan@cyrusbio.com>
This commit is contained in:
Daniel Farrell 2023-07-31 15:08:02 -07:00 committed by GitHub
parent 2b0c975429
commit 00fb86c726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 14 deletions

View File

@ -54,6 +54,9 @@ namespace mamba
std::map<std::string, std::string> copy();
std::string platform();
fs::u8path home_directory();
fs::u8path user_config_dir();
fs::u8path user_data_dir();
fs::u8path user_cache_dir();
fs::u8path expand_user(const fs::u8path& path);
fs::u8path shrink_user(const fs::u8path& path);

View File

@ -5,4 +5,11 @@ namespace mamba
{
void remove_menu_from_json(const fs::u8path& json_file, TransactionContext* context);
void create_menu_from_json(const fs::u8path& json_file, TransactionContext* context);
#ifdef _WIN32
namespace win
{
fs::u8path get_folder(const std::string& id);
}
#endif
}

View File

@ -1751,6 +1751,16 @@ namespace mamba
return res;
}
// Precedence is initially set least to most, and then at the end the list is reversed.
// Configuration::set_rc_values iterates over all config options, and then over all config
// file source. Essentially first come first serve.
// just FYI re "../conda": env::user_config_dir's default value is $XDG_CONFIG_HOME/mamba
// But we wanted to also allow $XDG_CONFIG_HOME/conda and '..' seems like the best way to
// make it conda/mamba compatible. Otherwise I would have to set user_config_dir to either
// be just $XDG_CONFIG_HOME and always supply mamba after calling it, or I would have to
// give env::user_config_dir a mamba argument, all so I can supply conda in a few default
// cases. It seems like ../conda is an easier solution
//
std::vector<fs::u8path> Configuration::compute_default_rc_sources(const RCConfigLevel& level)
{
auto& ctx = Context::instance();
@ -1776,11 +1786,30 @@ namespace mamba
ctx.prefix_params.root_prefix / "condarc.d",
ctx.prefix_params.root_prefix / ".mambarc" };
std::vector<fs::u8path> home = { env::home_directory() / ".conda/.condarc",
env::home_directory() / ".conda/condarc",
env::home_directory() / ".conda/condarc.d",
env::home_directory() / ".condarc",
env::home_directory() / ".mambarc" };
std::vector<fs::u8path> conda_user = {
env::user_config_dir() / "../conda/.condarc",
env::user_config_dir() / "../conda/condarc",
env::user_config_dir() / "../conda/condarc.d",
env::home_directory() / ".conda/.condarc",
env::home_directory() / ".conda/condarc",
env::home_directory() / ".conda/condarc.d",
env::home_directory() / ".condarc",
};
if (env::get("CONDARC"))
{
conda_user.push_back(fs::u8path(env::get("CONDARC").value()));
}
std::vector<fs::u8path> mamba_user = {
env::user_config_dir() / ".mambarc", env::user_config_dir() / "mambarc",
env::user_config_dir() / "mambarc.d", env::home_directory() / ".mamba/.mambarc",
env::home_directory() / ".mamba/mambarc", env::home_directory() / ".mamba/mambarc.d",
env::home_directory() / ".mambarc",
};
if (env::get("MAMBARC"))
{
mamba_user.push_back(fs::u8path(env::get("MAMBARC").value()));
}
std::vector<fs::u8path> prefix = { ctx.prefix_params.target_prefix / ".condarc",
ctx.prefix_params.target_prefix / "condarc",
@ -1799,7 +1828,8 @@ namespace mamba
}
if (level >= RCConfigLevel::kHomeDir)
{
sources.insert(sources.end(), home.begin(), home.end());
sources.insert(sources.end(), conda_user.begin(), conda_user.end());
sources.insert(sources.end(), mamba_user.begin(), mamba_user.end());
}
if ((level >= RCConfigLevel::kTargetPrefix) && !ctx.prefix_params.target_prefix.empty())
{

View File

@ -5,11 +5,13 @@
// The full license is in the file LICENSE, distributed with this software.
#include "mamba/core/environment.hpp"
#include "mamba/core/util.hpp"
#include "mamba/core/util_string.hpp"
#ifdef _WIN32
#include <mutex>
#include "mamba/core/menuinst.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/util_os.hpp"
#endif
@ -267,7 +269,49 @@ namespace mamba
throw std::runtime_error("HOME not set.");
}
#endif
return maybe_home;
return fs::u8path(maybe_home);
}
fs::u8path user_config_dir()
{
std::string maybe_user_config_dir = env::get("XDG_CONFIG_HOME").value_or("");
if (maybe_user_config_dir.empty())
{
#ifdef _WIN32
maybe_user_config_dir = ::mamba::win::get_folder("roamingappdata");
#else
maybe_user_config_dir = home_directory() / ".config";
#endif
}
return fs::u8path(maybe_user_config_dir) / "mamba";
}
fs::u8path user_data_dir()
{
std::string maybe_user_data_dir = env::get("XDG_DATA_HOME").value_or("");
if (maybe_user_data_dir.empty())
{
#ifdef _WIN32
maybe_user_data_dir = ::mamba::win::get_folder("roamingappdata");
#else
maybe_user_data_dir = home_directory() / ".local" / "share";
#endif
}
return fs::u8path(maybe_user_data_dir) / "mamba";
}
fs::u8path user_cache_dir()
{
std::string maybe_user_cache_dir = env::get("XDG_CACHE_HOME").value_or("");
if (maybe_user_cache_dir.empty())
{
#ifdef _WIN32
maybe_user_cache_dir = ::mamba::win::get_folder("localappdata");
#else
maybe_user_cache_dir = home_directory() / ".cache";
#endif
}
return fs::u8path(maybe_user_cache_dir) / "mamba";
}
fs::u8path expand_user(const fs::u8path& path)

View File

@ -168,9 +168,9 @@ namespace mamba
}
const std::map<std::string, KNOWNFOLDERID> knownfolders = {
{ "programs", FOLDERID_Programs },
{ "profile", FOLDERID_Profile },
{ "documents", FOLDERID_Documents },
{ "programs", FOLDERID_Programs }, { "profile", FOLDERID_Profile },
{ "documents", FOLDERID_Documents }, { "roamingappdata", FOLDERID_RoamingAppData },
{ "programdata", FOLDERID_ProgramData }, { "localappdata", FOLDERID_LocalAppData },
};
fs::u8path get_folder(const std::string& id)

View File

@ -114,7 +114,7 @@ namespace mamba
const fs::u8path& proc_dir()
{
static const auto path = env::home_directory() / ".mamba" / "proc";
static auto path = env::user_cache_dir() / "proc";
return path;
}

View File

@ -68,6 +68,10 @@ def tmp_home(
for env in used_homes:
os.environ[env] = str(new_home)
if platform.system() == "Windows":
os.environ["APPDATA"] = str(new_home / "AppData" / "Roaming")
os.environ["LOCALAPPDATA"] = str(new_home / "AppData" / "Local")
yield new_home
# Pytest would clean it automatically but this can be large (0.5 Gb for repodata)
@ -83,7 +87,7 @@ def tmp_home(
def tmp_clean_env(tmp_environ: None) -> None:
"""Remove all Conda/Mamba activation artifacts from environment."""
for k, v in os.environ.items():
if k.startswith(("CONDA", "_CONDA", "MAMBA", "_MAMBA")):
if k.startswith(("CONDA", "_CONDA", "MAMBA", "_MAMBA", "XDG_")):
del os.environ[k]
def keep_in_path(
@ -179,3 +183,48 @@ def tmp_xtensor_env(tmp_prefix: pathlib.Path) -> Generator[pathlib.Path, None, N
"""An activated environment with Xtensor installed."""
helpers.install("-c", "conda-forge", "--json", "xtensor", no_dry_run=True)
yield tmp_prefix
@pytest.fixture
def user_config_dir(tmp_home: pathlib.Path) -> Generator[pathlib.Path, None, None]:
"""Location of config files that are generated from mamba"""
maybe_xdg_config = os.getenv("XDG_CONFIG_DIR", "")
if maybe_xdg_config:
yield pathlib.Path(maybe_xdg_config)
system = platform.system()
if system == "Linux" or system == "Darwin":
yield tmp_home / ".config/mamba"
elif system == "Windows":
yield pathlib.Path(os.environ["APPDATA"]) / "mamba"
else:
raise RuntimeError(f"Unsupported system {system}")
@pytest.fixture
def user_data_dir(tmp_home: pathlib.Path) -> Generator[pathlib.Path, None, None]:
"""Location of data files that are generated from mamba"""
maybe_xdg_data = os.getenv("XDG_DATA_DIR", "")
if maybe_xdg_data:
yield pathlib.Path(maybe_xdg_data)
system = platform.system()
if system == "Linux" or system == "Darwin":
yield tmp_home / ".local/share/mamba"
elif system == "Windows":
yield pathlib.Path(os.environ["APPDATA"]) / "mamba"
else:
raise RuntimeError(f"Unsupported system {system}")
@pytest.fixture
def user_cache_dir(tmp_home: pathlib.Path) -> Generator[pathlib.Path, None, None]:
"""Location of data files that are generated from mamba"""
maybe_xdg_cache = os.getenv("XDG_CACHE_DIR", "")
if maybe_xdg_cache:
yield pathlib.Path(maybe_xdg_cache)
system = platform.system()
if system == "Linux" or system == "Darwin":
yield tmp_home / ".cache/mamba"
elif system == "Windows":
yield pathlib.Path(os.environ["LOCALAPPDATA"]) / "mamba"
else:
raise RuntimeError(f"Unsupported system {system}")

View File

@ -45,7 +45,15 @@ def rc_file_text(rc_file_args):
@pytest.fixture
def rc_file(request, rc_file_text, tmp_home, tmp_root_prefix, tmp_prefix, tmp_path):
def rc_file(
request,
rc_file_text,
tmp_home,
tmp_root_prefix,
tmp_prefix,
tmp_path,
user_config_dir,
):
"""Parametrizable fixture to create an rc file at the desired location.
The file is created in an isolated folder and set as the prefix, root prefix, or
@ -59,6 +67,11 @@ def rc_file(request, rc_file_text, tmp_home, tmp_root_prefix, tmp_prefix, tmp_pa
rc_file = tmp_root_prefix / rc_filename
elif where == "prefix":
rc_file = tmp_prefix / rc_filename
elif where == "user_config_dir":
rc_file = user_config_dir / rc_filename
elif where == "env_set_xdg":
os.environ["XDG_CONFIG_HOME"] = str(tmp_home / "custom_xdg_config_dir")
rc_file = tmp_home / "custom_xdg_config_dir" / "mamba" / rc_filename
elif where == "absolute":
rc_file = Path(rc_filename)
else:
@ -114,7 +127,6 @@ class TestConfigSources:
res = config("sources", quiet_flag)
assert res.startswith("Configuration files (by precedence order):")
# TODO: test OS specific sources
# TODO: test system located sources?
@pytest.mark.parametrize(
"rc_file",
@ -127,6 +139,8 @@ class TestConfigSources:
# "/var/lib/conda/condarc",
# "/var/lib/conda/condarc.d/",
# "/var/lib/conda/.mambarc",
("user_config_dir", "mambarc"),
("env_set_xdg", "mambarc"),
("home", ".conda/.condarc"),
("home", ".conda/condarc"),
("home", ".conda/condarc.d"),