mirror of https://github.com/mamba-org/mamba.git
763 lines
27 KiB
C++
763 lines
27 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 <reproc/reproc.h>
|
|
#include <reproc++/run.hpp>
|
|
#include <stdexcept>
|
|
|
|
#include "mamba/api/configuration.hpp"
|
|
#include "mamba/api/install.hpp"
|
|
#include "mamba/api/channel_loader.hpp"
|
|
|
|
#include "mamba/core/mamba_fs.hpp"
|
|
#include "mamba/core/output.hpp"
|
|
#include "mamba/core/package_cache.hpp"
|
|
#include "mamba/core/pinning.hpp"
|
|
#include "mamba/core/transaction.hpp"
|
|
#include "mamba/core/util.hpp"
|
|
#include "mamba/core/virtual_packages.hpp"
|
|
#include "mamba/core/env_lockfile.hpp"
|
|
#include "mamba/core/activation.hpp"
|
|
|
|
#include "termcolor/termcolor.hpp"
|
|
|
|
namespace mamba
|
|
{
|
|
namespace
|
|
{
|
|
using command_args = std::vector<std::string>;
|
|
|
|
tl::expected<command_args, std::runtime_error> get_other_pkg_mgr_install_instructions(
|
|
const std::string& name, const std::string& target_prefix, fs::path spec_file)
|
|
{
|
|
const auto get_python_path
|
|
= [&] { return env::which("python", get_path_dirs(target_prefix)).u8string(); };
|
|
|
|
const std::unordered_map<std::string, command_args> other_pkg_mgr_install_instructions{
|
|
{ "pip",
|
|
{ get_python_path(), "-m", "pip", "install", "-r", spec_file, "--no-input" } }
|
|
};
|
|
|
|
auto found_it = other_pkg_mgr_install_instructions.find(name);
|
|
if (found_it != other_pkg_mgr_install_instructions.end())
|
|
return found_it->second;
|
|
else
|
|
return tl::unexpected(std::runtime_error(
|
|
fmt::format("no install instruction found for package manager '{}'", name)));
|
|
}
|
|
|
|
}
|
|
|
|
bool reproc_killed(int status)
|
|
{
|
|
return status == REPROC_SIGKILL;
|
|
}
|
|
|
|
bool reproc_terminated(int status)
|
|
{
|
|
return status == REPROC_SIGTERM;
|
|
}
|
|
|
|
void assert_reproc_success(const reproc::options& options, int status, std::error_code ec)
|
|
{
|
|
bool killed_not_an_err = (options.stop.first.action == reproc::stop::kill)
|
|
|| (options.stop.second.action == reproc::stop::kill)
|
|
|| (options.stop.third.action == reproc::stop::kill);
|
|
|
|
bool terminated_not_an_err = (options.stop.first.action == reproc::stop::terminate)
|
|
|| (options.stop.second.action == reproc::stop::terminate)
|
|
|| (options.stop.third.action == reproc::stop::terminate);
|
|
|
|
if (ec || (!killed_not_an_err && reproc_killed(status))
|
|
|| (!terminated_not_an_err && reproc_terminated(status)))
|
|
{
|
|
if (ec)
|
|
LOG_ERROR << "Subprocess call failed: " << ec.message();
|
|
else if (reproc_killed(status))
|
|
LOG_ERROR << "Subprocess call failed (killed)";
|
|
else
|
|
LOG_ERROR << "Subprocess call failed (terminated)";
|
|
throw std::runtime_error("Subprocess call failed. Aborting.");
|
|
}
|
|
}
|
|
|
|
auto install_for_other_pkgmgr(const detail::other_pkg_mgr_spec& other_spec)
|
|
{
|
|
const auto& pkg_mgr = other_spec.pkg_mgr;
|
|
const auto& deps = other_spec.deps;
|
|
const auto& cwd = other_spec.cwd;
|
|
|
|
const auto& ctx = Context::instance();
|
|
|
|
TemporaryFile specs;
|
|
{
|
|
std::ofstream specs_f = open_ofstream(specs.path());
|
|
for (auto& d : deps)
|
|
specs_f << d.c_str() << '\n';
|
|
}
|
|
|
|
command_args install_instructions = [&]
|
|
{
|
|
const auto maybe_instructions
|
|
= get_other_pkg_mgr_install_instructions(pkg_mgr, ctx.target_prefix, specs.path());
|
|
if (maybe_instructions)
|
|
return maybe_instructions.value();
|
|
else
|
|
throw maybe_instructions.error();
|
|
}();
|
|
|
|
auto [wrapped_command, tmpfile]
|
|
= prepare_wrapped_call(ctx.target_prefix, install_instructions);
|
|
|
|
reproc::options options;
|
|
options.redirect.parent = true;
|
|
options.working_directory = cwd.c_str();
|
|
|
|
Console::stream() << "\n"
|
|
<< termcolor::cyan << "Installing " << pkg_mgr
|
|
<< " packages: " << join(", ", deps) << termcolor::reset;
|
|
LOG_INFO << "Calling: " << join(" ", install_instructions);
|
|
|
|
auto [status, ec] = reproc::run(wrapped_command, options);
|
|
assert_reproc_success(options, status, ec);
|
|
if (status != 0)
|
|
{
|
|
throw std::runtime_error("pip failed to install packages");
|
|
}
|
|
}
|
|
|
|
auto& truthy_values()
|
|
{
|
|
static std::map<std::string, int> vals{
|
|
{ "win", 0 },
|
|
{ "unix", 0 },
|
|
{ "osx", 0 },
|
|
{ "linux", 0 },
|
|
};
|
|
|
|
const auto& ctx = Context::instance();
|
|
if (starts_with(ctx.platform, "win"))
|
|
{
|
|
vals["win"] = true;
|
|
}
|
|
else
|
|
{
|
|
vals["unix"] = true;
|
|
if (starts_with(ctx.platform, "linux"))
|
|
{
|
|
vals["linux"] = true;
|
|
}
|
|
else if (starts_with(ctx.platform, "osx"))
|
|
{
|
|
vals["osx"] = true;
|
|
}
|
|
}
|
|
return vals;
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
bool eval_selector(const std::string& selector)
|
|
{
|
|
if (!(starts_with(selector, "sel(") && selector[selector.size() - 1] == ')'))
|
|
{
|
|
throw std::runtime_error(
|
|
"Couldn't parse selector. Needs to start with sel( and end with )");
|
|
}
|
|
std::string expr = selector.substr(4, selector.size() - 5);
|
|
|
|
if (truthy_values().find(expr) == truthy_values().end())
|
|
{
|
|
throw std::runtime_error("Couldn't parse selector. Value not in [unix, linux, "
|
|
"osx, win] or additional whitespaces found.");
|
|
}
|
|
|
|
return truthy_values()[expr];
|
|
}
|
|
|
|
yaml_file_contents read_yaml_file(fs::path yaml_file)
|
|
{
|
|
auto file = fs::weakly_canonical(env::expand_user(yaml_file));
|
|
if (!fs::exists(file))
|
|
{
|
|
LOG_ERROR << "YAML spec file '" << file.string() << "' not found";
|
|
throw std::runtime_error("File not found. Aborting.");
|
|
}
|
|
|
|
yaml_file_contents result;
|
|
YAML::Node f;
|
|
try
|
|
{
|
|
f = YAML::LoadFile(file);
|
|
}
|
|
catch (YAML::Exception& e)
|
|
{
|
|
LOG_ERROR << "YAML error in spec file '" << file.string() << "'";
|
|
throw e;
|
|
}
|
|
|
|
YAML::Node deps;
|
|
if (f["dependencies"] && f["dependencies"].IsSequence() && f["dependencies"].size() > 0)
|
|
deps = f["dependencies"];
|
|
else
|
|
{
|
|
LOG_ERROR << "No 'dependencies' specified in YAML spec file '" << file.string()
|
|
<< "'";
|
|
throw std::runtime_error("Invalid spec file. Aborting.");
|
|
}
|
|
YAML::Node final_deps;
|
|
|
|
bool has_pip_deps = false;
|
|
for (auto it = deps.begin(); it != deps.end(); ++it)
|
|
{
|
|
if (it->IsScalar())
|
|
{
|
|
final_deps.push_back(*it);
|
|
}
|
|
else if (it->IsMap())
|
|
{
|
|
// we merge a map to the upper level if the selector works
|
|
for (const auto& map_el : *it)
|
|
{
|
|
std::string key = map_el.first.as<std::string>();
|
|
if (starts_with(key, "sel("))
|
|
{
|
|
bool selected = detail::eval_selector(key);
|
|
if (selected)
|
|
{
|
|
const YAML::Node& rest = map_el.second;
|
|
if (rest.IsScalar())
|
|
{
|
|
final_deps.push_back(rest);
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error(
|
|
"Complicated selection merge not implemented yet.");
|
|
}
|
|
}
|
|
}
|
|
else if (key == "pip")
|
|
{
|
|
result.others_pkg_mgrs_specs.push_back(
|
|
{ "pip",
|
|
map_el.second.as<std::vector<std::string>>(),
|
|
fs::absolute(yaml_file.parent_path()) });
|
|
has_pip_deps = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> dependencies;
|
|
try
|
|
{
|
|
dependencies = final_deps.as<std::vector<std::string>>();
|
|
}
|
|
catch (const YAML::Exception& e)
|
|
{
|
|
LOG_ERROR << "Bad conversion of 'dependencies' to a vector of string: "
|
|
<< final_deps;
|
|
throw e;
|
|
}
|
|
|
|
if (has_pip_deps && !std::count(dependencies.begin(), dependencies.end(), "pip"))
|
|
dependencies.push_back("pip");
|
|
|
|
result.dependencies = dependencies;
|
|
|
|
if (f["channels"])
|
|
{
|
|
try
|
|
{
|
|
result.channels = f["channels"].as<std::vector<std::string>>();
|
|
}
|
|
catch (YAML::Exception& e)
|
|
{
|
|
LOG_ERROR << "Could not read 'channels' as vector of strings from '"
|
|
<< file.string() << "'";
|
|
throw e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG << "No 'channels' specified in YAML spec file '" << file.string() << "'";
|
|
}
|
|
|
|
if (f["name"])
|
|
{
|
|
result.name = f["name"].as<std::string>();
|
|
}
|
|
{
|
|
LOG_DEBUG << "No env 'name' specified in YAML spec file '" << file.string() << "'";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::tuple<std::vector<PackageInfo>, std::vector<MatchSpec>> parse_urls_to_package_info(
|
|
const std::vector<std::string>& urls)
|
|
{
|
|
std::vector<PackageInfo> pi_result;
|
|
std::vector<MatchSpec> ms_result;
|
|
for (auto& u : urls)
|
|
{
|
|
if (strip(u).size() == 0)
|
|
continue;
|
|
std::size_t hash = u.find_first_of('#');
|
|
MatchSpec ms(u.substr(0, hash));
|
|
PackageInfo p(ms.name);
|
|
p.url = ms.url;
|
|
p.build_string = ms.build;
|
|
p.version = ms.version;
|
|
p.channel = ms.channel;
|
|
p.fn = ms.fn;
|
|
|
|
if (hash != std::string::npos)
|
|
{
|
|
p.md5 = u.substr(hash + 1);
|
|
ms.brackets["md5"] = u.substr(hash + 1);
|
|
}
|
|
pi_result.push_back(p);
|
|
ms_result.push_back(ms);
|
|
}
|
|
return std::make_tuple(pi_result, ms_result);
|
|
}
|
|
|
|
bool operator==(const other_pkg_mgr_spec& s1, const other_pkg_mgr_spec& s2)
|
|
{
|
|
return (s1.pkg_mgr == s2.pkg_mgr) && (s1.deps == s2.deps) && (s1.cwd == s2.cwd);
|
|
}
|
|
}
|
|
|
|
void install()
|
|
{
|
|
auto& config = Configuration::instance();
|
|
|
|
config.at("create_base").set_value(true);
|
|
config.at("use_target_prefix_fallback").set_value(true);
|
|
config.at("target_prefix_checks")
|
|
.set_value(MAMBA_ALLOW_EXISTING_PREFIX | MAMBA_NOT_ALLOW_MISSING_PREFIX
|
|
| MAMBA_NOT_ALLOW_NOT_ENV_PREFIX | MAMBA_EXPECT_EXISTING_PREFIX);
|
|
config.load();
|
|
|
|
auto& install_specs = config.at("specs").value<std::vector<std::string>>();
|
|
auto& use_explicit = config.at("explicit_install").value<bool>();
|
|
|
|
if (Context::instance().env_lockfile)
|
|
{
|
|
const auto lockfile_path = Context::instance().env_lockfile.value();
|
|
LOG_DEBUG << "Lockfile: " << lockfile_path.string();
|
|
install_lockfile_specs(lockfile_path, false);
|
|
}
|
|
else if (!install_specs.empty())
|
|
{
|
|
if (use_explicit)
|
|
{
|
|
install_explicit_specs(install_specs, false);
|
|
}
|
|
else
|
|
{
|
|
mamba::install_specs(install_specs, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console::instance().print("Nothing to do.");
|
|
}
|
|
|
|
config.operation_teardown();
|
|
}
|
|
|
|
int RETRY_SUBDIR_FETCH = 1 << 0;
|
|
int RETRY_SOLVE_ERROR = 1 << 1;
|
|
|
|
void install_specs(const std::vector<std::string>& specs,
|
|
bool create_env,
|
|
int solver_flag,
|
|
int is_retry)
|
|
{
|
|
auto& ctx = Context::instance();
|
|
auto& config = Configuration::instance();
|
|
|
|
auto& no_pin = config.at("no_pin").value<bool>();
|
|
auto& no_py_pin = config.at("no_py_pin").value<bool>();
|
|
auto& freeze_installed = config.at("freeze_installed").value<bool>();
|
|
auto& force_reinstall = config.at("force_reinstall").value<bool>();
|
|
auto& no_deps = config.at("no_deps").value<bool>();
|
|
auto& only_deps = config.at("only_deps").value<bool>();
|
|
auto& retry_clean_cache = config.at("retry_clean_cache").value<bool>();
|
|
|
|
if (ctx.target_prefix.empty())
|
|
{
|
|
throw std::runtime_error("No active target prefix");
|
|
}
|
|
if (!fs::exists(ctx.target_prefix) && create_env == false)
|
|
{
|
|
throw std::runtime_error(
|
|
fmt::format("Prefix does not exist at: {}", ctx.target_prefix.string()));
|
|
}
|
|
|
|
MultiPackageCache package_caches(ctx.pkgs_dirs);
|
|
|
|
// add channels from specs
|
|
std::vector<mamba::MatchSpec> match_specs(specs.begin(), specs.end());
|
|
for (const auto& m : match_specs)
|
|
{
|
|
if (!m.channel.empty())
|
|
{
|
|
ctx.channels.push_back(m.channel);
|
|
}
|
|
}
|
|
|
|
if (ctx.channels.empty() && !ctx.offline)
|
|
{
|
|
LOG_WARNING << "No 'channels' specified";
|
|
}
|
|
|
|
MPool pool;
|
|
// functions implied in 'and_then' coding-styles must return the same type
|
|
// which limits this syntax
|
|
/*auto exp_prefix_data = load_channels(pool, package_caches, is_retry)
|
|
.and_then([&ctx](const auto&) { return
|
|
PrefixData::create(ctx.target_prefix); } ) .map_error([](const mamba_error& err) { throw
|
|
std::runtime_error(err.what());
|
|
});*/
|
|
auto exp_load = load_channels(pool, package_caches, is_retry);
|
|
if (!exp_load)
|
|
{
|
|
throw std::runtime_error(exp_load.error().what());
|
|
}
|
|
|
|
auto exp_prefix_data = PrefixData::create(ctx.target_prefix);
|
|
if (!exp_prefix_data)
|
|
{
|
|
throw std::runtime_error(exp_prefix_data.error().what());
|
|
}
|
|
PrefixData& prefix_data = exp_prefix_data.value();
|
|
|
|
std::vector<std::string> prefix_pkgs;
|
|
for (auto& it : prefix_data.records())
|
|
prefix_pkgs.push_back(it.first);
|
|
|
|
prefix_data.add_packages(get_virtual_packages());
|
|
|
|
MRepo::create(pool, prefix_data);
|
|
|
|
MSolver solver(pool,
|
|
{ { SOLVER_FLAG_ALLOW_UNINSTALL, ctx.allow_uninstall },
|
|
{ SOLVER_FLAG_ALLOW_DOWNGRADE, ctx.allow_downgrade },
|
|
{ SOLVER_FLAG_STRICT_REPO_PRIORITY,
|
|
ctx.channel_priority == ChannelPriority::kStrict } });
|
|
|
|
solver.set_postsolve_flags({ { MAMBA_NO_DEPS, no_deps },
|
|
{ MAMBA_ONLY_DEPS, only_deps },
|
|
{ MAMBA_FORCE_REINSTALL, force_reinstall } });
|
|
|
|
if (freeze_installed && !prefix_pkgs.empty())
|
|
{
|
|
LOG_INFO << "Locking environment: " << prefix_pkgs.size() << " packages freezed";
|
|
solver.add_jobs(prefix_pkgs, SOLVER_LOCK);
|
|
}
|
|
|
|
solver.add_jobs(specs, solver_flag);
|
|
|
|
if (!no_pin)
|
|
{
|
|
solver.add_pins(file_pins(prefix_data.path() / "conda-meta" / "pinned"));
|
|
solver.add_pins(ctx.pinned_packages);
|
|
}
|
|
|
|
if (!no_py_pin)
|
|
{
|
|
auto py_pin = python_pin(prefix_data, specs);
|
|
if (!py_pin.empty())
|
|
{
|
|
solver.add_pin(py_pin);
|
|
}
|
|
}
|
|
if (!solver.pinned_specs().empty())
|
|
{
|
|
std::vector<std::string> pinned_str;
|
|
for (auto& ms : solver.pinned_specs())
|
|
pinned_str.push_back(" - " + ms.conda_build_form() + "\n");
|
|
Console::instance().print("\nPinned packages:\n" + join("", pinned_str));
|
|
}
|
|
|
|
bool success = solver.solve();
|
|
if (!success)
|
|
{
|
|
Console::stream() << solver.problems_to_str();
|
|
if (retry_clean_cache && !(is_retry & RETRY_SOLVE_ERROR))
|
|
{
|
|
ctx.local_repodata_ttl = 2;
|
|
return install_specs(specs, create_env, solver_flag, is_retry | RETRY_SOLVE_ERROR);
|
|
}
|
|
if (freeze_installed)
|
|
Console::instance().print("Possible hints:\n - 'freeze_installed' is turned on\n");
|
|
|
|
if (ctx.json)
|
|
{
|
|
Console::instance().json_write(
|
|
{ { "success", false }, { "solver_problems", solver.all_problems() } });
|
|
}
|
|
|
|
Console::stream() << "The environment can't be solved, aborting the operation";
|
|
LOG_ERROR << "Could not solve for environment specs";
|
|
throw std::runtime_error("UnsatisfiableError");
|
|
}
|
|
|
|
MTransaction trans(solver, package_caches);
|
|
|
|
if (ctx.json)
|
|
{
|
|
trans.log_json();
|
|
}
|
|
|
|
Console::stream();
|
|
|
|
if (trans.prompt())
|
|
{
|
|
if (create_env && !Context::instance().dry_run)
|
|
detail::create_target_directory(ctx.target_prefix);
|
|
|
|
trans.execute(prefix_data);
|
|
|
|
for (auto other_spec : config.at("others_pkg_mgrs_specs")
|
|
.value<std::vector<detail::other_pkg_mgr_spec>>())
|
|
{
|
|
install_for_other_pkgmgr(other_spec);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
// TransactionFunc: (MPool& pool, MultiPackageCache& package_caches) -> MTransaction
|
|
template <typename TransactionFunc>
|
|
void install_explicit_with_transaction(TransactionFunc create_transaction, bool create_env)
|
|
{
|
|
MPool pool;
|
|
auto& ctx = Context::instance();
|
|
auto exp_prefix_data = PrefixData::create(ctx.target_prefix);
|
|
if (!exp_prefix_data)
|
|
{
|
|
// TODO: propagate tl::expected mechanism
|
|
throw std::runtime_error("could not load prefix data");
|
|
}
|
|
PrefixData& prefix_data = exp_prefix_data.value();
|
|
|
|
fs::path pkgs_dirs(Context::instance().root_prefix / "pkgs");
|
|
MultiPackageCache pkg_caches({ pkgs_dirs });
|
|
|
|
auto transaction = create_transaction(pool, pkg_caches);
|
|
|
|
prefix_data.add_packages(get_virtual_packages());
|
|
MRepo::create(pool, prefix_data);
|
|
|
|
if (ctx.json)
|
|
transaction.log_json();
|
|
|
|
if (transaction.prompt())
|
|
{
|
|
if (create_env && !Context::instance().dry_run)
|
|
detail::create_target_directory(ctx.target_prefix);
|
|
|
|
transaction.execute(prefix_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void install_explicit_specs(const std::vector<std::string>& specs, bool create_env)
|
|
{
|
|
detail::install_explicit_with_transaction(
|
|
[&](auto& pool, auto& pkg_caches)
|
|
{ return create_explicit_transaction_from_urls(pool, specs, pkg_caches); },
|
|
create_env);
|
|
}
|
|
|
|
void install_lockfile_specs(const fs::path& lockfile, bool create_env)
|
|
{
|
|
detail::install_explicit_with_transaction(
|
|
[&](auto& pool, auto& pkg_caches)
|
|
{ return create_explicit_transaction_from_lockfile(pool, lockfile, pkg_caches); },
|
|
create_env);
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
void create_empty_target(const fs::path& prefix)
|
|
{
|
|
detail::create_target_directory(prefix);
|
|
|
|
Console::instance().print(join(
|
|
"", std::vector<std::string>({ "Empty environment created at prefix: ", prefix })));
|
|
Console::instance().json_write({ { "success", true } });
|
|
}
|
|
|
|
void create_target_directory(const fs::path prefix)
|
|
{
|
|
path::touch(prefix / "conda-meta" / "history", true);
|
|
}
|
|
|
|
void file_specs_hook(std::vector<std::string>& file_specs)
|
|
{
|
|
auto& config = Configuration::instance();
|
|
auto& env_name = config.at("spec_file_env_name");
|
|
auto& specs = config.at("specs");
|
|
auto& others_pkg_mgrs_specs = config.at("others_pkg_mgrs_specs");
|
|
auto& channels = config.at("channels");
|
|
|
|
if (file_specs.size() == 0)
|
|
return;
|
|
|
|
for (const auto& file : file_specs)
|
|
{
|
|
if (is_yaml_file_name(file) && file_specs.size() != 1)
|
|
{
|
|
throw std::runtime_error("Can only handle 1 yaml file!");
|
|
}
|
|
}
|
|
|
|
for (auto& file : file_specs)
|
|
{
|
|
// read specs from file :)
|
|
if (is_env_lockfile_name(file))
|
|
{
|
|
const auto lockfile_path = fs::absolute(file);
|
|
LOG_DEBUG << "File spec Lockfile: " << lockfile_path.string();
|
|
Context::instance().env_lockfile = lockfile_path;
|
|
}
|
|
else if (is_yaml_file_name(file))
|
|
{
|
|
const auto parse_result = read_yaml_file(file);
|
|
|
|
if (parse_result.channels.size() != 0)
|
|
{
|
|
std::vector<std::string> updated_channels;
|
|
if (channels.cli_configured())
|
|
{
|
|
updated_channels = channels.cli_value<std::vector<std::string>>();
|
|
}
|
|
for (auto& c : parse_result.channels)
|
|
{
|
|
updated_channels.push_back(c);
|
|
}
|
|
channels.set_cli_value(updated_channels);
|
|
}
|
|
|
|
if (parse_result.name.size() != 0)
|
|
{
|
|
env_name.set_cli_yaml_value(parse_result.name);
|
|
}
|
|
|
|
if (parse_result.dependencies.size() != 0)
|
|
{
|
|
std::vector<std::string> updated_specs;
|
|
if (specs.cli_configured())
|
|
{
|
|
updated_specs = specs.cli_value<std::vector<std::string>>();
|
|
}
|
|
for (auto& s : parse_result.dependencies)
|
|
{
|
|
updated_specs.push_back(s);
|
|
}
|
|
specs.set_cli_value(updated_specs);
|
|
}
|
|
|
|
others_pkg_mgrs_specs.set_value(parse_result.others_pkg_mgrs_specs);
|
|
}
|
|
else
|
|
{
|
|
const std::vector<std::string> file_contents = read_lines(file);
|
|
if (file_contents.size() == 0)
|
|
{
|
|
throw std::runtime_error(concat("Got an empty file: ", file));
|
|
}
|
|
for (std::size_t i = 0; i < file_contents.size(); ++i)
|
|
{
|
|
auto& line = file_contents[i];
|
|
if (starts_with(line, "@EXPLICIT"))
|
|
{
|
|
// this is an explicit env
|
|
// we can check if the platform is correct with the previous line
|
|
std::string platform;
|
|
if (i >= 1)
|
|
{
|
|
for (std::size_t j = 0; j < i; ++j)
|
|
{
|
|
platform = file_contents[j];
|
|
if (starts_with(platform, "# platform: "))
|
|
{
|
|
platform = platform.substr(12);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
LOG_INFO << "Installing explicit specs for platform " << platform;
|
|
|
|
std::vector<std::string> explicit_specs;
|
|
for (auto f = file_contents.begin() + i + 1; f != file_contents.end();
|
|
++f)
|
|
{
|
|
std::string_view spec = strip((*f));
|
|
if (!spec.empty() && spec[0] != '#')
|
|
explicit_specs.push_back(*f);
|
|
}
|
|
|
|
specs.clear_values();
|
|
specs.set_value(explicit_specs);
|
|
config.at("explicit_install").set_value(true);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> f_specs;
|
|
for (auto& line : file_contents)
|
|
{
|
|
if (line[0] != '#' && line[0] != '@')
|
|
{
|
|
f_specs.push_back(line);
|
|
}
|
|
}
|
|
|
|
if (specs.cli_configured())
|
|
{
|
|
auto current_specs = specs.cli_value<std::vector<std::string>>();
|
|
current_specs.insert(current_specs.end(), f_specs.cbegin(), f_specs.cend());
|
|
specs.set_cli_value(current_specs);
|
|
}
|
|
else
|
|
{
|
|
if (!f_specs.empty())
|
|
{
|
|
specs.set_cli_value(f_specs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void channels_hook(std::vector<std::string>& channels)
|
|
{
|
|
auto& config = Configuration::instance();
|
|
auto& config_channels = config.at("channels");
|
|
std::vector<std::string> cli_channels;
|
|
|
|
if (config_channels.cli_configured())
|
|
{
|
|
cli_channels = config_channels.cli_value<std::vector<std::string>>();
|
|
auto it = find(cli_channels.begin(), cli_channels.end(), "nodefaults");
|
|
if (it != cli_channels.end())
|
|
{
|
|
cli_channels.erase(it);
|
|
channels = cli_channels;
|
|
}
|
|
}
|
|
}
|
|
} // detail
|
|
} // mamba
|