mirror of https://github.com/mamba-org/mamba.git
1520 lines
58 KiB
C++
1520 lines
58 KiB
C++
// Copyright (c) 2019, QuantStack and Mamba Contributors
|
|
//
|
|
// Distributed under the terms of the BSD 3-Clause License.
|
|
//
|
|
// The full license is in the file LICENSE, distributed with this software.
|
|
|
|
#include "mamba/api/configuration.hpp"
|
|
#include "mamba/api/info.hpp"
|
|
#include "mamba/api/install.hpp"
|
|
|
|
#include "mamba/core/environment.hpp"
|
|
#include "mamba/core/fetch.hpp"
|
|
#include "mamba/core/fsutil.hpp"
|
|
#include "mamba/core/output.hpp"
|
|
|
|
#include <reproc++/run.hpp>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
|
|
#include "termcolor/termcolor.hpp"
|
|
|
|
|
|
namespace mamba
|
|
{
|
|
namespace detail
|
|
{
|
|
void ssl_verify_hook(std::string& value)
|
|
{
|
|
bool& offline = Configuration::instance().at("offline").value<bool>();
|
|
if (offline)
|
|
{
|
|
LOG_DEBUG << "SSL verification disabled by offline mode";
|
|
value = "<false>";
|
|
return;
|
|
}
|
|
if ((value == "false") || (value == "0") || (value == "<false>"))
|
|
{
|
|
value = "<false>";
|
|
return;
|
|
}
|
|
|
|
auto& cacert
|
|
= Configuration::instance().at("cacert_path").template get_wrapped<std::string>();
|
|
if (!cacert.value().empty())
|
|
{
|
|
value = cacert.value();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (value.empty() || (value == "true") || (value == "1") || (value == "<true>"))
|
|
{
|
|
value = "<system>";
|
|
}
|
|
}
|
|
};
|
|
|
|
void always_softlink_hook(bool& value)
|
|
{
|
|
auto& config = Configuration::instance();
|
|
auto& always_copy = config.at("always_copy").value<bool>();
|
|
|
|
if (value && always_copy)
|
|
{
|
|
LOG_ERROR << "'always_softlink' and 'always_copy' are mutually exclusive.";
|
|
throw std::runtime_error("Incompatible configuration. Aborting.");
|
|
}
|
|
}
|
|
|
|
void file_spec_env_name_hook(std::string& name)
|
|
{
|
|
if (name.find_first_of("/\\") != std::string::npos)
|
|
{
|
|
throw std::runtime_error(
|
|
"An unexpected file-system separator was found in environment name: '" + name
|
|
+ "'");
|
|
}
|
|
}
|
|
|
|
void env_name_hook(std::string& name)
|
|
{
|
|
file_spec_env_name_hook(name);
|
|
|
|
auto& config = Configuration::instance();
|
|
auto& root_prefix = config.at("root_prefix").value<fs::path>();
|
|
|
|
auto& env_name = config.at("env_name");
|
|
|
|
auto& spec_file_env_name = config.at("spec_file_env_name");
|
|
auto& spec_file_name = spec_file_env_name.value<std::string>();
|
|
|
|
// Allow spec file environment name to be overriden by target prefix
|
|
if (env_name.cli_configured() && config.at("target_prefix").cli_configured())
|
|
{
|
|
LOG_ERROR << "Cannot set both prefix and env name";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
|
|
// Consider file spec environment name as env_name specified at CLI level
|
|
if (!env_name.configured() && spec_file_env_name.configured())
|
|
{
|
|
name = spec_file_name;
|
|
env_name.set_cli_value<std::string>(spec_file_name);
|
|
}
|
|
|
|
if (!name.empty())
|
|
{
|
|
fs::path prefix;
|
|
if (name == "base")
|
|
{
|
|
prefix = root_prefix;
|
|
}
|
|
else
|
|
{
|
|
prefix = root_prefix / "envs" / name;
|
|
}
|
|
|
|
if (!config.at("target_prefix").cli_configured()
|
|
&& config.at("env_name").cli_configured())
|
|
config.at("target_prefix").set_cli_value<fs::path>(prefix);
|
|
|
|
if (!config.at("target_prefix").api_configured()
|
|
&& config.at("env_name").api_configured())
|
|
config.at("target_prefix").set_value(prefix);
|
|
}
|
|
}
|
|
|
|
void target_prefix_hook(fs::path& prefix)
|
|
{
|
|
auto& config = Configuration::instance();
|
|
auto& root_prefix = config.at("root_prefix").value<fs::path>();
|
|
|
|
if (!prefix.empty())
|
|
{
|
|
if (prefix.string().find_first_of("/\\") == std::string::npos)
|
|
{
|
|
std::string old_prefix = prefix.string();
|
|
prefix = root_prefix / "envs" / prefix;
|
|
LOG_WARNING << unindent((R"(
|
|
')" + old_prefix
|
|
+ R"(' does not contain any filesystem separator.
|
|
It will be handled as env name, resulting to the following
|
|
'target_prefix': ')"
|
|
+ prefix.string() + R"('
|
|
If 'target_prefix' is expressed as a relative directory to
|
|
the current working directory, use './some_prefix')")
|
|
.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool use_fallback = config.at("use_target_prefix_fallback").value<bool>();
|
|
if (use_fallback)
|
|
prefix = std::getenv("CONDA_PREFIX") ? std::getenv("CONDA_PREFIX") : "";
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
std::string sep = "\\";
|
|
#else
|
|
std::string sep = "/";
|
|
#endif
|
|
if (!prefix.empty())
|
|
prefix = rstrip(fs::weakly_canonical(env::expand_user(prefix)).string(), sep);
|
|
|
|
if ((prefix == root_prefix)
|
|
&& Configuration::instance().at("create_base").value<bool>())
|
|
{
|
|
path::touch(root_prefix / "conda-meta" / "history", true);
|
|
}
|
|
}
|
|
|
|
void root_prefix_hook(fs::path& prefix)
|
|
{
|
|
auto& env_name = Configuration::instance().at("env_name");
|
|
|
|
if (prefix.empty())
|
|
{
|
|
if (env::get("MAMBA_DEFAULT_ROOT_PREFIX").empty())
|
|
{
|
|
prefix = env::home_directory() / "micromamba";
|
|
}
|
|
else
|
|
{
|
|
prefix = env::get("MAMBA_DEFAULT_ROOT_PREFIX");
|
|
LOG_WARNING << unindent(R"(
|
|
'MAMBA_DEFAULT_ROOT_PREFIX' is meant for testing purpose.
|
|
Consider using 'MAMBA_ROOT_PREFIX' instead)");
|
|
}
|
|
|
|
if (env_name.configured())
|
|
{
|
|
LOG_WARNING << "'root_prefix' set with default value: " << prefix.string();
|
|
}
|
|
|
|
if (fs::exists(prefix) && !fs::is_empty(prefix))
|
|
{
|
|
if (!(fs::exists(prefix / "pkgs") || fs::exists(prefix / "conda-meta")
|
|
|| fs::exists(prefix / "envs")))
|
|
{
|
|
LOG_ERROR << "Could not use default 'root_prefix': " << prefix.string();
|
|
LOG_ERROR << "Directory exists, is not empty and not a conda prefix.";
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (env_name.configured())
|
|
{
|
|
LOG_INFO << unindent(R"(
|
|
You have not set the 'root_prefix' environment variable.
|
|
To permanently modify the root prefix location, either:
|
|
- set the 'MAMBA_ROOT_PREFIX' environment variable
|
|
- use the '-r,--root-prefix' CLI option
|
|
- use 'micromamba shell init ...' to initialize your shell
|
|
(then restart or source the contents of the shell init script))");
|
|
}
|
|
}
|
|
|
|
prefix = fs::weakly_canonical(env::expand_user(prefix));
|
|
}
|
|
|
|
void rc_loading_hook(const RCConfigLevel& level)
|
|
{
|
|
auto& config = Configuration::instance();
|
|
auto& rc_files = config.at("rc_files").value<std::vector<fs::path>>();
|
|
config.set_rc_values(rc_files, level);
|
|
}
|
|
|
|
void post_root_prefix_rc_loading()
|
|
{
|
|
if (!Context::instance().no_rc)
|
|
rc_loading_hook(RCConfigLevel::kHomeDir);
|
|
}
|
|
|
|
void post_target_prefix_rc_loading()
|
|
{
|
|
auto& config = Configuration::instance();
|
|
|
|
if (!Context::instance().no_rc)
|
|
{
|
|
rc_loading_hook(RCConfigLevel::kTargetPrefix);
|
|
|
|
config.at("no_env").compute(MAMBA_CONF_FORCE_COMPUTE);
|
|
config.at("always_yes").compute(MAMBA_CONF_FORCE_COMPUTE);
|
|
config.at("quiet").compute(MAMBA_CONF_FORCE_COMPUTE);
|
|
config.at("json").compute(MAMBA_CONF_FORCE_COMPUTE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
void log_level_hook(LogLevel& lvl)
|
|
{
|
|
MessageLogger::global_log_level() = lvl;
|
|
}
|
|
*/
|
|
void verbose_hook(std::uint8_t& lvl)
|
|
{
|
|
auto& ctx = Context::instance();
|
|
/*
|
|
auto& config = Configuration::instance();
|
|
auto& log_level = config.at("log_level").get_wrapped<LogLevel>();
|
|
|
|
if (config.at("verbose").configured()
|
|
&& (config.at("verbose").value<std::uint8_t>() > 0))
|
|
{
|
|
if (log_level.cli_configured() || log_level.api_configured()
|
|
|| log_level.env_var_configured())
|
|
{
|
|
LOG_ERROR << "'verbose' can't override 'log_level' (skipped)";
|
|
config.at("verbose")
|
|
.clear_values(); // Avoid the error message appears multiple times
|
|
}
|
|
else
|
|
{
|
|
ctx.set_verbosity(lvl);
|
|
// Make it appears like set with the CLI
|
|
// TODO: find a better way than passing by YAML to convert to string?
|
|
log_level.set_cli_config(YAML::Node(ctx.verbosity).as<std::string>());
|
|
}
|
|
}
|
|
*/
|
|
ctx.set_verbosity(lvl);
|
|
}
|
|
|
|
void target_prefix_checks_hook(int& options)
|
|
{
|
|
auto& ctx = Context::instance();
|
|
auto& prefix = ctx.target_prefix;
|
|
|
|
bool no_checks = options & MAMBA_NO_PREFIX_CHECK;
|
|
bool allow_missing = options & MAMBA_ALLOW_MISSING_PREFIX;
|
|
bool allow_not_env = options & MAMBA_ALLOW_NOT_ENV_PREFIX;
|
|
bool allow_existing = options & MAMBA_ALLOW_EXISTING_PREFIX;
|
|
bool expect_existing = options & MAMBA_EXPECT_EXISTING_PREFIX;
|
|
|
|
if (no_checks)
|
|
return;
|
|
|
|
if (prefix.empty())
|
|
{
|
|
if (allow_missing)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR << "No target prefix specified";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
|
|
if (fs::exists(prefix))
|
|
{
|
|
if (!allow_existing)
|
|
{
|
|
LOG_ERROR << "Not allowed pre-existing prefix: " << prefix.string();
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
|
|
if (!fs::exists(prefix / "conda-meta") && !allow_not_env)
|
|
{
|
|
LOG_ERROR << "Expected environment not found at prefix: " << prefix.string();
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
else if (expect_existing)
|
|
{
|
|
LOG_ERROR << "No prefix found at: " << prefix.string();
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
|
|
void show_banner_hook(bool& show)
|
|
{
|
|
if (show)
|
|
Console::print(banner());
|
|
}
|
|
|
|
void rc_files_hook(std::vector<fs::path>& files)
|
|
{
|
|
auto& ctx = Context::instance();
|
|
|
|
if (!files.empty())
|
|
{
|
|
if (ctx.no_rc)
|
|
{
|
|
LOG_ERROR << "Configuration files disabled by 'no_rc'";
|
|
throw std::runtime_error("Incompatible configuration. Aborting.");
|
|
}
|
|
for (auto& f : files)
|
|
{
|
|
f = env::expand_user(f);
|
|
if (!fs::exists(f))
|
|
{
|
|
LOG_ERROR << "Configuration file specified but does not exist at '"
|
|
<< f.string() << "'";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void experimental_hook(bool& value)
|
|
{
|
|
if (value)
|
|
{
|
|
Console::stream() << termcolor::yellow << "Experimental mode enabled!"
|
|
<< termcolor::reset;
|
|
LOG_WARNING << "Experimental mode enabled";
|
|
}
|
|
}
|
|
|
|
void debug_hook(bool& value)
|
|
{
|
|
if (value)
|
|
LOG_WARNING << "Debug mode enabled";
|
|
}
|
|
|
|
void print_config_only_hook(bool& value)
|
|
{
|
|
if (value)
|
|
{
|
|
if (!Configuration::instance().at("debug").value<bool>())
|
|
{
|
|
LOG_ERROR << "Debug mode required to use 'print_config_only'";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
Configuration::instance().at("quiet").set_value(true);
|
|
}
|
|
}
|
|
|
|
void print_context_only_hook(bool& value)
|
|
{
|
|
if (value)
|
|
{
|
|
if (!Configuration::instance().at("debug").value<bool>())
|
|
{
|
|
LOG_ERROR << "Debug mode required to use 'print_context_only'";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
Configuration::instance().at("quiet").set_value(true);
|
|
}
|
|
}
|
|
|
|
std::vector<fs::path> fallback_envs_dirs_hook()
|
|
{
|
|
return { Context::instance().root_prefix / "envs" };
|
|
}
|
|
|
|
void envs_dirs_hook(std::vector<fs::path>& dirs)
|
|
{
|
|
for (auto& d : dirs)
|
|
{
|
|
d = fs::weakly_canonical(env::expand_user(d)).string();
|
|
if (fs::exists(d) && !fs::is_directory(d))
|
|
{
|
|
LOG_ERROR << "Env dir specified is not a directory: " << d.string();
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<fs::path> fallback_pkgs_dirs_hook()
|
|
{
|
|
std::vector<fs::path> paths = { Context::instance().root_prefix / "pkgs",
|
|
env::home_directory() / ".mamba" / "pkgs" };
|
|
#ifdef _WIN32
|
|
if (!env::get("APPDATA").empty())
|
|
paths.push_back(fs::path(env::get("APPDATA")) / ".mamba" / "pkgs");
|
|
#endif
|
|
return paths;
|
|
}
|
|
|
|
void pkgs_dirs_hook(std::vector<fs::path>& dirs)
|
|
{
|
|
for (auto& d : dirs)
|
|
{
|
|
d = fs::weakly_canonical(env::expand_user(d)).string();
|
|
if (fs::exists(d) && !fs::is_directory(d))
|
|
{
|
|
LOG_ERROR << "Packages dir specified is not a directory: " << d.string();
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fs::path get_conda_root_prefix()
|
|
{
|
|
std::vector<std::string> args = { "conda", "config", "--show", "root_prefix", "--json" };
|
|
std::string out, err;
|
|
auto [status, ec] = reproc::run(
|
|
args, reproc::options{}, reproc::sink::string(out), reproc::sink::string(err));
|
|
|
|
if (ec)
|
|
{
|
|
LOG_ERROR << "Conda root prefix not found using 'conda config' command";
|
|
throw std::runtime_error("Aborting.");
|
|
}
|
|
else
|
|
{
|
|
auto j = nlohmann::json::parse(out);
|
|
return j.at("root_prefix").get<std::string>();
|
|
}
|
|
}
|
|
|
|
void use_conda_root_prefix(bool force)
|
|
{
|
|
if (!Configuration::instance().at("root_prefix").configured() || force)
|
|
{
|
|
env::set("MAMBA_ROOT_PREFIX", get_conda_root_prefix());
|
|
}
|
|
}
|
|
|
|
Configuration::Configuration()
|
|
{
|
|
set_configurables();
|
|
}
|
|
|
|
Configuration& Configuration::instance()
|
|
{
|
|
static Configuration config;
|
|
return config;
|
|
}
|
|
|
|
void Configuration::set_configurables()
|
|
{
|
|
auto& ctx = Context::instance();
|
|
|
|
// Basic
|
|
insert(Configurable("root_prefix", &ctx.root_prefix)
|
|
.group("Basic")
|
|
.set_env_var_names()
|
|
.needs({ "verbose", "create_base", "rc_files" })
|
|
.description("Path to the root prefix")
|
|
.set_post_merge_hook(detail::root_prefix_hook)
|
|
.set_post_context_hook(detail::post_root_prefix_rc_loading));
|
|
|
|
insert(Configurable("create_base", false)
|
|
.group("Basic")
|
|
.set_single_op_lifetime()
|
|
.description("Define if base environment will be initialized empty"));
|
|
|
|
insert(Configurable("target_prefix", &ctx.target_prefix)
|
|
.group("Basic")
|
|
.set_env_var_names()
|
|
.needs({ "root_prefix",
|
|
"envs_dirs",
|
|
"env_name",
|
|
"spec_file_env_name",
|
|
"use_target_prefix_fallback",
|
|
"verbose",
|
|
"always_yes" })
|
|
.set_single_op_lifetime()
|
|
.description("Path to the target prefix")
|
|
.set_post_merge_hook(detail::target_prefix_hook)
|
|
.set_post_context_hook(detail::post_target_prefix_rc_loading));
|
|
|
|
insert(Configurable("use_target_prefix_fallback", true)
|
|
.group("Basic")
|
|
.set_single_op_lifetime()
|
|
.description("Fallback to the current target prefix or not"));
|
|
|
|
insert(Configurable("target_prefix_checks", MAMBA_NO_PREFIX_CHECK)
|
|
.group("Basic")
|
|
.needs({ "target_prefix", "rc_files" })
|
|
.description("The type of checks performed on the target prefix")
|
|
.set_single_op_lifetime()
|
|
.set_post_merge_hook(detail::target_prefix_checks_hook));
|
|
|
|
insert(Configurable("env_name", std::string(""))
|
|
.group("Basic")
|
|
.needs({ "root_prefix", "spec_file_env_name" })
|
|
.set_single_op_lifetime()
|
|
.set_post_merge_hook(detail::env_name_hook)
|
|
.description("Name of the target prefix"));
|
|
|
|
insert(Configurable("envs_dirs", &ctx.envs_dirs)
|
|
.group("Basic")
|
|
.set_rc_configurable(RCConfigLevel::kHomeDir)
|
|
.set_env_var_names({ "CONDA_ENVS_DIRS" })
|
|
.needs({ "root_prefix" })
|
|
.set_fallback_value_hook(detail::fallback_envs_dirs_hook)
|
|
.set_post_merge_hook(detail::envs_dirs_hook)
|
|
.description("Possible locations of named environments"));
|
|
|
|
insert(Configurable("pkgs_dirs", &ctx.pkgs_dirs)
|
|
.group("Basic")
|
|
.set_rc_configurable()
|
|
.set_env_var_names({ "CONDA_PKGS_DIRS" })
|
|
.needs({ "root_prefix" })
|
|
.set_fallback_value_hook(detail::fallback_pkgs_dirs_hook)
|
|
.set_post_merge_hook(detail::pkgs_dirs_hook)
|
|
.description("Possible locations of packages caches"));
|
|
|
|
insert(Configurable("platform", &ctx.platform)
|
|
.group("Basic")
|
|
.set_rc_configurable()
|
|
.set_env_var_names({ "CONDA_SUBDIR" })
|
|
.description("The platform description")
|
|
.long_description(unindent(R"(
|
|
The plaftorm description points what channels
|
|
subdir(s) have to fetched for package solving.
|
|
This can be 'linux-64' or similar.)")));
|
|
|
|
insert(Configurable("spec_file_env_name", std::string(""))
|
|
.group("Basic")
|
|
.needs({ "file_specs", "root_prefix" })
|
|
.set_single_op_lifetime()
|
|
.set_post_merge_hook(detail::file_spec_env_name_hook)
|
|
.description("Name of the target prefix, specified in a YAML spec file"));
|
|
|
|
insert(Configurable("specs", std::vector<std::string>({}))
|
|
.group("Basic")
|
|
.needs({ "file_specs" }) // explicit file specs overwrite current specs
|
|
.set_single_op_lifetime()
|
|
.description("Packages specification"));
|
|
|
|
insert(Configurable("others_pkg_mgrs_specs", std::vector<detail::other_pkg_mgr_spec>({}))
|
|
.group("Basic")
|
|
.set_single_op_lifetime()
|
|
.description("Others package managers specifications"));
|
|
|
|
insert(Configurable("experimental", &ctx.experimental)
|
|
.group("Basic")
|
|
.description("Enable experimental features")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.long_description(unindent(R"(
|
|
Enable experimental features that may be still.
|
|
under active development and not stable yet.)"))
|
|
.set_post_merge_hook(detail::experimental_hook));
|
|
|
|
insert(Configurable("debug", &ctx.debug)
|
|
.group("Basic")
|
|
.set_env_var_names()
|
|
.description("Turn on the debug mode")
|
|
.long_description(unindent(R"(
|
|
Turn on the debug mode that allow introspection
|
|
in intermediate steps of the operation called.
|
|
Debug features may/will interrupt the operation,
|
|
if you only need further logs refer to 'verbose'.)"))
|
|
.set_post_merge_hook(detail::debug_hook));
|
|
|
|
// Channels
|
|
insert(Configurable("channels", &ctx.channels)
|
|
.group("Channels")
|
|
.set_rc_configurable()
|
|
.set_env_var_names({ "CONDA_CHANNELS" })
|
|
.description("Define the list of channels")
|
|
.needs({ "file_specs" })
|
|
.long_description(unindent(R"(
|
|
The list of channels where the packages will be searched for.
|
|
See also 'channel_priority'.)")));
|
|
|
|
insert(Configurable("channel_alias", &ctx.channel_alias)
|
|
.group("Channels")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("The prepended url location to associate with channel names"));
|
|
|
|
insert(Configurable("default_channels", &ctx.default_channels)
|
|
.group("Channels")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Default channels used")
|
|
.long_description(unindent(R"(
|
|
The list of channel names and/or urls used for the 'defaults'
|
|
multichannel.)")));
|
|
|
|
insert(Configurable("custom_channels", &ctx.custom_channels)
|
|
.group("Channels")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Custom channels")
|
|
.long_description("A dictionary with name: url to use for custom channels."));
|
|
|
|
insert(Configurable("override_channels_enabled", &ctx.override_channels_enabled)
|
|
.group("Channels")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Permit use of the --overide-channels command-line flag"));
|
|
|
|
// Network
|
|
insert(Configurable("cacert_path", std::string(""))
|
|
.group("Network")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Path (file or directory) SSL certificate(s)")
|
|
.long_description(unindent(R"(
|
|
Path (file or directory) SSL certificate(s) to use whe
|
|
'ssl_verify' in turned on but not set with path to certs.
|
|
WARNING: overrides 'ssl_verify' if provided and 'ssl_verify'
|
|
also contains a path to SSL certificates.)")));
|
|
|
|
insert(Configurable("local_repodata_ttl", &ctx.local_repodata_ttl)
|
|
.group("Network")
|
|
.set_rc_configurable()
|
|
.description("Repodata time-to-live")
|
|
.long_description(unindent(R"(
|
|
For a value of 0, always fetch remote repodata (HTTP 304
|
|
responses respected).
|
|
For a value of 1, respect the HTTP Cache-Control max-age header.
|
|
Any other positive integer values is the number of seconds to
|
|
locally cache repodata before checking the remote server for
|
|
an update.)")));
|
|
|
|
insert(Configurable("offline", &ctx.offline)
|
|
.group("Network")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Force use cached repodata"));
|
|
|
|
insert(Configurable("ssl_no_revoke", &ctx.ssl_no_revoke)
|
|
.group("Network")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("SSL certificate revocation checks")
|
|
.long_description(unindent(R"(
|
|
This option tells curl to disable certificate revocation checks.
|
|
It's only working for Windows back-end.
|
|
WARNING: this option loosens the SSL security.)")));
|
|
|
|
insert(Configurable("ssl_verify", &ctx.ssl_verify)
|
|
.group("Network")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Verify SSL certificates for HTTPS requests")
|
|
.long_description(unindent(R"(
|
|
'ssl_verify' can be either an empty string (regular SSL verification),
|
|
the string "<false>" to indicate no SSL verification, or a path to
|
|
a directory with cert files, or a cert file..)"))
|
|
.needs({ "cacert_path", "offline" })
|
|
.set_post_merge_hook(detail::ssl_verify_hook));
|
|
|
|
// Solver
|
|
insert(Configurable("channel_priority", &ctx.channel_priority)
|
|
.group("Solver")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Define the channel priority ('strict' or 'disabled')")
|
|
.long_description(unindent(R"(
|
|
Accepts values of 'strict' and 'disabled'. The default
|
|
value is 'strict'. With strict channel priority, packages in lower
|
|
priority channels are not considered if a package with the same name
|
|
appears in a higher priority channel.
|
|
With channel priority disabled, package version takes precedence, and the
|
|
configured priority of channels is used only to break ties. In
|
|
previous versions of conda, this parameter was configured as either
|
|
True or False. True is now an alias to 'flexible'.)")));
|
|
|
|
insert(Configurable("explicit_install", false)
|
|
.group("Solver")
|
|
.description("Use explicit install instead of solving environment"));
|
|
|
|
insert(Configurable("file_specs", std::vector<std::string>({}))
|
|
.group("Solver")
|
|
.set_post_merge_hook(detail::file_specs_hook)
|
|
.set_post_merge_hook(detail::file_specs_hook)
|
|
.description("File (yaml, explicit or plain)"));
|
|
|
|
insert(Configurable("no_pin", false)
|
|
.group("Solver")
|
|
.set_env_var_names()
|
|
.description("Ignore pinned packages"));
|
|
|
|
insert(Configurable("no_py_pin", false)
|
|
.group("Solver")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Do not automatically pin Python")
|
|
.long_description(unindent(R"(
|
|
Do not automatically pin Python when not present in
|
|
the packages specifications, which is the default
|
|
behavior.)")));
|
|
|
|
insert(Configurable("add_pip_as_python_dependency", &ctx.add_pip_as_python_dependency)
|
|
.group("Solver")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Add pip as a Python dependency")
|
|
.long_description("Automatically add pip as a Python dependency"));
|
|
|
|
insert(Configurable("pinned_packages", &ctx.pinned_packages)
|
|
.group("Solver")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("A list of package specs to pin for every environment resolution"));
|
|
|
|
insert(Configurable("freeze_installed", &ctx.freeze_installed)
|
|
.group("Solver")
|
|
.description("Freeze already installed dependencies"));
|
|
|
|
insert(Configurable("retry_clean_cache", false)
|
|
.group("Solver")
|
|
.set_env_var_names()
|
|
.description("If solve fails, try to fetch updated repodata"));
|
|
|
|
// Link & Install
|
|
insert(Configurable("allow_softlinks", &ctx.allow_softlinks)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Allow to use soft-links when hard-links are not possible")
|
|
.long_description(unindent(R"(
|
|
Allow to use soft-links (symlinks) when hard-links are not possible,
|
|
such as when installing on a different filesystem than the one that
|
|
the package cache is on.)")));
|
|
|
|
insert(Configurable("always_copy", &ctx.always_copy)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Use copy instead of hard-link")
|
|
.long_description(unindent(R"(
|
|
Register a preference that files be copied into a prefix during
|
|
install rather than hard-linked.)")));
|
|
|
|
insert(Configurable("always_softlink", &ctx.always_softlink)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.needs({ "always_copy" })
|
|
.set_post_merge_hook(detail::always_softlink_hook)
|
|
.description("Use soft-link instead of hard-link")
|
|
.long_description(unindent(R"(
|
|
Register a preference that files be soft-linked (symlinked) into a
|
|
prefix during install rather than hard-linked. The link source is the
|
|
package cache from where the package is being linked.
|
|
!WARNING: Using this option can result in corruption of long-lived
|
|
environments due to broken links (deleted cache).)")));
|
|
|
|
insert(
|
|
Configurable("shortcuts", &ctx.shortcuts)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description(
|
|
"Install start-menu shortcuts on Windows (not implemented on Linux / macOS)"));
|
|
|
|
insert(Configurable("safety_checks", &ctx.safety_checks)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names({ "CONDA_SAFETY_CHECKS", "MAMBA_SAFETY_CHECKS" })
|
|
.description("Safety checks policy ('enabled', 'warn', or 'disabled')")
|
|
.long_description(unindent(R"(
|
|
Enforce available safety guarantees during package installation. The
|
|
value must be one of 'enabled', 'warn', or 'disabled'.)")));
|
|
|
|
insert(Configurable("extra_safety_checks", &ctx.extra_safety_checks)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names({ "CONDA_EXTRA_SAFETY_CHECKS", "MAMBA_EXTRA_SAFETY_CHECKS" })
|
|
.description("Run extra verifications on packages")
|
|
.long_description(unindent(R"(
|
|
Spend extra time validating package contents. Currently, runs sha256
|
|
verification on every file within each package during installation.)")));
|
|
|
|
insert(Configurable("verify_artifacts", &ctx.verify_artifacts)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Run verifications on packages signatures")
|
|
.long_description(unindent(R"(
|
|
Spend extra time validating package contents. It consists of running
|
|
cryptographic verifications on channels and packages metadata.)")));
|
|
|
|
insert(Configurable("lock_timeout", &ctx.lock_timeout)
|
|
.group("Link & Install")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("LockFile timeout")
|
|
.long_description(unindent(R"(
|
|
LockFile timeout for blocking mode when waiting for another process
|
|
to release the path. Default is 0 (no timeout))")));
|
|
|
|
// Output, Prompt and Flow
|
|
insert(Configurable("always_yes", &ctx.always_yes)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Automatically answer yes on prompted questions"));
|
|
|
|
insert(Configurable("auto_activate_base", &ctx.auto_activate_base)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Automatically activate the base env")
|
|
.long_description(unindent(R"(
|
|
Automatically activate the base environment during shell
|
|
initialization.)")));
|
|
|
|
insert(Configurable("dry_run", &ctx.dry_run)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_env_var_names()
|
|
.description("Only display what would have been done"));
|
|
/*
|
|
insert(Configurable("log_level", &ctx.verbosity)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_env_var_names()
|
|
.description("Set the log level")
|
|
.long_description(unindent(R"(
|
|
Set the log level. Log level can be one of {'off', 'fatal',
|
|
'error', 'warning', 'info', 'debug', 'trace'}.)"))
|
|
.set_post_merge_hook(detail::log_level_hook));
|
|
*/
|
|
insert(Configurable("json", &ctx.json)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.description("Report all output as json"));
|
|
|
|
insert(
|
|
Configurable("print_config_only", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.needs({ "debug" })
|
|
.set_post_merge_hook(detail::print_config_only_hook)
|
|
.description("Print the context after loading the config. Allow ultra-dry runs"));
|
|
|
|
insert(
|
|
Configurable("print_context_only", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.needs({ "debug" })
|
|
.set_post_merge_hook(detail::print_context_only_hook)
|
|
.description("Print the context after loading the config. Allow ultra-dry runs"));
|
|
|
|
insert(Configurable("show_banner", true)
|
|
.group("Output, Prompt and Flow Control")
|
|
.needs({ "quiet", "json" })
|
|
.set_post_merge_hook(detail::show_banner_hook)
|
|
.set_single_op_lifetime()
|
|
.description("Show the banner"));
|
|
|
|
insert(Configurable("show_all_configs", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display all configs, including not rc configurable"));
|
|
|
|
insert(Configurable("show_all_rc_configs", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display all rc configurable configs"));
|
|
|
|
insert(Configurable("show_config_descriptions", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display configs descriptions"));
|
|
|
|
insert(Configurable("show_config_groups", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display configs groups"));
|
|
|
|
insert(Configurable("show_config_long_descriptions", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display configs long descriptions"));
|
|
|
|
insert(Configurable("show_config_sources", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display all configs sources"));
|
|
|
|
insert(Configurable("show_config_values", false)
|
|
.group("Output, Prompt and Flow Control")
|
|
.description("Display configs values"));
|
|
|
|
insert(Configurable("quiet", &ctx.quiet)
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_rc_configurable()
|
|
.set_env_var_names()
|
|
.needs({ "json" })
|
|
.implies({ "show_banner" })
|
|
.description("Set quiet mode (print less output)"));
|
|
|
|
insert(Configurable("verbose", std::uint8_t(0))
|
|
.group("Output, Prompt and Flow Control")
|
|
.set_post_merge_hook(detail::verbose_hook)
|
|
.description("Set higher verbosity")
|
|
.long_description(unindent(R"(
|
|
Set a higher log verbosity than the default one.
|
|
This configurable has a similar effect as 'log_level',
|
|
except it can only increase the log level. If you need
|
|
fine-grained control, prefer 'log_level'.
|
|
'verbose' and 'log_level' are exclusive.)")));
|
|
|
|
// Config
|
|
insert(Configurable("rc_files", std::vector<fs::path>({}))
|
|
.group("Config sources")
|
|
.set_env_var_names({ "MAMBARC", "CONDARC" })
|
|
.needs({ "no_rc" })
|
|
.set_post_merge_hook(detail::rc_files_hook)
|
|
.description("Paths to the configuration files to use"));
|
|
|
|
insert(Configurable("override_rc_files", true)
|
|
.group("Config sources")
|
|
.set_env_var_names()
|
|
.description("Whether to override rc files by highest precedence"));
|
|
|
|
insert(Configurable("no_rc", &ctx.no_rc)
|
|
.group("Config sources")
|
|
.set_env_var_names()
|
|
.description("Disable the use of configuration files"));
|
|
|
|
insert(Configurable("no_env", &ctx.no_env)
|
|
.group("Config sources")
|
|
.set_env_var_names()
|
|
.description("Disable the use of environment variables"));
|
|
}
|
|
|
|
void Configuration::reset_configurables()
|
|
{
|
|
m_config.clear();
|
|
m_config_order.clear();
|
|
set_configurables();
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::vector<ConfigurableInterface*>>>
|
|
Configuration::get_grouped_config()
|
|
{
|
|
std::map<std::string, std::vector<ConfigurableInterface*>> map;
|
|
std::vector<std::pair<std::string, std::vector<ConfigurableInterface*>>> res;
|
|
std::vector<std::string> group_order;
|
|
|
|
for (auto& name : m_config_order)
|
|
{
|
|
auto& c = m_config.at(name);
|
|
|
|
if (map.count(c.group()) == 0)
|
|
{
|
|
group_order.push_back(c.group());
|
|
}
|
|
map[c.group()].push_back(&c);
|
|
}
|
|
|
|
for (auto& g : group_order)
|
|
{
|
|
res.push_back({ g, map.at(g) });
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
std::vector<fs::path> Configuration::compute_default_rc_sources(const RCConfigLevel& level)
|
|
{
|
|
auto& ctx = Context::instance();
|
|
|
|
std::vector<fs::path> system;
|
|
if (on_mac || on_linux)
|
|
{
|
|
system = { "/etc/conda/.condarc", "/etc/conda/condarc",
|
|
"/etc/conda/condarc.d/", "/etc/conda/.mambarc",
|
|
"/var/lib/conda/.condarc", "/var/lib/conda/condarc",
|
|
"/var/lib/conda/condarc.d/", "/var/lib/conda/.mambarc" };
|
|
}
|
|
else
|
|
{
|
|
system = { "C:\\ProgramData\\conda\\.condarc",
|
|
"C:\\ProgramData\\conda\\condarc",
|
|
"C:\\ProgramData\\conda\\condarc.d",
|
|
"C:\\ProgramData\\conda\\.mambarc" };
|
|
}
|
|
|
|
std::vector<fs::path> root = { ctx.root_prefix / ".condarc",
|
|
ctx.root_prefix / "condarc",
|
|
ctx.root_prefix / "condarc.d",
|
|
ctx.root_prefix / ".mambarc" };
|
|
|
|
std::vector<fs::path> 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::path> prefix = { ctx.target_prefix / ".condarc",
|
|
ctx.target_prefix / "condarc",
|
|
ctx.target_prefix / "condarc.d",
|
|
ctx.target_prefix / ".mambarc" };
|
|
|
|
std::vector<fs::path> sources;
|
|
|
|
if (level >= RCConfigLevel::kSystemDir)
|
|
sources.insert(sources.end(), system.begin(), system.end());
|
|
if (level >= RCConfigLevel::kRootPrefix)
|
|
sources.insert(sources.end(), root.begin(), root.end());
|
|
if (level >= RCConfigLevel::kHomeDir)
|
|
sources.insert(sources.end(), home.begin(), home.end());
|
|
if (level >= RCConfigLevel::kTargetPrefix)
|
|
sources.insert(sources.end(), prefix.begin(), prefix.end());
|
|
|
|
// Sort by precedence
|
|
std::reverse(sources.begin(), sources.end());
|
|
|
|
return sources;
|
|
}
|
|
|
|
void Configuration::load()
|
|
{
|
|
clear_rc_sources();
|
|
clear_rc_values();
|
|
|
|
compute_loading_sequence();
|
|
reset_compute_counters();
|
|
|
|
m_load_lock = true;
|
|
for (auto& c : m_loading_sequence)
|
|
{
|
|
at(c).compute();
|
|
}
|
|
m_load_lock = false;
|
|
CONFIG_DEBUGGING;
|
|
}
|
|
|
|
bool Configuration::is_loading()
|
|
{
|
|
return m_load_lock;
|
|
}
|
|
|
|
void Configuration::compute_loading_sequence()
|
|
{
|
|
// break circular dependencies
|
|
// target_prefix (env configurable) -> no_env (rc configurable) -> rc_files -> target_prefix
|
|
// target_prefix -> always_yes (rc configurable) -> rc_files -> target_prefix
|
|
// hack to eventually display the banner before log messages. Needs to recompute those
|
|
// rc configurable configs in rc_files_hook if any rc file is used
|
|
m_loading_sequence
|
|
= { "no_env", "always_yes", "debug", "print_context_only", "print_config_only",
|
|
"quiet", "json", "show_banner" };
|
|
|
|
std::vector<std::string> locks;
|
|
for (auto& c : m_config_order)
|
|
{
|
|
add_to_loading_sequence(m_loading_sequence, c, locks);
|
|
}
|
|
}
|
|
|
|
void Configuration::add_to_loading_sequence(std::vector<std::string>& seq,
|
|
const std::string& name,
|
|
std::vector<std::string>& locks)
|
|
{
|
|
auto found = std::find(seq.begin(), seq.end(), name);
|
|
|
|
if (found == seq.end())
|
|
{
|
|
at(name).lock();
|
|
locks.push_back(name);
|
|
|
|
for (auto& n : at(name).needed())
|
|
{
|
|
if (at(n).locked())
|
|
{
|
|
LOG_ERROR << "Circular import: " << join("->", locks) << "->" << n;
|
|
throw std::runtime_error(
|
|
"Circular import detected in configuration. Aborting.");
|
|
}
|
|
add_to_loading_sequence(seq, n, locks);
|
|
}
|
|
|
|
// The given config may have been added by implied configs
|
|
found = std::find(seq.begin(), seq.end(), name);
|
|
if (found == seq.end())
|
|
{
|
|
seq.push_back(name);
|
|
}
|
|
|
|
at(name).free();
|
|
locks.pop_back();
|
|
|
|
for (auto& n : at(name).implied())
|
|
add_to_loading_sequence(seq, n, locks);
|
|
}
|
|
}
|
|
|
|
void Configuration::reset_compute_counters()
|
|
{
|
|
for (auto& c : m_config)
|
|
c.second.reset_compute_counter();
|
|
}
|
|
|
|
void Configuration::clear_rc_values()
|
|
{
|
|
for (auto& c : m_config)
|
|
c.second.clear_rc_values();
|
|
}
|
|
|
|
void Configuration::clear_rc_sources()
|
|
{
|
|
m_sources.clear();
|
|
m_valid_sources.clear();
|
|
m_rc_yaml_nodes_cache.clear();
|
|
}
|
|
|
|
void Configuration::clear_cli_values()
|
|
{
|
|
for (auto& c : m_config)
|
|
c.second.clear_cli_value();
|
|
}
|
|
|
|
void Configuration::clear_values()
|
|
{
|
|
for (auto& c : m_config)
|
|
c.second.clear_values();
|
|
}
|
|
|
|
void Configuration::operation_teardown()
|
|
{
|
|
for (auto& c : m_config)
|
|
if (c.second.has_single_op_lifetime())
|
|
c.second.clear_values();
|
|
else
|
|
c.second.clear_cli_value();
|
|
}
|
|
|
|
std::vector<fs::path> Configuration::sources()
|
|
{
|
|
return m_sources;
|
|
}
|
|
|
|
std::vector<fs::path> Configuration::valid_sources()
|
|
{
|
|
return m_valid_sources;
|
|
}
|
|
|
|
std::map<std::string, ConfigurableInterface>& Configuration::config()
|
|
{
|
|
return m_config;
|
|
}
|
|
|
|
ConfigurableInterface& Configuration::at(const std::string& name)
|
|
{
|
|
try
|
|
{
|
|
return m_config.at(name);
|
|
}
|
|
catch (const std::out_of_range& e)
|
|
{
|
|
LOG_ERROR << "Configurable '" << name << "' does not exists";
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
YAML::Node Configuration::load_rc_file(const fs::path& file)
|
|
{
|
|
YAML::Node config;
|
|
try
|
|
{
|
|
config = YAML::LoadFile(file);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_ERROR << "Error in file " << file << " (Skipped)";
|
|
}
|
|
return config;
|
|
}
|
|
|
|
void Configuration::set_rc_values(std::vector<fs::path> possible_rc_paths,
|
|
const RCConfigLevel& level)
|
|
{
|
|
if (possible_rc_paths.empty())
|
|
possible_rc_paths = compute_default_rc_sources(level);
|
|
|
|
m_sources = get_existing_rc_sources(possible_rc_paths);
|
|
m_valid_sources.clear();
|
|
|
|
for (const auto& s : m_sources)
|
|
{
|
|
if (!m_rc_yaml_nodes_cache.count(s))
|
|
{
|
|
auto node = load_rc_file(s);
|
|
if (node.IsNull())
|
|
continue;
|
|
|
|
m_rc_yaml_nodes_cache.insert({ s, node });
|
|
}
|
|
m_valid_sources.push_back(s);
|
|
}
|
|
|
|
if (!m_valid_sources.empty())
|
|
{
|
|
for (auto& it : m_config)
|
|
{
|
|
auto& key = it.first;
|
|
auto& c = it.second;
|
|
|
|
if (!c.rc_configurable() || (c.rc_configurable_level() > level)
|
|
|| c.rc_configured())
|
|
continue;
|
|
|
|
for (const auto& source : m_valid_sources)
|
|
{
|
|
auto yaml = m_rc_yaml_nodes_cache[source];
|
|
if (!yaml[key] || yaml[key].IsNull())
|
|
continue;
|
|
|
|
c.set_rc_yaml_value(yaml[key], env::shrink_user(source));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<fs::path> Configuration::get_existing_rc_sources(
|
|
const std::vector<fs::path>& possible_rc_paths)
|
|
{
|
|
std::vector<fs::path> sources;
|
|
|
|
for (const fs::path& l : possible_rc_paths)
|
|
{
|
|
if (detail::is_config_file(l))
|
|
{
|
|
sources.push_back(l);
|
|
LOG_DEBUG << "Configuration found at '" << l.string() << "'";
|
|
}
|
|
else if (fs::is_directory(l))
|
|
{
|
|
for (fs::path p : fs::directory_iterator(l))
|
|
{
|
|
if (detail::is_config_file(p))
|
|
{
|
|
sources.push_back(p);
|
|
LOG_DEBUG << "Configuration found at '" << p.string() << "'";
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG << "Configuration not found at '" << p.string() << "'";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!l.empty())
|
|
LOG_DEBUG << "Configuration not found at '" << l.string() << "'";
|
|
}
|
|
}
|
|
|
|
return sources;
|
|
}
|
|
|
|
std::string Configuration::dump(int opts, std::vector<std::string> names)
|
|
{
|
|
bool show_values = opts & MAMBA_SHOW_CONFIG_VALUES;
|
|
bool show_sources = opts & MAMBA_SHOW_CONFIG_SRCS;
|
|
bool show_descs = opts & MAMBA_SHOW_CONFIG_DESCS;
|
|
bool show_long_descs = opts & MAMBA_SHOW_CONFIG_LONG_DESCS;
|
|
bool show_groups = opts & MAMBA_SHOW_CONFIG_GROUPS;
|
|
bool show_all_rcs = opts & MAMBA_SHOW_ALL_RC_CONFIGS;
|
|
bool show_all = opts & MAMBA_SHOW_ALL_CONFIGS;
|
|
|
|
bool first_config = true;
|
|
YAML::Emitter out;
|
|
// out.SetNullFormat(YAML::EMITTER_MANIP::LowerNull); // TODO: switch from ~ to null
|
|
|
|
for (auto& group_it : get_grouped_config())
|
|
{
|
|
auto& group_name = group_it.first;
|
|
auto& configs = group_it.second;
|
|
bool first_group_config = true;
|
|
|
|
for (auto& c : configs)
|
|
{
|
|
auto is_required = std::find(names.begin(), names.end(), c->name()) != names.end();
|
|
if (!names.empty() && !is_required)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((c->rc_configurable() && (c->configured() || show_all_rcs)) || is_required
|
|
|| show_all)
|
|
{
|
|
if (show_descs || show_long_descs)
|
|
{
|
|
if (show_groups && first_group_config)
|
|
{
|
|
if (!first_config)
|
|
out << YAML::Newline << YAML::Newline;
|
|
detail::print_group_title(out, group_name);
|
|
}
|
|
|
|
if (!first_config || (first_config && show_groups))
|
|
out << YAML::Newline << YAML::Newline;
|
|
|
|
out << YAML::Comment(c->name()) << YAML::Newline;
|
|
if (show_long_descs)
|
|
{
|
|
out << YAML::Comment(prepend(c->long_description(), " ", " "));
|
|
}
|
|
else
|
|
{
|
|
out << YAML::Comment(prepend(c->description(), " ", " "));
|
|
}
|
|
}
|
|
|
|
if (show_values)
|
|
{
|
|
if (first_config)
|
|
out << YAML::BeginMap;
|
|
out << YAML::Key << c->name();
|
|
out << YAML::Value;
|
|
detail::print_configurable(out, *c, show_sources);
|
|
}
|
|
|
|
first_config = false;
|
|
first_group_config = false;
|
|
}
|
|
}
|
|
}
|
|
if (show_values && !first_config)
|
|
out << YAML::EndMap;
|
|
|
|
return out.c_str();
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
bool has_config_name(const std::string& file)
|
|
{
|
|
return fs::path(file).filename() == ".condarc" || fs::path(file).filename() == "condarc"
|
|
|| fs::path(file).filename() == ".mambarc"
|
|
|| fs::path(file).filename() == "mambarc" || ends_with(file, ".yml")
|
|
|| ends_with(file, ".yaml");
|
|
}
|
|
|
|
bool is_config_file(const fs::path& path)
|
|
{
|
|
return fs::exists(path) && (!fs::is_directory(path)) && has_config_name(path.string());
|
|
}
|
|
|
|
void print_scalar_node(YAML::Emitter& out,
|
|
YAML::Node value,
|
|
YAML::Node source,
|
|
bool show_source)
|
|
{
|
|
if (!value.IsScalar())
|
|
{
|
|
throw std::runtime_error("Invalid scalar value");
|
|
}
|
|
out << value;
|
|
|
|
if (show_source)
|
|
{
|
|
if (source.IsScalar())
|
|
{
|
|
out << YAML::Comment("'" + source.as<std::string>() + "'");
|
|
}
|
|
else
|
|
{
|
|
auto srcs = source.as<std::vector<std::string>>();
|
|
std::string comment = "'" + srcs.at(0) + "'";
|
|
for (std::size_t i = 1; i < srcs.size(); ++i)
|
|
{
|
|
comment += " > '" + srcs.at(i) + "'";
|
|
}
|
|
out << YAML::Comment(comment);
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_seq_node(YAML::Emitter& out,
|
|
YAML::Node value,
|
|
YAML::Node source,
|
|
bool show_source)
|
|
{
|
|
if (!value.IsSequence())
|
|
{
|
|
throw std::runtime_error("Invalid sequence value");
|
|
}
|
|
|
|
if (value.size() > 0)
|
|
{
|
|
out << YAML::BeginSeq;
|
|
for (std::size_t n = 0; n < value.size(); ++n)
|
|
{
|
|
if (value[n].IsScalar())
|
|
{
|
|
print_scalar_node(out, value[n], source[n], show_source);
|
|
}
|
|
if (value[n].IsSequence())
|
|
{
|
|
print_seq_node(out, value[n], source[n], show_source);
|
|
}
|
|
if (value[n].IsMap())
|
|
{
|
|
print_map_node(out, value[n], source[n], show_source);
|
|
}
|
|
}
|
|
out << YAML::EndSeq;
|
|
}
|
|
else
|
|
{
|
|
out << YAML::_Null();
|
|
if (show_source)
|
|
out << YAML::Comment("'default'");
|
|
}
|
|
}
|
|
|
|
void print_map_node(YAML::Emitter& out,
|
|
YAML::Node value,
|
|
YAML::Node source,
|
|
bool show_source)
|
|
{
|
|
if (!value.IsMap())
|
|
{
|
|
throw std::runtime_error("Invalid map value");
|
|
}
|
|
|
|
out << YAML::BeginMap;
|
|
for (auto n : value)
|
|
{
|
|
auto key = n.first.as<std::string>();
|
|
out << YAML::Key << n.first;
|
|
out << YAML::Value;
|
|
|
|
if (n.second.IsScalar())
|
|
{
|
|
print_scalar_node(out, n.second, source[key], show_source);
|
|
}
|
|
if (n.second.IsSequence())
|
|
{
|
|
print_seq_node(out, n.second, source[key], show_source);
|
|
}
|
|
if (n.second.IsMap())
|
|
{
|
|
print_map_node(out, n.second, source[key], show_source);
|
|
}
|
|
}
|
|
out << YAML::EndMap;
|
|
}
|
|
|
|
void print_configurable(YAML::Emitter& out,
|
|
const ConfigurableInterface& config,
|
|
bool show_source)
|
|
{
|
|
auto value = config.yaml_value();
|
|
auto source = config.source();
|
|
|
|
if (value.IsScalar())
|
|
{
|
|
print_scalar_node(out, value, source, show_source);
|
|
}
|
|
if (value.IsSequence())
|
|
{
|
|
print_seq_node(out, value, source, show_source);
|
|
}
|
|
if (value.IsMap())
|
|
{
|
|
print_map_node(out, value, source, show_source);
|
|
}
|
|
}
|
|
|
|
void print_group_title(YAML::Emitter& out, const std::string& name)
|
|
{
|
|
auto group_title = name + " Configuration";
|
|
auto blk_size = 52 - group_title.size();
|
|
int prepend_blk = blk_size / 2;
|
|
int append_blk = blk_size - prepend_blk;
|
|
|
|
out << YAML::Comment(std::string(54, '#')) << YAML::Newline;
|
|
out << YAML::Comment("#" + std::string(prepend_blk, ' ') + group_title
|
|
+ std::string(append_blk, ' ') + "#")
|
|
<< YAML::Newline;
|
|
out << YAML::Comment(std::string(54, '#'));
|
|
}
|
|
}
|
|
}
|