Simplify MPool Interface (#3177)

* Remove some use to MPool::ChannelContext

* Explicit palette in UnSolvable

* Explicit option for use_only_tar_bz2 in Pool

* Remove context from Pool

* Remove Context capture in Pool

* Remove ChannelContext capture in Pool

* Silence warnings

* Chane Unsolvable to use solv::ObjPool

* Fine grain control of MPoll raw access

* Remove MPool::id2pkginfo

* Remove MPool::matchspec2id and select_solvables

* Remove MPool::dep2str

* Auto create_whatprovides in repoquery

* Remove MPool::create_whatprovides

* Change MPool::remove_repo API

* Private libsolv
This commit is contained in:
Antoine Prouvost 2024-02-07 09:25:37 -05:00 committed by GitHub
parent b3cb456764
commit a2a2d26445
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 384 additions and 270 deletions

View File

@ -446,13 +446,13 @@ macro(libmamba_create_target target_name linkage output_name)
target_link_libraries(
${target_name}
PUBLIC
fmt::fmt-header-only
spdlog::spdlog_header_only
yaml-cpp::yaml-cpp
PUBLIC fmt::fmt-header-only spdlog::spdlog_header_only yaml-cpp::yaml-cpp
PRIVATE
reproc
reproc++
simdjson::simdjson_static
solv::libsolv_static
solv::libsolvext_static
PRIVATE reproc reproc++ simdjson::simdjson_static
)
if(UNIX)
@ -589,8 +589,6 @@ macro(libmamba_create_target target_name linkage output_name)
# only version to avoid chasing after the correct fmt version mathching the one used
# in the bundle
spdlog::spdlog_header_only
solv::libsolv
solv::libsolvext
PRIVATE
${LibArchive_LIBRARIES}
${CURL_LIBRARIES}
@ -600,6 +598,8 @@ macro(libmamba_create_target target_name linkage output_name)
reproc++
simdjson::simdjson
zstd::libzstd_shared
solv::libsolv
solv::libsolvext
)
endif()
@ -749,8 +749,6 @@ install(
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
DESTINATION ${LIBMAMBA_CMAKECONFIG_INSTALL_DIR}
)
# Need to install the FindLibsolv for the installed target to work
install(FILES "../cmake/modules/FindLibsolv.cmake" DESTINATION ${LIBMAMBA_CMAKECONFIG_INSTALL_DIR})
install(
EXPORT ${PROJECT_NAME}Targets

View File

@ -12,10 +12,15 @@
namespace mamba
{
class Context;
class ChannelContext;
class MPool;
class MultiPackageCache;
expected_t<void, mamba_aggregated_error>
load_channels(Context& ctx, MPool& pool, MultiPackageCache& package_caches);
auto load_channels( //
Context& ctx,
ChannelContext& channel_context,
MPool& pool,
MultiPackageCache& package_caches
) -> expected_t<void, mamba_aggregated_error>;
}
#endif

View File

@ -11,7 +11,6 @@
namespace mamba
{
struct Palette
{
/** Something that is possible or exsists. */
@ -45,21 +44,21 @@ namespace mamba
fmt::text_style progress_bar_extracted;
/** A Palette with no colors at all. */
static constexpr Palette no_color();
static constexpr auto no_color() -> Palette;
/** A Palette with terminal 4 bit colors. */
static constexpr Palette terminal();
static constexpr auto terminal() -> Palette;
};
/*******************************
* Implementation of Palette *
*******************************/
inline constexpr Palette Palette::no_color()
inline constexpr auto Palette::no_color() -> Palette
{
return {};
}
inline constexpr Palette Palette::terminal()
inline constexpr auto Palette::terminal() -> Palette
{
return {
/* .success= */ fmt::fg(fmt::terminal_color::green),

View File

@ -7,20 +7,19 @@
#ifndef MAMBA_CORE_POOL_HPP
#define MAMBA_CORE_POOL_HPP
#include <functional>
#include <memory>
#include <optional>
#include <solv/pooltypes.h>
#include "mamba/core/error_handling.hpp"
#include "mamba/solver/libsolv/parameters.hpp"
#include "mamba/solver/libsolv/repo_info.hpp"
#include "mamba/specs/channel.hpp"
#include "mamba/specs/package_info.hpp"
#include "mamba/util/loop_control.hpp"
namespace mamba
{
class ChannelContext;
class Context;
class PrefixData;
class SubdirData;
@ -40,6 +39,12 @@ namespace mamba
class MatchSpec;
}
namespace solver::libsolv
{
class Solver;
class UnSolvable;
}
/**
* Pool of solvable involved in resolving en environment.
*
@ -52,26 +57,20 @@ namespace mamba
{
public:
MPool(Context& ctx, ChannelContext& channel_context);
using logger_type = std::function<void(solver::libsolv::LogLevel, std::string_view)>;
MPool(specs::ChannelResolveParams channel_params);
~MPool();
void set_debuglevel();
void create_whatprovides();
[[nodiscard]] auto channel_params() const -> const specs::ChannelResolveParams&;
std::vector<Id> select_solvables(Id id, bool sorted = false) const;
Id matchspec2id(const specs::MatchSpec& ms);
std::optional<specs::PackageInfo> id2pkginfo(Id solv_id) const;
std::optional<std::string> dep2str(Id dep_id) const;
// TODO: (TMP) This is not meant to be public but is needed for a transition period
solv::ObjPool& pool();
const solv::ObjPool& pool() const;
void set_logger(logger_type callback);
auto add_repo_from_repodata_json(
const fs::u8path& path,
std::string_view url,
solver::libsolv::PipAsPythonDependency add = solver::libsolv::PipAsPythonDependency::No,
solver::libsolv::UseOnlyTarBz2 only_tar = solver::libsolv::UseOnlyTarBz2::No,
solver::libsolv::RepodataParser parser = solver::libsolv::RepodataParser::Mamba
) -> expected_t<solver::libsolv::RepoInfo>;
@ -109,7 +108,7 @@ namespace mamba
void
set_repo_priority(solver::libsolv::RepoInfo repo, solver::libsolv::Priorities priorities);
void remove_repo(::Id repo_id, bool reuse_ids);
void remove_repo(solver::libsolv::RepoInfo repo);
template <typename Func>
void for_each_package_in_repo(solver::libsolv::RepoInfo repo, Func&&) const;
@ -120,14 +119,20 @@ namespace mamba
template <typename Func>
void for_each_package_depending_on(const specs::MatchSpec& ms, Func&&);
ChannelContext& channel_context() const;
const Context& context() const;
/** A wrapper struct to fine-grain controll who can access the raw repr of the Pool. */
class Impl
{
[[nodiscard]] static auto get(MPool& pool) -> solv::ObjPool&;
[[nodiscard]] static auto get(const MPool& pool) -> const solv::ObjPool&;
friend class solver::libsolv::Solver;
friend class solver::libsolv::UnSolvable;
};
private:
struct MPoolData;
/**
* Make MPool behave like a shared_ptr (with move and copy).
*
@ -142,6 +147,10 @@ namespace mamba
*/
std::shared_ptr<MPoolData> m_data;
friend class Impl;
auto pool() -> solv::ObjPool&;
[[nodiscard]] auto pool() const -> const solv::ObjPool&;
auto add_repo_from_packages_impl_pre(std::string_view name) -> solver::libsolv::RepoInfo;
void add_repo_from_packages_impl_loop(
const solver::libsolv::RepoInfo& repo,
@ -167,6 +176,8 @@ namespace mamba
};
// TODO machinery functions in separate files
void add_spdlog_logger_to_pool(MPool& pool);
auto load_subdir_in_pool(const Context& ctx, MPool& pool, const SubdirData& subdir)
-> expected_t<solver::libsolv::RepoInfo>;

View File

@ -25,6 +25,20 @@ namespace mamba::solver::libsolv
Yes = true,
};
enum class UseOnlyTarBz2
{
No = false,
Yes = true,
};
enum class LogLevel
{
Debug,
Warning,
Error,
Fatal,
};
struct Priorities
{
using value_type = int;

View File

@ -17,6 +17,7 @@
namespace mamba
{
class MPool;
class Palette;
namespace solv
{
@ -46,8 +47,13 @@ namespace mamba::solver::libsolv
[[nodiscard]] auto problems_graph(const MPool& pool) const -> ProblemsGraph;
auto explain_problems_to(MPool& pool, std::ostream& out) const -> std::ostream&;
[[nodiscard]] auto explain_problems(MPool& pool) const -> std::string;
auto explain_problems_to( //
MPool& pool,
std::ostream& out,
const Palette& palette
) const -> std::ostream&;
[[nodiscard]] auto explain_problems(MPool& pool, const Palette& palette) const -> std::string;
private:

View File

@ -28,7 +28,6 @@ find_dependency(tl-expected)
find_dependency(nlohmann_json)
find_dependency(yaml-cpp)
find_dependency(reproc++)
find_dependency(Libsolv MODULE)
if(NOT (TARGET libmamba-dyn OR TARGET libmamba-static))
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")

View File

@ -11,7 +11,6 @@
#include "mamba/core/pool.hpp"
#include "mamba/core/prefix_data.hpp"
#include "mamba/core/subdirdata.hpp"
#include "mamba/download/downloader.hpp"
#include "mamba/solver/libsolv/repo_info.hpp"
#include "mamba/specs/package_info.hpp"
@ -19,15 +18,19 @@ namespace mamba
{
namespace
{
solver::libsolv::RepoInfo
create_repo_from_pkgs_dir(const Context& ctx, MPool& pool, const fs::u8path& pkgs_dir)
auto create_repo_from_pkgs_dir(
const Context& ctx,
ChannelContext& channel_context,
MPool& pool,
const fs::u8path& pkgs_dir
) -> solver::libsolv::RepoInfo
{
if (!fs::exists(pkgs_dir))
{
// TODO : us tl::expected mechanis
throw std::runtime_error("Specified pkgs_dir does not exist\n");
}
auto sprefix_data = PrefixData::create(pkgs_dir, pool.channel_context());
auto sprefix_data = PrefixData::create(pkgs_dir, channel_context);
if (!sprefix_data)
{
throw std::runtime_error("Specified pkgs_dir does not exist\n");
@ -106,8 +109,13 @@ namespace mamba
}
}
expected_t<void, mamba_aggregated_error>
load_channels_impl(Context& ctx, MPool& pool, MultiPackageCache& package_caches, bool is_retry)
auto load_channels_impl(
Context& ctx,
ChannelContext& channel_context,
MPool& pool,
MultiPackageCache& package_caches,
bool is_retry
) -> expected_t<void, mamba_aggregated_error>
{
std::vector<SubdirData> subdirs;
@ -121,12 +129,12 @@ namespace mamba
for (const auto& mirror : ctx.mirrored_channels)
{
for (auto channel : pool.channel_context().make_channel(mirror.first, mirror.second))
for (auto channel : channel_context.make_channel(mirror.first, mirror.second))
{
create_mirrors(channel, ctx.mirrors);
create_subdirs(
ctx,
pool.channel_context(),
channel_context,
channel,
package_caches,
subdirs,
@ -145,7 +153,7 @@ namespace mamba
// TODO: C++20, replace with contains
if (ctx.mirrored_channels.find(location) == ctx.mirrored_channels.end())
{
for (auto channel : pool.channel_context().make_channel(location))
for (auto channel : channel_context.make_channel(location))
{
if (channel.is_package())
{
@ -157,7 +165,7 @@ namespace mamba
create_mirrors(channel, ctx.mirrors);
create_subdirs(
ctx,
pool.channel_context(),
channel_context,
channel,
package_caches,
subdirs,
@ -203,7 +211,7 @@ namespace mamba
LOG_INFO << "Creating repo from pkgs_dir for offline";
for (const auto& c : ctx.pkgs_dirs)
{
create_repo_from_pkgs_dir(ctx, pool, c);
create_repo_from_pkgs_dir(ctx, channel_context, pool, c);
}
}
std::string prev_channel;
@ -255,12 +263,12 @@ namespace mamba
if (!ctx.offline && !is_retry)
{
LOG_WARNING << "Encountered malformed repodata.json cache. Redownloading.";
return load_channels_impl(ctx, pool, package_caches, true);
return load_channels_impl(ctx, channel_context, pool, package_caches, true);
}
error_list.push_back(mamba_error(
error_list.emplace_back(
"Could not load repodata. Cache corrupted?",
mamba_error_code::repodata_not_loaded
));
);
}
using return_type = expected_t<void, mamba_aggregated_error>;
return error_list.empty() ? return_type()
@ -268,9 +276,10 @@ namespace mamba
}
}
expected_t<void, mamba_aggregated_error>
load_channels(Context& ctx, MPool& pool, MultiPackageCache& package_caches)
auto
load_channels(Context& ctx, ChannelContext& channel_context, MPool& pool, MultiPackageCache& package_caches)
-> expected_t<void, mamba_aggregated_error>
{
return load_channels_impl(ctx, pool, package_caches, false);
return load_channels_impl(ctx, channel_context, pool, package_caches, false);
}
}

View File

@ -530,7 +530,8 @@ namespace mamba
LOG_WARNING << "No 'channels' specified";
}
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(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)
@ -538,16 +539,13 @@ namespace mamba
PrefixData::create(ctx.prefix_params.target_prefix); } ) .map_error([](const
mamba_error& err) { throw std::runtime_error(err.what());
});*/
auto exp_load = load_channels(ctx, pool, package_caches);
auto exp_load = load_channels(ctx, channel_context, pool, package_caches);
if (!exp_load)
{
throw std::runtime_error(exp_load.error().what());
}
auto exp_prefix_data = PrefixData::create(
ctx.prefix_params.target_prefix,
pool.channel_context()
);
auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context);
if (!exp_prefix_data)
{
throw std::runtime_error(exp_prefix_data.error().what());
@ -571,7 +569,7 @@ namespace mamba
if (auto* unsolvable = std::get_if<solver::libsolv::UnSolvable>(&outcome))
{
unsolvable->explain_problems_to(pool, LOG_ERROR);
unsolvable->explain_problems_to(pool, LOG_ERROR, ctx.graphics_params.palette);
if (retry_clean_cache && !is_retry)
{
ctx.local_repodata_ttl = 2;
@ -686,7 +684,9 @@ namespace mamba
bool remove_prefix_on_failure
)
{
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
auto exp_prefix_data = PrefixData::create(ctx.prefix_params.target_prefix, channel_context);
if (!exp_prefix_data)
{

View File

@ -127,8 +127,8 @@ namespace mamba
}
PrefixData& prefix_data = exp_prefix_data.value();
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
load_installed_packages_in_pool(ctx, pool, prefix_data);
const fs::u8path pkgs_dirs(ctx.prefix_params.root_prefix / "pkgs");

View File

@ -6,8 +6,6 @@
#include <iostream>
#include <solv/solver.h>
#include "mamba/api/channel_loader.hpp"
#include "mamba/api/configuration.hpp"
#include "mamba/api/repoquery.hpp"
@ -30,7 +28,8 @@ namespace mamba
config.load();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
// bool installed = (type == QueryType::kDepends) || (type == QueryType::kWhoneeds);
MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params);
@ -66,13 +65,12 @@ namespace mamba
{
Console::stream() << "Getting repodata from channels..." << std::endl;
}
auto exp_load = load_channels(ctx, pool, package_caches);
auto exp_load = load_channels(ctx, channel_context, pool, package_caches);
if (!exp_load)
{
throw std::runtime_error(exp_load.error().what());
}
}
pool.create_whatprovides();
return pool;
}
}

View File

@ -106,10 +106,12 @@ namespace mamba
}
}
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params);
auto exp_loaded = load_channels(ctx, pool, package_caches);
auto exp_loaded = load_channels(ctx, channel_context, pool, package_caches);
if (!exp_loaded)
{
throw std::runtime_error(exp_loaded.error().what());

View File

@ -4,6 +4,9 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <exception>
#include <iostream>
#include <limits>
#include <string_view>
#include <fmt/format.h>
@ -33,138 +36,100 @@ namespace mamba
{
struct MPool::MPoolData
{
MPoolData(Context& ctx, ChannelContext& cc)
: context(ctx)
, channel_context(cc)
MPoolData(specs::ChannelResolveParams p_channel_params)
: channel_params(std::move(p_channel_params))
{
}
specs::ChannelResolveParams channel_params;
solv::ObjPool pool = {};
Context& context;
ChannelContext& channel_context;
};
MPool::MPool(Context& ctx, ChannelContext& channel_context)
: m_data(std::make_shared<MPoolData>(ctx, channel_context))
MPool::MPool(specs::ChannelResolveParams channel_params)
: m_data(std::make_unique<MPoolData>(std::move(channel_params)))
{
pool().set_disttype(DISTTYPE_CONDA);
set_debuglevel();
// Ensure that debug logging never goes to stdout as to not interfere json output
pool().raw()->debugmask |= SOLV_DEBUG_TO_STDERR;
::pool_setdebuglevel(pool().raw(), -1); // Off
}
MPool::~MPool() = default;
ChannelContext& MPool::channel_context() const
{
return m_data->channel_context;
}
const Context& MPool::context() const
{
return m_data->context;
}
solv::ObjPool& MPool::pool()
auto MPool::pool() -> solv::ObjPool&
{
return m_data->pool;
}
const solv::ObjPool& MPool::pool() const
auto MPool::pool() const -> const solv::ObjPool&
{
return m_data->pool;
}
void MPool::set_debuglevel()
auto MPool::Impl::get(MPool& pool) -> solv::ObjPool&
{
// ensure that debug logging goes to stderr as to not interfere with stdout json output
pool().raw()->debugmask |= SOLV_DEBUG_TO_STDERR;
const auto& ctx = context();
if (ctx.output_params.verbosity > 2)
return pool.pool();
}
auto MPool::Impl::get(const MPool& pool) -> const solv::ObjPool&
{
return pool.pool();
}
auto MPool::channel_params() const -> const specs::ChannelResolveParams&
{
return m_data->channel_params;
}
namespace
{
auto libsolv_to_log_level(int type) -> solver::libsolv::LogLevel
{
pool_setdebuglevel(pool().raw(), ctx.output_params.verbosity - 1);
pool().set_debug_callback(
[logger = spdlog::get("libsolv"), &ctx](::Pool*, int type, std::string_view msg) noexcept
if (type & SOLV_FATAL)
{
return solver::libsolv::LogLevel::Fatal;
}
if (type & SOLV_ERROR)
{
return solver::libsolv::LogLevel::Error;
}
if (type & SOLV_WARN)
{
return solver::libsolv::LogLevel::Warning;
}
return solver::libsolv::LogLevel::Debug;
}
}
void MPool::set_logger(logger_type callback)
{
::pool_setdebuglevel(pool().raw(), std::numeric_limits<int>::max()); // All
pool().set_debug_callback(
[logger = std::move(callback)](::Pool*, int type, std::string_view msg) noexcept
{
try
{
if (msg.size() == 0 || msg.back() != '\n')
{
return;
}
auto log = Console::hide_secrets(msg);
if (type & SOLV_FATAL || type & SOLV_ERROR)
{
logger->error(log);
}
else if (type & SOLV_WARN)
{
logger->warn(log);
}
else if (ctx.output_params.verbosity > 2)
{
logger->info(log);
}
logger(libsolv_to_log_level(type), msg);
}
);
}
}
void MPool::create_whatprovides()
{
pool().create_whatprovides();
}
std::vector<Id> MPool::select_solvables(Id matchspec, bool sorted) const
{
auto solvables = pool().select_solvables({ SOLVER_SOLVABLE_PROVIDES, matchspec });
if (sorted)
{
std::sort(
solvables.begin(),
solvables.end(),
[pool_ptr = pool().raw()](Id a, Id b)
catch (std::exception const& e)
{
Solvable* sa = pool_id2solvable(pool_ptr, a);
Solvable* sb = pool_id2solvable(pool_ptr, b);
return (pool_evrcmp(pool_ptr, sa->evr, sb->evr, EVRCMP_COMPARE) > 0);
std::cerr << "Developer error: error in libsolv logging function: \n"
<< e.what();
}
);
}
return solvables.as<std::vector>();
}
auto MPool::matchspec2id(const specs::MatchSpec& ms) -> ::Id
{
return solver::libsolv::pool_add_matchspec(pool(), ms, channel_context().params())
.or_else([](mamba_error&& error) { throw std::move(error); })
.value_or(0);
}
std::optional<specs::PackageInfo> MPool::id2pkginfo(Id solv_id) const
{
if (const auto solv = pool().get_solvable(solv_id))
{
return { solver::libsolv::make_package_info(pool(), solv.value()) };
}
return std::nullopt;
}
std::optional<std::string> MPool::dep2str(Id dep_id) const
{
if (!dep_id)
{
return std::nullopt;
}
// Not const because might alloctmp space
return pool_dep2str(const_cast<::Pool*>(pool().raw()), dep_id);
}
);
}
auto MPool::add_repo_from_repodata_json(
const fs::u8path& path,
std::string_view url,
solver::libsolv::PipAsPythonDependency add,
solver::libsolv::UseOnlyTarBz2 only_tar,
solver::libsolv::RepodataParser parser
) -> expected_t<solver::libsolv::RepoInfo>
{
const auto use_only_tar_bz2 = static_cast<bool>(only_tar);
if (!fs::exists(path))
{
return make_unexpected(
@ -184,29 +149,29 @@ namespace mamba
repo,
path,
std::string(url),
context().use_only_tar_bz2
use_only_tar_bz2
);
}
return solver::libsolv::libsolv_read_json(repo, path, context().use_only_tar_bz2)
return solver::libsolv::libsolv_read_json(repo, path, use_only_tar_bz2)
.transform(
[&url](solv::ObjRepoView repo)
[&url](solv::ObjRepoView p_repo)
{
solver::libsolv::set_solvables_url(repo, std::string(url));
return repo;
solver::libsolv::set_solvables_url(p_repo, std::string(url));
return p_repo;
}
);
};
return make_repo()
.transform(
[&](solv::ObjRepoView repo) -> solver::libsolv::RepoInfo
[&](solv::ObjRepoView p_repo) -> solver::libsolv::RepoInfo
{
if (add == solver::libsolv::PipAsPythonDependency::Yes)
{
solver::libsolv::add_pip_as_python_dependency(pool(), repo);
solver::libsolv::add_pip_as_python_dependency(pool(), p_repo);
}
repo.internalize();
return solver::libsolv::RepoInfo{ repo.raw() };
p_repo.internalize();
return solver::libsolv::RepoInfo{ p_repo.raw() };
}
)
.or_else([&](const auto&) { pool().remove_repo(repo.id(), /* reuse_ids= */ true); });
@ -222,16 +187,16 @@ namespace mamba
return solver::libsolv::read_solv(pool(), repo, path, expected, static_cast<bool>(add))
.transform(
[&](solv::ObjRepoView repo) -> solver::libsolv::RepoInfo
[&](solv::ObjRepoView p_repo) -> solver::libsolv::RepoInfo
{
repo.set_url(expected.url);
solver::libsolv::set_solvables_url(repo, expected.url);
p_repo.set_url(expected.url);
solver::libsolv::set_solvables_url(p_repo, expected.url);
if (add == solver::libsolv::PipAsPythonDependency::Yes)
{
solver::libsolv::add_pip_as_python_dependency(pool(), repo);
solver::libsolv::add_pip_as_python_dependency(pool(), p_repo);
}
repo.internalize();
return solver::libsolv::RepoInfo(repo.raw());
p_repo.internalize();
return solver::libsolv::RepoInfo(p_repo.raw());
}
)
.or_else([&](const auto&) { pool().remove_repo(repo.id(), /* reuse_ids= */ true); });
@ -279,12 +244,13 @@ namespace mamba
{
assert(repo.m_ptr != nullptr);
return solver::libsolv::write_solv(solv::ObjRepoView(*repo.m_ptr), path, metadata)
.transform([](solv::ObjRepoView repo) { return solver::libsolv::RepoInfo(repo.raw()); });
.transform([](solv::ObjRepoView solv_repo)
{ return solver::libsolv::RepoInfo(solv_repo.raw()); });
}
void MPool::remove_repo(::Id repo_id, bool reuse_ids)
void MPool::remove_repo(solver::libsolv::RepoInfo repo)
{
pool().remove_repo(repo_id, reuse_ids);
pool().remove_repo(repo.id(), /* reuse_ids= */ true);
}
auto MPool::installed_repo() const -> std::optional<solver::libsolv::RepoInfo>
@ -316,8 +282,9 @@ namespace mamba
auto MPool::package_id_to_package_info(PackageId id) const -> specs::PackageInfo
{
static_assert(std::is_same_v<std::underlying_type_t<PackageId>, solv::SolvableId>);
// Safe because the ID is coming from libsolv
return id2pkginfo(static_cast<solv::SolvableId>(id)).value();
const auto solv = pool().get_solvable(static_cast<solv::SolvableId>(id));
assert(solv.has_value()); // Safe because the ID is coming from libsolv
return { solver::libsolv::make_package_info(pool(), solv.value()) };
}
auto MPool::packages_in_repo(solver::libsolv::RepoInfo repo) const -> std::vector<PackageId>
@ -330,12 +297,26 @@ namespace mamba
return out;
}
namespace
{
auto matchspec2id(
solv::ObjPool& pool,
const specs::ChannelResolveParams& channel_params,
const specs::MatchSpec& ms
) -> solv::DependencyId
{
return solver::libsolv::pool_add_matchspec(pool, ms, channel_params)
.or_else([](mamba_error&& error) { throw std::move(error); })
.value_or(0);
}
}
auto MPool::packages_matching_ids(const specs::MatchSpec& ms) -> std::vector<PackageId>
{
static_assert(std::is_same_v<std::underlying_type_t<PackageId>, solv::SolvableId>);
pool().ensure_whatprovides();
const auto ms_id = matchspec2id(ms);
const auto ms_id = matchspec2id(pool(), channel_params(), ms);
auto solvables = pool().select_solvables({ SOLVER_SOLVABLE_PROVIDES, ms_id });
auto out = std::vector<PackageId>(solvables.size());
std::transform(
@ -352,7 +333,7 @@ namespace mamba
static_assert(std::is_same_v<std::underlying_type_t<PackageId>, solv::SolvableId>);
pool().ensure_whatprovides();
const auto ms_id = matchspec2id(ms);
const auto ms_id = matchspec2id(pool(), channel_params(), ms);
auto solvables = pool().what_matches_dep(SOLVABLE_REQUIRES, ms_id);
auto out = std::vector<PackageId>(solvables.size());
std::transform(
@ -365,6 +346,31 @@ namespace mamba
}
// TODO machinery functions in separate files
void add_spdlog_logger_to_pool(MPool& pool)
{
pool.set_logger(
[logger = spdlog::get("libsolv")](solver::libsolv::LogLevel level, std::string_view msg)
{
switch (level)
{
case (solver::libsolv::LogLevel::Fatal):
logger->critical(msg);
break;
case (solver::libsolv::LogLevel::Error):
logger->error(msg);
break;
case (solver::libsolv::LogLevel::Warning):
logger->warn(msg);
break;
case (solver::libsolv::LogLevel::Debug):
logger->debug(msg);
break;
}
}
);
}
auto load_subdir_in_pool(const Context& ctx, MPool& pool, const SubdirData& subdir)
-> expected_t<solver::libsolv::RepoInfo>
{
@ -408,6 +414,7 @@ namespace mamba
repodata_json,
util::rsplit(subdir.metadata().url(), "/", 1).front(),
add_pip,
static_cast<solver::libsolv::UseOnlyTarBz2>(ctx.use_only_tar_bz2),
json_parser
);
}

View File

@ -6,18 +6,14 @@
#include <cassert>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <solv/conda.h>
#include <solv/pool.h>
#include <solv/poolid.h>
#include <solv/pooltypes.h>
#include <solv/repo.h>
#include <solv/selection.h>
extern "C" // Incomplete header
{
#include <solv/conda.h>
}
#include "solv-cpp/pool.hpp"

View File

@ -622,7 +622,7 @@ namespace mamba::solver::libsolv
}
// conda_build_form does **NOT** contain the channel info
const solv::DependencyId match = pool_conda_matchspec(
const solv::DependencyId match_id = pool_conda_matchspec(
pool.raw(),
ms.conda_build_form().c_str()
);
@ -632,7 +632,7 @@ namespace mamba::solver::libsolv
solv::ObjQueue selected_pkgs = {};
auto other_subdir_match = std::string();
pool.for_each_whatprovides(
match,
match_id,
[&](solv::ObjSolvableViewConst s)
{
if (s.installed())
@ -1021,7 +1021,7 @@ namespace mamba::solver::libsolv
auto installed_python(const solv::ObjPool& pool) -> std::optional<solv::ObjSolvableViewConst>
{
auto py_id = solv::SolvableId(0);
auto py_id = solv::SolvableId{ 0 };
pool.for_each_installed_solvable(
[&](solv::ObjSolvableViewConst s)
{

View File

@ -53,8 +53,8 @@ namespace mamba::solver::libsolv
auto Solver::solve_impl(MPool& mpool, const Request& request) -> expected_t<Outcome>
{
auto& pool = mpool.pool();
const auto& chan_params = mpool.channel_context().params();
auto& pool = MPool::Impl::get(mpool);
const auto& chan_params = mpool.channel_params();
const auto& flags = request.flags;
return solver::libsolv::request_to_decision_queue(request, pool, chan_params, flags.force_reinstall)

View File

@ -9,9 +9,10 @@
#include <vector>
#include <solv/problems.h>
#include <solv/solver.h>
#include "mamba/core/context.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/palette.hpp"
#include "mamba/core/pool.hpp"
#include "mamba/solver/libsolv/unsolvable.hpp"
#include "mamba/specs/match_spec.hpp"
@ -19,6 +20,8 @@
#include "solv-cpp/pool.hpp"
#include "solv-cpp/solver.hpp"
#include "solver/libsolv/helpers.hpp"
namespace mamba::solver::libsolv
{
UnSolvable::UnSolvable(std::unique_ptr<solv::ObjSolver>&& solver)
@ -38,37 +41,39 @@ namespace mamba::solver::libsolv
return *m_solver;
}
auto UnSolvable::problems(MPool& pool) const -> std::vector<std::string>
auto UnSolvable::problems(MPool& mpool) const -> std::vector<std::string>
{
auto& pool = MPool::Impl::get(mpool);
std::vector<std::string> problems;
solver().for_each_problem_id(
[&](solv::ProblemId pb)
{ problems.emplace_back(solver().problem_to_string(pool.pool(), pb)); }
solver().for_each_problem_id([&](solv::ProblemId pb)
{ problems.emplace_back(solver().problem_to_string(pool, pb)); }
);
return problems;
}
auto UnSolvable::problems_to_str(MPool& pool) const -> std::string
auto UnSolvable::problems_to_str(MPool& mpool) const -> std::string
{
auto& pool = MPool::Impl::get(mpool);
std::stringstream problems;
problems << "Encountered problems while solving:\n";
solver().for_each_problem_id(
[&](solv::ProblemId pb)
{ problems << " - " << solver().problem_to_string(pool.pool(), pb) << "\n"; }
{ problems << " - " << solver().problem_to_string(pool, pb) << "\n"; }
);
return problems.str();
}
auto UnSolvable::all_problems_to_str(MPool& pool) const -> std::string
auto UnSolvable::all_problems_to_str(MPool& mpool) const -> std::string
{
auto& pool = MPool::Impl::get(mpool);
std::stringstream problems;
solver().for_each_problem_id(
[&](solv::ProblemId pb)
{
for (solv::RuleId const rule : solver().problem_rules(pb))
{
auto const info = solver().get_rule_info(pool.pool(), rule);
problems << " - " << solver().rule_info_to_string(pool.pool(), info) << "\n";
auto const info = solver().get_rule_info(pool, rule);
problems << " - " << solver().rule_info_to_string(pool, info) << "\n";
}
}
);
@ -77,6 +82,28 @@ namespace mamba::solver::libsolv
namespace
{
auto pool_id_to_package_info( //
const solv::ObjPool& pool,
solv::SolvableId id
) -> std::optional<specs::PackageInfo>
{
if (const auto solv = pool.get_solvable(id))
{
return { solver::libsolv::make_package_info(pool, solv.value()) };
}
return std::nullopt;
}
auto pool_dependency_to_string(const solv::ObjPool& pool, solv::SolvableId id)
-> std::optional<std::string>
{
if (!id)
{
return std::nullopt;
}
return { pool.dependency_to_string(id) };
}
struct SolverProblem
{
SolverRuleinfo type;
@ -89,14 +116,13 @@ namespace mamba::solver::libsolv
std::string description;
};
// TODO change MSolver problem
auto make_solver_problem(
const solv::ObjSolver& solver,
const MPool& pool,
SolverRuleinfo type,
Id source_id,
Id target_id,
Id dep_id
const solv::ObjPool& pool,
::SolverRuleinfo type,
solv::SolvableId source_id,
solv::SolvableId target_id,
solv::DependencyId dep_id
) -> SolverProblem
{
return {
@ -104,9 +130,9 @@ namespace mamba::solver::libsolv
/* .source_id= */ source_id,
/* .target_id= */ target_id,
/* .dep_id= */ dep_id,
/* .source= */ pool.id2pkginfo(source_id),
/* .target= */ pool.id2pkginfo(target_id),
/* .dep= */ pool.dep2str(dep_id),
/* .source= */ pool_id_to_package_info(pool, source_id),
/* .target= */ pool_id_to_package_info(pool, target_id),
/* .dep= */ pool_dependency_to_string(pool, dep_id),
/* .description= */
::solver_problemruleinfo2str(
const_cast<::Solver*>(solver.raw()), // Not const because might alloctmp space
@ -118,7 +144,7 @@ namespace mamba::solver::libsolv
};
}
auto all_problems_structured(const MPool& pool, const solv::ObjSolver& solver)
auto all_problems_structured(const solv::ObjPool& pool, const solv::ObjSolver& solver)
-> std::vector<SolverProblem>
{
std::vector<SolverProblem> res = {};
@ -128,7 +154,7 @@ namespace mamba::solver::libsolv
{
for (solv::RuleId const rule : solver.problem_rules(pb))
{
auto info = solver.get_rule_info(pool.pool(), rule);
auto info = solver.get_rule_info(pool, rule);
res.push_back(make_solver_problem(
/* solver= */ solver,
/* pool= */ pool,
@ -165,14 +191,14 @@ namespace mamba::solver::libsolv
using edge_t = ProblemsGraph::edge_t;
using conflicts_t = ProblemsGraph::conflicts_t;
ProblemsGraphCreator(const MPool& pool, const solv::ObjSolver& solver);
ProblemsGraphCreator(const solv::ObjPool& pool, const solv::ObjSolver& solver);
auto problem_graph() && -> ProblemsGraph;
private:
const solv::ObjSolver& m_solver;
const MPool& m_pool;
const solv::ObjPool& m_pool;
graph_t m_graph;
conflicts_t m_conflicts;
std::map<solv::SolvableId, node_id> m_solv2node;
@ -201,7 +227,7 @@ namespace mamba::solver::libsolv
void parse_problems();
};
ProblemsGraphCreator::ProblemsGraphCreator(const MPool& pool, const solv::ObjSolver& solver)
ProblemsGraphCreator::ProblemsGraphCreator(const solv::ObjPool& pool, const solv::ObjSolver& solver)
: m_solver{ solver }
, m_pool{ pool }
{
@ -246,10 +272,10 @@ namespace mamba::solver::libsolv
) -> bool
{
bool added = false;
for (const auto& solv_id : m_pool.select_solvables(dep_id))
for (const auto& solv_id : m_pool.select_solvables({ SOLVER_SOLVABLE_PROVIDES, dep_id }))
{
added = true;
auto pkg_info = m_pool.id2pkginfo(solv_id);
auto pkg_info = pool_id_to_package_info(m_pool, solv_id);
assert(pkg_info.has_value());
node_id to_id = add_solvable(solv_id, PackageNode{ std::move(pkg_info).value() }, false);
m_graph.add_edge(from_id, to_id, edge);
@ -420,8 +446,8 @@ namespace mamba::solver::libsolv
// dependency.
auto edge = specs::MatchSpec::parse(source.value().name);
// The package cannot exist without its name in the pool
assert(m_pool.pool().find_string(edge.name().str()).has_value());
const auto dep_id = m_pool.pool().find_string(edge.name().str()).value();
assert(m_pool.find_string(edge.name().str()).has_value());
const auto dep_id = m_pool.find_string(edge.name().str()).value();
const bool added = add_expanded_deps_edges(m_root_node, dep_id, edge);
if (!added)
{
@ -444,12 +470,12 @@ namespace mamba::solver::libsolv
auto UnSolvable::problems_graph(const MPool& pool) const -> ProblemsGraph
{
assert(m_solver != nullptr);
return ProblemsGraphCreator(pool, *m_solver).problem_graph();
return ProblemsGraphCreator(MPool::Impl::get(pool), *m_solver).problem_graph();
}
auto UnSolvable::explain_problems_to(MPool& pool, std::ostream& out) const -> std::ostream&
auto UnSolvable::explain_problems_to(MPool& pool, std::ostream& out, const Palette& palette) const
-> std::ostream&
{
const auto& ctx = pool.context();
out << "Could not solve for environment specs\n";
const auto pbs = problems_graph(pool);
const auto pbs_simplified = simplify_conflicts(pbs);
@ -457,16 +483,19 @@ namespace mamba::solver::libsolv
print_problem_tree_msg(
out,
cp_pbs,
{ /* .unavailable= */ ctx.graphics_params.palette.failure,
/* .available= */ ctx.graphics_params.palette.success }
{
/* .unavailable= */ palette.failure,
/* .available= */ palette.success,
}
);
return out;
}
auto UnSolvable::explain_problems(MPool& pool) const -> std::string
auto UnSolvable::explain_problems(MPool& pool, const Palette& palette) const -> std::string
{
std::stringstream ss;
explain_problems_to(pool, ss);
explain_problems_to(pool, ss, palette);
return ss.str();
}
}

View File

@ -104,11 +104,12 @@ target_include_directories(
find_package(doctest REQUIRED)
find_package(Threads REQUIRED)
find_package(Libsolv REQUIRED)
target_link_libraries(
test_libmamba
PUBLIC mamba::libmamba reproc reproc++
PRIVATE doctest::doctest Threads::Threads
PRIVATE doctest::doctest Threads::Threads solv::libsolv solv::libsolvext
)
# Copy data directory into binary dir to avoid modifications

View File

@ -126,7 +126,8 @@ namespace mamba
const fs::u8path lockfile_path{ mambatests::test_data_dir
/ "env_lockfile/good_multiple_categories-lock.yaml" };
auto channel_context = ChannelContext::make_conda_compatible(mambatests::context());
MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
mamba::MultiPackageCache pkg_cache({ "/tmp/" }, ctx.validation_params);
ctx.platform = "linux-64";

View File

@ -102,9 +102,9 @@ namespace
* The underlying packages do not exist, we are onl interested in the conflict.
*/
template <typename PkgRange>
auto create_pkgs_pool(Context& ctx, ChannelContext& channel_context, const PkgRange& packages)
auto create_pkgs_pool(ChannelContext& channel_context, const PkgRange& packages)
{
auto pool = MPool{ ctx, channel_context };
MPool pool{ channel_context.params() };
pool.add_repo_from_packages(packages);
return pool;
}
@ -114,7 +114,7 @@ TEST_CASE("Test create_pool utility")
{
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
auto pool = create_pkgs_pool(ctx, channel_context, std::array{ mkpkg("foo", "0.1.0", {}) });
auto pool = create_pkgs_pool(channel_context, std::array{ mkpkg("foo", "0.1.0", {}) });
auto request = Request{ {}, { Request::Install{ "foo"_ms } } };
const auto outcome = solver::libsolv::Solver().solve(pool, request).value();
REQUIRE(std::holds_alternative<solver::Solution>(outcome));
@ -125,7 +125,6 @@ TEST_CASE("Test empty specs")
auto& ctx = mambatests::context();
auto channel_context = ChannelContext::make_conda_compatible(ctx);
auto pool = create_pkgs_pool(
ctx,
channel_context,
std::array{ mkpkg("foo", "0.1.0", {}), mkpkg("", "", {}) }
);
@ -136,11 +135,10 @@ TEST_CASE("Test empty specs")
namespace
{
auto create_basic_conflict(Context& ctx, ChannelContext& channel_context)
auto create_basic_conflict(Context&, ChannelContext& channel_context)
{
return std::pair(
create_pkgs_pool(
ctx,
channel_context,
std::array{
mkpkg("A", "0.1.0"),
@ -158,11 +156,10 @@ namespace
* The example given by Natalie Weizenbaum
* (credits https://nex3.medium.com/pubgrub-2fb6470504f).
*/
auto create_pubgrub(Context& ctx, ChannelContext& channel_context)
auto create_pubgrub(Context&, ChannelContext& channel_context)
{
return std::pair(
create_pkgs_pool(
ctx,
channel_context,
std::array{
mkpkg("menu", "1.5.0", { "dropdown=2.*" }),
@ -194,7 +191,7 @@ namespace
);
}
auto create_pubgrub_hard_(Context& ctx, ChannelContext& channel_context, bool missing_package)
auto create_pubgrub_hard_(Context&, ChannelContext& channel_context, bool missing_package)
{
auto packages = std::vector{
mkpkg("menu", "2.1.0", { "dropdown>=2.1", "emoji" }),
@ -244,7 +241,7 @@ namespace
packages.push_back(mkpkg("dropdown", "2.9.0", { "libicons>10.0" }));
}
return std::pair(
create_pkgs_pool(ctx, channel_context, packages),
create_pkgs_pool(channel_context, packages),
Request{
{},
{
@ -287,19 +284,24 @@ namespace
/**
* Mock of channel_loader.hpp:load_channels that takes a list of channels.
*/
auto
load_channels(Context& ctx, MPool& pool, MultiPackageCache& cache, std::vector<std::string>&& channels)
auto load_channels(
Context& ctx,
ChannelContext& channel_context,
MPool& pool,
MultiPackageCache& cache,
std::vector<std::string>&& channels
)
{
auto sub_dirs = std::vector<SubdirData>();
for (const auto& location : channels)
{
for (const auto& chan : pool.channel_context().make_channel(location))
for (const auto& chan : channel_context.make_channel(location))
{
for (const auto& platform : chan.platforms())
{
auto sub_dir = SubdirData::create(
ctx,
pool.channel_context(),
channel_context,
chan,
platform,
chan.platform_url(platform).str(),
@ -336,7 +338,7 @@ namespace
auto prefix_data = PrefixData::create(tmp_dir.path() / "prefix", channel_context).value();
prefix_data.add_packages(virtual_packages);
auto pool = MPool{ ctx, channel_context };
auto pool = MPool{ channel_context.params() };
load_installed_packages_in_pool(ctx, pool, prefix_data);
@ -345,7 +347,13 @@ namespace
bool prev_progress_bars_value = ctx.graphics_params.no_progress_bars;
ctx.graphics_params.no_progress_bars = true;
load_channels(ctx, pool, cache, make_platform_channels(std::move(channels), platforms));
load_channels(
ctx,
channel_context,
pool,
cache,
make_platform_channels(std::move(channels), platforms)
);
ctx.graphics_params.no_progress_bars = prev_progress_bars_value;
return pool;

View File

@ -380,25 +380,15 @@ bind_submodule_impl(pybind11::module_ m)
py::add_ostream_redirect(m, "ostream_redirect");
py::class_<MPool>(m, "Pool")
.def(
py::init<>(
[](ChannelContext& channel_context) {
return MPool{ mambapy::singletons.context(), channel_context };
}
),
py::arg("channel_context")
)
.def("set_debuglevel", &MPool::set_debuglevel)
.def("create_whatprovides", &MPool::create_whatprovides)
.def("select_solvables", &MPool::select_solvables, py::arg("id"), py::arg("sorted") = false)
.def("matchspec2id", &MPool::matchspec2id, py::arg("spec"))
.def("id2pkginfo", &MPool::id2pkginfo, py::arg("id"))
.def(py::init<specs::ChannelResolveParams>(), py::arg("channel_params"))
.def("set_logger", &MPool::set_logger, py::call_guard<py::gil_scoped_acquire>())
.def(
"add_repo_from_repodata_json",
&MPool::add_repo_from_repodata_json,
py::arg("path"),
py::arg("url"),
py::arg("add_pip_as_python_dependency") = solver::libsolv::PipAsPythonDependency::No,
py::arg("use_only_tar_bz2") = solver::libsolv::UseOnlyTarBz2::No,
py::arg("repodata_parsers") = solver::libsolv::RepodataParser::Mamba
)
.def(
@ -655,6 +645,10 @@ bind_submodule_impl(pybind11::module_ m)
.def("params", &ChannelContext::params)
.def("has_zst", &ChannelContext::has_zst);
py::class_<Palette>(m, "Palette")
.def_static("no_color", &Palette::no_color)
.def_static("terminal", &Palette::terminal);
py::class_<Context, std::unique_ptr<Context, py::nodelete>> ctx(m, "Context");
ctx //
.def_static(

View File

@ -7,6 +7,7 @@
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include "mamba/core/palette.hpp"
#include "mamba/core/pool.hpp"
#include "mamba/solver/libsolv/parameters.hpp"
#include "mamba/solver/libsolv/repo_info.hpp"
@ -37,6 +38,20 @@ namespace mambapy
.def(py::init([](bool val) { return static_cast<PipAsPythonDependency>(val); }));
py::implicitly_convertible<py::bool_, PipAsPythonDependency>();
py::enum_<UseOnlyTarBz2>(m, "UseOnlyTarBz2")
.value("No", UseOnlyTarBz2::No)
.value("Yes", UseOnlyTarBz2::Yes)
.def(py::init([](bool val) { return static_cast<UseOnlyTarBz2>(val); }));
py::implicitly_convertible<py::bool_, UseOnlyTarBz2>();
py::enum_<LogLevel>(m, "LogLevel")
.value("Debug", LogLevel::Debug)
.value("Warning", LogLevel::Warning)
.value("Error", LogLevel::Error)
.value("Fatal", LogLevel::Fatal)
.def(py::init(&enum_from_str<LogLevel>));
py::implicitly_convertible<py::bool_, LogLevel>();
py::class_<Priorities>(m, "Priorities")
.def(
py::init(

View File

@ -36,6 +36,26 @@ def test_PipASPythonDependency():
assert libsolv.PipAsPythonDependency(True) == libsolv.PipAsPythonDependency.Yes
def test_UseOnlyTarBz2():
assert libsolv.UseOnlyTarBz2.No.name == "No"
assert libsolv.UseOnlyTarBz2.Yes.name == "Yes"
assert libsolv.UseOnlyTarBz2(True) == libsolv.UseOnlyTarBz2.Yes
def test_Platform():
assert libsolv.LogLevel.Debug.name == "Debug"
assert libsolv.LogLevel.Warning.name == "Warning"
assert libsolv.LogLevel.Error.name == "Error"
assert libsolv.LogLevel.Fatal.name == "Fatal"
assert libsolv.LogLevel("Error") == libsolv.LogLevel.Error
with pytest.raises(KeyError):
# No parsing, explicit name
libsolv.LogLevel("Unicorn")
def test_Priorities():
p = libsolv.Priorities(priority=-1, subpriority=-2)

View File

@ -71,16 +71,18 @@ update_self(Configuration& config, const std::optional<std::string>& version)
ctx.prefix_params.target_prefix = ctx.prefix_params.root_prefix;
auto channel_context = ChannelContext::make_conda_compatible(ctx);
mamba::MPool pool{ ctx, channel_context };
MPool pool{ channel_context.params() };
add_spdlog_logger_to_pool(pool);
mamba::MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params);
auto exp_loaded = load_channels(ctx, pool, package_caches);
auto exp_loaded = load_channels(ctx, channel_context, pool, package_caches);
if (!exp_loaded)
{
throw exp_loaded.error();
}
pool.create_whatprovides();
auto matchspec = specs::MatchSpec::parse(
version ? fmt::format("micromamba={}", version.value())
: fmt::format("micromamba>{}", umamba::version())