mirror of https://github.com/mamba-org/mamba.git
Custom resolve complex MatchSpec in Solver (#3233)
* Add ObjPool::current_error * Support reference_wrapper in MatchSpec::contains * Rename subdir > platform in solv-cpp * Add Matcher as namespace callback * Add MatcherFlags * Redefine MatchSpec::is_simple * Add VersionSpec::from_predicate * Refactor reinstall jobs * Fix MatchSpec::is_simple * Handle complex MatchSpec in solver * Add version cache in Matcher * Add more pool namespace tests * Handle exception in ObjPool callback * Try removing SOLVABLE_PROVIDES * Add unsolvable complex spec test * Fix Database callback exceptions * Add channel in Matcher * Adapt channel_specific tests * Fix updates
This commit is contained in:
parent
25431a6d38
commit
43e38efb18
|
@ -181,6 +181,7 @@ set(
|
|||
# Solver libsolv implementation
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/database.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/helpers.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/matcher.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/parameters.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/repo_info.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/solver.cpp
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef MAMBA_SOLV_POOL_HPP
|
||||
#define MAMBA_SOLV_POOL_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
@ -44,6 +45,11 @@ namespace solv
|
|||
auto raw() -> raw_ptr;
|
||||
auto raw() const -> const_raw_ptr;
|
||||
|
||||
auto current_error() const -> std::string_view;
|
||||
|
||||
void set_current_error(raw_str_view msg);
|
||||
void set_current_error(const std::string& msg);
|
||||
|
||||
/**
|
||||
* Get the current distribution type of the pool.
|
||||
*
|
||||
|
@ -285,6 +291,22 @@ namespace solv
|
|||
template <typename UnaryFunc>
|
||||
void for_each_installed_solvable(UnaryFunc&& func);
|
||||
|
||||
/** Rethrow exception thrown in callback. */
|
||||
void rethrow_potential_callback_exception() const;
|
||||
|
||||
protected:
|
||||
|
||||
using UserCallback = std::function<OffsetId(ObjPoolView, StringId, StringId)>;
|
||||
|
||||
/**
|
||||
* A wrapper around user callback to handle exceptions.
|
||||
*
|
||||
* This cannot be set in this class since this is only a view type but can be checked
|
||||
* for errors.
|
||||
*/
|
||||
struct NamespaceCallbackWrapper;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
raw_ptr m_pool;
|
||||
|
@ -302,6 +324,8 @@ namespace solv
|
|||
~ObjPool();
|
||||
|
||||
using ObjPoolView::raw;
|
||||
using ObjPoolView::current_error;
|
||||
using ObjPoolView::set_current_error;
|
||||
using ObjPoolView::disttype;
|
||||
using ObjPoolView::set_disttype;
|
||||
using ObjPoolView::find_string;
|
||||
|
@ -337,6 +361,7 @@ namespace solv
|
|||
using ObjPoolView::for_each_solvable;
|
||||
using ObjPoolView::for_each_installed_solvable_id;
|
||||
using ObjPoolView::for_each_installed_solvable;
|
||||
using ObjPoolView::rethrow_potential_callback_exception;
|
||||
|
||||
/** Set the callback to handle libsolv messages.
|
||||
*
|
||||
|
@ -347,8 +372,9 @@ namespace solv
|
|||
template <typename Func>
|
||||
void set_debug_callback(Func&& callback);
|
||||
|
||||
template <typename Func>
|
||||
void set_namespace_callback(Func&& callback);
|
||||
using UserCallback = ObjPoolView::UserCallback;
|
||||
|
||||
void set_namespace_callback(UserCallback&& callback);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -358,7 +384,7 @@ namespace solv
|
|||
};
|
||||
|
||||
std::unique_ptr<void, void (*)(void*)> m_user_debug_callback;
|
||||
std::unique_ptr<void, void (*)(void*)> m_user_namespace_callback;
|
||||
std::unique_ptr<NamespaceCallbackWrapper> m_user_namespace_callback;
|
||||
// Must be deleted before the debug callback
|
||||
std::unique_ptr<::Pool, ObjPool::PoolDeleter> m_pool = nullptr;
|
||||
};
|
||||
|
@ -531,28 +557,5 @@ namespace solv
|
|||
|
||||
::pool_setdebugcallback(raw(), debug_callback, m_user_debug_callback.get());
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ObjPool::set_namespace_callback(Func&& callback)
|
||||
{
|
||||
static_assert(
|
||||
std::is_nothrow_invocable_v<Func, ObjPoolView, StringId, StringId>,
|
||||
"User callback must be marked noexcept."
|
||||
);
|
||||
|
||||
m_user_namespace_callback.reset(new Func(std::forward<Func>(callback)));
|
||||
m_user_namespace_callback.get_deleter() = [](void* ptr)
|
||||
{ delete reinterpret_cast<Func*>(ptr); };
|
||||
|
||||
// Wrap the user callback in the libsolv function type that must cast the callback ptr
|
||||
auto namespace_callback = [](::Pool* pool, void* user_data, StringId name, StringId ver
|
||||
) noexcept -> OffsetId
|
||||
{
|
||||
auto* user_namespace_callback = reinterpret_cast<Func*>(user_data);
|
||||
return (*user_namespace_callback)(ObjPoolView(pool), name, ver); // noexcept
|
||||
};
|
||||
|
||||
::pool_setnamespacecallback(raw(), namespace_callback, m_user_namespace_callback.get());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -75,11 +75,11 @@ namespace solv
|
|||
auto channel() const -> std::string_view;
|
||||
|
||||
/**
|
||||
* The sub-directory of the solvable.
|
||||
* The platform of the solvable.
|
||||
*
|
||||
* @see ObjSolvableView::set_subdir
|
||||
**/
|
||||
auto subdir() const -> std::string_view;
|
||||
auto platform() const -> std::string_view;
|
||||
|
||||
/**
|
||||
* Queue of ``DependencyId``.
|
||||
|
@ -254,7 +254,7 @@ namespace solv
|
|||
void set_channel(const std::string& str) const;
|
||||
|
||||
/**
|
||||
* Set the sub-directory of the solvable.
|
||||
* Set the platform of the solvable.
|
||||
*
|
||||
* This has no effect for libsolv and is purely for data storing.
|
||||
* This may not be the same as @ref ObjRepoViewConst::channel, for instance the install
|
||||
|
@ -263,8 +263,8 @@ namespace solv
|
|||
* @note A call to @ref ObjRepoView::internalize is required for this attribute to
|
||||
* be available for lookup.
|
||||
*/
|
||||
void set_subdir(raw_str_view str) const;
|
||||
void set_subdir(const std::string& str) const;
|
||||
void set_platform(raw_str_view str) const;
|
||||
void set_platform(const std::string& str) const;
|
||||
|
||||
/** Set the dependencies of the solvable. */
|
||||
void set_dependencies(const ObjQueue& q, DependencyMarker marker = 0) const;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <solv/conda.h>
|
||||
|
@ -38,6 +40,21 @@ namespace solv
|
|||
return m_pool;
|
||||
}
|
||||
|
||||
auto ObjPoolView::current_error() const -> std::string_view
|
||||
{
|
||||
return ::pool_errstr(m_pool);
|
||||
}
|
||||
|
||||
void ObjPoolView::set_current_error(raw_str_view msg)
|
||||
{
|
||||
::pool_error(m_pool, -1, "%s", msg);
|
||||
}
|
||||
|
||||
void ObjPoolView::set_current_error(const std::string& msg)
|
||||
{
|
||||
return set_current_error(msg.c_str());
|
||||
}
|
||||
|
||||
auto ObjPoolView::disttype() const -> DistType
|
||||
{
|
||||
return raw()->disttype;
|
||||
|
@ -323,6 +340,24 @@ namespace solv
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ObjPoolView::NamespaceCallbackWrapper
|
||||
{
|
||||
UserCallback callback;
|
||||
std::exception_ptr error = nullptr;
|
||||
};
|
||||
|
||||
void ObjPoolView::rethrow_potential_callback_exception() const
|
||||
{
|
||||
if (auto callback = reinterpret_cast<NamespaceCallbackWrapper*>(raw()->nscallbackdata))
|
||||
{
|
||||
if (auto error = callback->error)
|
||||
{
|
||||
callback->error = nullptr;
|
||||
std::rethrow_exception(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* Implementation of ObjPool *
|
||||
*******************************/
|
||||
|
@ -335,11 +370,45 @@ namespace solv
|
|||
ObjPool::ObjPool()
|
||||
: ObjPoolView(nullptr)
|
||||
, m_user_debug_callback(nullptr, [](void* /*ptr*/) {})
|
||||
, m_user_namespace_callback(nullptr, [](void* /*ptr*/) {})
|
||||
, m_user_namespace_callback(nullptr)
|
||||
, m_pool(::pool_create())
|
||||
{
|
||||
ObjPoolView::m_pool = m_pool.get();
|
||||
}
|
||||
|
||||
ObjPool::~ObjPool() = default;
|
||||
|
||||
void ObjPool::set_namespace_callback(UserCallback&& callback)
|
||||
{
|
||||
m_user_namespace_callback = std::make_unique<NamespaceCallbackWrapper>();
|
||||
|
||||
// Set the callback
|
||||
m_user_namespace_callback->callback = [wrapper = m_user_namespace_callback.get(),
|
||||
callback = std::move(callback
|
||||
)](ObjPoolView pool, StringId name, StringId ver
|
||||
) mutable noexcept -> OffsetId
|
||||
{
|
||||
auto error = std::exception_ptr(nullptr);
|
||||
try
|
||||
{
|
||||
std::swap(error, wrapper->error);
|
||||
return callback(pool, name, ver);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
wrapper->error = std::current_exception();
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the user callback in the libsolv function type that must cast the callback ptr
|
||||
auto libsolv_callback = +[](::Pool* pool, void* user_data, StringId name, StringId ver
|
||||
) noexcept -> OffsetId
|
||||
{
|
||||
auto* user_namespace_callback = reinterpret_cast<NamespaceCallbackWrapper*>(user_data);
|
||||
return user_namespace_callback->callback(ObjPoolView(pool), name, ver); // noexcept
|
||||
};
|
||||
|
||||
::pool_setnamespacecallback(raw(), libsolv_callback, m_user_namespace_callback.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -352,20 +352,20 @@ namespace solv
|
|||
return set_channel(str.c_str());
|
||||
}
|
||||
|
||||
auto ObjSolvableViewConst::subdir() const -> std::string_view
|
||||
auto ObjSolvableViewConst::platform() const -> std::string_view
|
||||
{
|
||||
return ptr_to_strview(::solvable_lookup_str(const_cast<::Solvable*>(raw()), SOLVABLE_MEDIADIR)
|
||||
);
|
||||
}
|
||||
|
||||
void ObjSolvableView::set_subdir(raw_str_view str) const
|
||||
void ObjSolvableView::set_platform(raw_str_view str) const
|
||||
{
|
||||
::solvable_set_str(raw(), SOLVABLE_MEDIADIR, str);
|
||||
}
|
||||
|
||||
void ObjSolvableView::set_subdir(const std::string& str) const
|
||||
void ObjSolvableView::set_platform(const std::string& str) const
|
||||
{
|
||||
return set_subdir(str.c_str());
|
||||
return set_platform(str.c_str());
|
||||
}
|
||||
|
||||
auto ObjSolvableViewConst::dependencies(DependencyMarker marker) const -> ObjQueue
|
||||
|
|
|
@ -180,10 +180,11 @@ namespace solv
|
|||
return val != 0;
|
||||
}
|
||||
|
||||
auto ObjSolver::solve(const ObjPool& /* pool */, const ObjQueue& jobs) -> bool
|
||||
auto ObjSolver::solve(const ObjPool& pool, const ObjQueue& jobs) -> bool
|
||||
{
|
||||
// pool is captured inside solver so we take it as a parameter to be explicit.
|
||||
const auto n_pbs = ::solver_solve(raw(), const_cast<::Queue*>(jobs.raw()));
|
||||
pool.rethrow_potential_callback_exception();
|
||||
return n_pbs == 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,12 @@ TEST_SUITE("solv::ObjPool")
|
|||
CHECK_EQ(pool.disttype(), DISTTYPE_CONDA);
|
||||
}
|
||||
|
||||
SUBCASE("Error")
|
||||
{
|
||||
pool.set_current_error("Some failure");
|
||||
CHECK_EQ(pool.current_error(), "Some failure");
|
||||
}
|
||||
|
||||
SUBCASE("Add strings")
|
||||
{
|
||||
const auto id_hello = pool.add_string("Hello");
|
||||
|
|
|
@ -151,26 +151,130 @@ TEST_SUITE("solv::scenariso")
|
|||
const auto dep_id = pool.add_dependency(dep_name_id, REL_NAMESPACE, dep_ver_id);
|
||||
|
||||
auto [repo_id, repo] = pool.add_repo("forge");
|
||||
auto [solv_id, solv] = repo.add_solvable();
|
||||
solv.set_name("a");
|
||||
solv.set_version("1.0");
|
||||
const auto a_solv_id = add_simple_package(pool, repo, SimplePkg{ "a", "1.0" });
|
||||
repo.internalize();
|
||||
|
||||
pool.set_namespace_callback(
|
||||
[&pool,
|
||||
name_id = dep_name_id,
|
||||
ver_id = dep_ver_id,
|
||||
solv_id = solv_id](ObjPoolView, StringId name, StringId ver) noexcept -> OffsetId
|
||||
SUBCASE("Direct job namespace dependency")
|
||||
{
|
||||
SUBCASE("Which resolves to some packages")
|
||||
{
|
||||
CHECK_EQ(name, name_id);
|
||||
CHECK_EQ(ver, ver_id);
|
||||
return pool.add_to_whatprovides_data({ solv_id });
|
||||
}
|
||||
);
|
||||
bool called = false;
|
||||
pool.set_namespace_callback(
|
||||
[&, a_solv_id = a_solv_id](ObjPoolView, StringId name, StringId ver) noexcept -> OffsetId
|
||||
{
|
||||
called = true;
|
||||
CHECK_EQ(name, dep_name_id);
|
||||
CHECK_EQ(ver, dep_ver_id);
|
||||
return pool.add_to_whatprovides_data({ a_solv_id });
|
||||
}
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
auto jobs = ObjQueue{ SOLVER_INSTALL, dep_id };
|
||||
auto solved = solver.solve(pool, jobs);
|
||||
CHECK(solved);
|
||||
auto solver = ObjSolver(pool);
|
||||
auto solved = solver.solve(pool, { SOLVER_INSTALL, dep_id });
|
||||
CHECK(solved);
|
||||
CHECK(called);
|
||||
}
|
||||
|
||||
SUBCASE("Which is unsatisfyable")
|
||||
{
|
||||
bool called = false;
|
||||
pool.set_namespace_callback(
|
||||
[&](ObjPoolView, StringId, StringId) noexcept -> OffsetId
|
||||
{
|
||||
called = true;
|
||||
return 0; // 0 means "not-found"
|
||||
}
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
auto solved = solver.solve(pool, { SOLVER_INSTALL, dep_id });
|
||||
CHECK(called);
|
||||
CHECK_FALSE(solved);
|
||||
}
|
||||
|
||||
SUBCASE("Callback throws")
|
||||
{
|
||||
pool.set_namespace_callback(
|
||||
[](ObjPoolView, StringId, StringId) -> OffsetId
|
||||
{ throw std::runtime_error("Error!"); }
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
CHECK_THROWS_AS(
|
||||
[&] {
|
||||
return solver.solve(pool, { SOLVER_INSTALL, dep_id });
|
||||
}(),
|
||||
std::runtime_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("transitive job dependency")
|
||||
{
|
||||
// Add a dependency ``job==3.0``
|
||||
const auto job_name_id = pool.add_string("job");
|
||||
const auto job_ver_id = pool.add_string("3.0");
|
||||
const auto job_id = pool.add_dependency(job_name_id, REL_EQ, job_ver_id);
|
||||
|
||||
// Add a package ``{name=job, version=3.0}`` with dependency in namespace dep.
|
||||
auto [job_solv_id, job_solv] = repo.add_solvable();
|
||||
job_solv.set_name(job_name_id);
|
||||
job_solv.set_version(job_ver_id);
|
||||
job_solv.set_dependencies({ dep_id });
|
||||
job_solv.add_self_provide();
|
||||
repo.internalize();
|
||||
|
||||
SUBCASE("Which resolves to some packages")
|
||||
{
|
||||
bool called = false;
|
||||
pool.set_namespace_callback(
|
||||
[&, a_solv_id = a_solv_id](ObjPoolView, StringId name, StringId ver) noexcept -> OffsetId
|
||||
{
|
||||
called = true;
|
||||
CHECK_EQ(name, dep_name_id);
|
||||
CHECK_EQ(ver, dep_ver_id);
|
||||
return pool.add_to_whatprovides_data({ a_solv_id });
|
||||
}
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
auto solved = solver.solve(pool, { SOLVER_INSTALL, job_id });
|
||||
CHECK(called);
|
||||
CHECK(solved);
|
||||
}
|
||||
|
||||
SUBCASE("Which is unsatisfyable")
|
||||
{
|
||||
bool called = false;
|
||||
pool.set_namespace_callback(
|
||||
[&](ObjPoolView, StringId, StringId) noexcept -> OffsetId
|
||||
{
|
||||
called = true;
|
||||
return 0; // 0 means "not-found"
|
||||
}
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
auto solved = solver.solve(pool, { SOLVER_INSTALL, job_id });
|
||||
CHECK(called);
|
||||
CHECK_FALSE(solved);
|
||||
}
|
||||
|
||||
SUBCASE("Callback throws")
|
||||
{
|
||||
pool.set_namespace_callback(
|
||||
[](ObjPoolView, StringId, StringId) -> OffsetId
|
||||
{ throw std::runtime_error("Error!"); }
|
||||
);
|
||||
|
||||
auto solver = ObjSolver(pool);
|
||||
CHECK_THROWS_AS(
|
||||
[&] {
|
||||
return solver.solve(pool, { SOLVER_INSTALL, job_id });
|
||||
}(),
|
||||
std::runtime_error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ TEST_SUITE("solv::ObjSolvable")
|
|||
solv.set_timestamp(4110596167);
|
||||
solv.set_url("https://conda.anaconda.org/conda-forge/linux-64");
|
||||
solv.set_channel("conda-forge");
|
||||
solv.set_subdir("linux-64");
|
||||
solv.set_platform("linux-64");
|
||||
solv.set_type(SolvableType::Virtualpackage);
|
||||
|
||||
SUBCASE("Empty without internalize")
|
||||
|
@ -70,7 +70,7 @@ TEST_SUITE("solv::ObjSolvable")
|
|||
CHECK_EQ(solv.timestamp(), 0);
|
||||
CHECK_EQ(solv.url(), "");
|
||||
CHECK_EQ(solv.channel(), "");
|
||||
CHECK_EQ(solv.subdir(), "");
|
||||
CHECK_EQ(solv.platform(), "");
|
||||
CHECK_EQ(solv.type(), SolvableType::Package);
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ TEST_SUITE("solv::ObjSolvable")
|
|||
CHECK_EQ(solv.timestamp(), 4110596167);
|
||||
CHECK_EQ(solv.url(), "https://conda.anaconda.org/conda-forge/linux-64");
|
||||
CHECK_EQ(solv.channel(), "conda-forge");
|
||||
CHECK_EQ(solv.subdir(), "linux-64");
|
||||
CHECK_EQ(solv.platform(), "linux-64");
|
||||
CHECK_EQ(solv.type(), SolvableType::Virtualpackage);
|
||||
|
||||
SUBCASE("Override attribute")
|
||||
|
@ -126,7 +126,7 @@ TEST_SUITE("solv::ObjSolvable")
|
|||
CHECK_EQ(solv.timestamp(), 0);
|
||||
CHECK_EQ(solv.url(), "");
|
||||
CHECK_EQ(solv.channel(), "");
|
||||
CHECK_EQ(solv.subdir(), "");
|
||||
CHECK_EQ(solv.platform(), "");
|
||||
CHECK_EQ(solv.type(), SolvableType::Package);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,8 +105,16 @@ namespace mamba::specs
|
|||
[[nodiscard]] auto conda_build_form() const -> std::string;
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
|
||||
/**
|
||||
* Return true if the MatchSpec can be written as ``<name> <version> <build_string>``.
|
||||
*/
|
||||
[[nodiscard]] auto is_simple() const -> bool;
|
||||
|
||||
/**
|
||||
* Return true if the MatchSpec contains an exact package name and nothing else.
|
||||
*/
|
||||
[[nodiscard]] auto is_only_package_name() const -> bool;
|
||||
|
||||
/**
|
||||
* Check if the MatchSpec matches the given package.
|
||||
*
|
||||
|
@ -184,31 +192,61 @@ struct fmt::formatter<::mamba::specs::MatchSpec>
|
|||
|
||||
namespace mamba::specs
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <typename Return>
|
||||
struct Deref
|
||||
{
|
||||
template <typename T>
|
||||
static auto deref(T&& x) -> decltype(auto)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Inner>
|
||||
struct Deref<std::reference_wrapper<Inner>>
|
||||
{
|
||||
template <typename T>
|
||||
static auto deref(T&& x) -> decltype(auto)
|
||||
{
|
||||
return std::forward<T>(x).get();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Attr, typename Pkg>
|
||||
auto invoke_pkg(Attr&& attr, Pkg&& pkg) -> decltype(auto)
|
||||
{
|
||||
using Return = std::decay_t<std::invoke_result_t<Attr&&, Pkg&&>>;
|
||||
return Deref<Return>::deref(std::invoke(std::forward<Attr>(attr), std::forward<Pkg>(pkg)));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Pkg>
|
||||
auto MatchSpec::contains_except_channel(const Pkg& pkg) const -> bool
|
||||
{
|
||||
if ( //
|
||||
!name().contains(std::invoke(&Pkg::name, pkg)) //
|
||||
|| !version().contains(std::invoke(&Pkg::version, pkg)) //
|
||||
|| !build_string().contains(std::invoke(&Pkg::build_string, pkg)) //
|
||||
|| !build_number().contains(std::invoke(&Pkg::build_number, pkg)) //
|
||||
|| (!md5().empty() && (md5() != std::invoke(&Pkg::md5, pkg))) //
|
||||
|| (!sha256().empty() && (sha256() != std::invoke(&Pkg::sha256, pkg))) //
|
||||
|| (!license().empty() && (license() != std::invoke(&Pkg::license, pkg))) //
|
||||
if ( //
|
||||
!name().contains(detail::invoke_pkg(&Pkg::name, pkg)) //
|
||||
|| !version().contains(detail::invoke_pkg(&Pkg::version, pkg)) //
|
||||
|| !build_string().contains(detail::invoke_pkg(&Pkg::build_string, pkg)) //
|
||||
|| !build_number().contains(detail::invoke_pkg(&Pkg::build_number, pkg)) //
|
||||
|| (!md5().empty() && (md5() != detail::invoke_pkg(&Pkg::md5, pkg))) //
|
||||
|| (!sha256().empty() && (sha256() != detail::invoke_pkg(&Pkg::sha256, pkg))) //
|
||||
|| (!license().empty() && (license() != detail::invoke_pkg(&Pkg::license, pkg))) //
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& plats = platforms();
|
||||
plats.has_value() && !plats->get().contains(std::invoke(&Pkg::platform, pkg)))
|
||||
plats.has_value() && !plats->get().contains(detail::invoke_pkg(&Pkg::platform, pkg)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& tfeats = track_features();
|
||||
tfeats.has_value()
|
||||
&& !util::set_is_subset_of(tfeats->get(), std::invoke(&Pkg::track_features, pkg)))
|
||||
&& !util::set_is_subset_of(tfeats->get(), detail::invoke_pkg(&Pkg::track_features, pkg)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -152,6 +152,11 @@ namespace mamba::specs
|
|||
|
||||
[[nodiscard]] static auto parse(std::string_view str) -> expected_parse_t<VersionSpec>;
|
||||
|
||||
/**
|
||||
* Create a Version spec with a single predicate in the expression.
|
||||
*/
|
||||
[[nodiscard]] static auto from_predicate(VersionPredicate pred) -> VersionSpec;
|
||||
|
||||
/** Construct VersionSpec that match all versions. */
|
||||
VersionSpec() = default;
|
||||
explicit VersionSpec(tree_type&& tree) noexcept;
|
||||
|
|
|
@ -24,18 +24,19 @@
|
|||
#include "solv-cpp/queue.hpp"
|
||||
|
||||
#include "solver/libsolv/helpers.hpp"
|
||||
#include "solver/libsolv/matcher.hpp"
|
||||
|
||||
namespace mamba::solver::libsolv
|
||||
{
|
||||
struct Database::DatabaseImpl
|
||||
{
|
||||
DatabaseImpl(specs::ChannelResolveParams p_channel_params)
|
||||
: channel_params(std::move(p_channel_params))
|
||||
explicit DatabaseImpl(specs::ChannelResolveParams p_channel_params)
|
||||
: matcher(std::move(p_channel_params))
|
||||
{
|
||||
}
|
||||
|
||||
specs::ChannelResolveParams channel_params;
|
||||
solv::ObjPool pool = {};
|
||||
Matcher matcher;
|
||||
};
|
||||
|
||||
Database::Database(specs::ChannelResolveParams channel_params)
|
||||
|
@ -45,6 +46,14 @@ namespace mamba::solver::libsolv
|
|||
// 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
|
||||
pool().set_namespace_callback(
|
||||
[&data = (*m_data
|
||||
)](solv::ObjPoolView pool, solv::StringId first, solv::StringId second) -> solv::OffsetId
|
||||
{
|
||||
auto [dep, flags] = get_abused_namespace_callback_args(pool, first, second);
|
||||
return data.matcher.get_matching_packages(pool, dep, flags);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Database::~Database() = default;
|
||||
|
@ -75,7 +84,7 @@ namespace mamba::solver::libsolv
|
|||
|
||||
auto Database::channel_params() const -> const specs::ChannelResolveParams&
|
||||
{
|
||||
return m_data->channel_params;
|
||||
return m_data->matcher.channel_params();
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -305,13 +314,9 @@ namespace mamba::solver::libsolv
|
|||
|
||||
namespace
|
||||
{
|
||||
auto matchspec2id(
|
||||
solv::ObjPool& pool,
|
||||
const specs::ChannelResolveParams& channel_params,
|
||||
const specs::MatchSpec& ms
|
||||
) -> solv::DependencyId
|
||||
auto matchspec2id(solv::ObjPool& pool, const specs::MatchSpec& ms) -> solv::DependencyId
|
||||
{
|
||||
return pool_add_matchspec(pool, ms, channel_params)
|
||||
return pool_add_matchspec(pool, ms)
|
||||
.or_else([](mamba_error&& error) { throw std::move(error); })
|
||||
.value_or(0);
|
||||
}
|
||||
|
@ -322,7 +327,7 @@ namespace mamba::solver::libsolv
|
|||
static_assert(std::is_same_v<std::underlying_type_t<PackageId>, solv::SolvableId>);
|
||||
|
||||
pool().ensure_whatprovides();
|
||||
const auto ms_id = matchspec2id(pool(), channel_params(), ms);
|
||||
const auto ms_id = matchspec2id(pool(), ms);
|
||||
auto solvables = pool().select_solvables({ SOLVER_SOLVABLE_PROVIDES, ms_id });
|
||||
auto out = std::vector<PackageId>(solvables.size());
|
||||
std::transform(
|
||||
|
@ -339,7 +344,7 @@ namespace mamba::solver::libsolv
|
|||
static_assert(std::is_same_v<std::underlying_type_t<PackageId>, solv::SolvableId>);
|
||||
|
||||
pool().ensure_whatprovides();
|
||||
const auto ms_id = matchspec2id(pool(), channel_params(), ms);
|
||||
const auto ms_id = matchspec2id(pool(), ms);
|
||||
auto solvables = pool().what_matches_dep(SOLVABLE_REQUIRES, ms_id);
|
||||
auto out = std::vector<PackageId>(solvables.size());
|
||||
std::transform(
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "solver/helpers.hpp"
|
||||
#include "solver/libsolv/helpers.hpp"
|
||||
#include "solver/libsolv/matcher.hpp"
|
||||
|
||||
#define MAMBA_TOOL_VERSION "2.0"
|
||||
|
||||
|
@ -51,8 +52,11 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
solv.set_build_number(pkg.build_number);
|
||||
solv.set_channel(pkg.channel);
|
||||
// TODO In the case of a repo with all similar subdir (which is not the case in the
|
||||
// install repo) we could also not set this (to save the strings stored in libsolv)
|
||||
// and recreate it by concatenating filename and repo URL.
|
||||
solv.set_url(pkg.package_url);
|
||||
solv.set_subdir(pkg.platform);
|
||||
solv.set_platform(pkg.platform);
|
||||
solv.set_file_name(pkg.filename);
|
||||
solv.set_license(pkg.license);
|
||||
solv.set_size(pkg.size);
|
||||
|
@ -98,7 +102,7 @@ namespace mamba::solver::libsolv
|
|||
out.build_number = s.build_number();
|
||||
out.channel = s.channel();
|
||||
out.package_url = s.url();
|
||||
out.platform = s.subdir();
|
||||
out.platform = s.platform();
|
||||
out.filename = s.file_name();
|
||||
out.license = s.license();
|
||||
out.size = s.size();
|
||||
|
@ -251,11 +255,11 @@ namespace mamba::solver::libsolv
|
|||
|
||||
if (auto subdir = pkg["subdir"].get_c_str(); !subdir.error())
|
||||
{
|
||||
solv.set_subdir(subdir.value_unsafe());
|
||||
solv.set_platform(subdir.value_unsafe());
|
||||
}
|
||||
else
|
||||
{
|
||||
solv.set_subdir(default_subdir);
|
||||
solv.set_platform(default_subdir);
|
||||
}
|
||||
|
||||
if (auto size = pkg["size"].get_uint64(); !size.error())
|
||||
|
@ -706,143 +710,31 @@ namespace mamba::solver::libsolv
|
|||
repo.set_pip_added(true);
|
||||
}
|
||||
|
||||
namespace
|
||||
auto
|
||||
make_abused_namespace_dep_args(solv::ObjPool& pool, std::string_view dependency, const MatchFlags& flags)
|
||||
-> std::pair<solv::StringId, solv::StringId>
|
||||
{
|
||||
auto
|
||||
channel_match(const std::vector<specs::Channel>& ms_channels, const specs::CondaURL& pkg_url)
|
||||
-> specs::Channel::Match
|
||||
{
|
||||
auto match = specs::Channel::Match::No;
|
||||
// More than one element means the channel spec was a custom_multi_channel
|
||||
for (const auto& chan : ms_channels)
|
||||
{
|
||||
switch (chan.contains_package(pkg_url))
|
||||
{
|
||||
case specs::Channel::Match::Full:
|
||||
return specs::Channel::Match::Full;
|
||||
case specs::Channel::Match::InOtherPlatform:
|
||||
// Keep looking for full matches
|
||||
match = specs::Channel::Match::InOtherPlatform;
|
||||
break;
|
||||
case specs::Channel::Match::No:
|
||||
// No overriding potential InOtherPlatform match
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return {
|
||||
pool.add_string(dependency),
|
||||
pool.add_string(flags.internal_serialize()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add function to handle matchspec while parsing is done by libsolv.
|
||||
*/
|
||||
auto add_channel_specific_matchspec(
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const specs::ChannelResolveParams& params
|
||||
) -> expected_t<solv::DependencyId>
|
||||
{
|
||||
assert(ms.channel().has_value());
|
||||
const std::string repr = ms.str();
|
||||
|
||||
// Already added, return that id
|
||||
if (const auto maybe_id = pool.find_string(repr))
|
||||
{
|
||||
return maybe_id.value();
|
||||
}
|
||||
|
||||
// conda_build_form does **NOT** contain the channel info
|
||||
const solv::DependencyId match_id = pool_conda_matchspec(
|
||||
pool.raw(),
|
||||
ms.conda_build_form().c_str()
|
||||
);
|
||||
|
||||
auto maybe_ms_channels = specs::Channel::resolve(*ms.channel(), params);
|
||||
if (!maybe_ms_channels)
|
||||
{
|
||||
return make_unexpected(
|
||||
fmt::format(R"(Failed to resolve channels in "{}")", ms.channel().value()),
|
||||
mamba_error_code::invalid_spec
|
||||
);
|
||||
}
|
||||
const auto& ms_channels = maybe_ms_channels.value();
|
||||
|
||||
solv::ObjQueue selected_pkgs = {};
|
||||
auto other_subdir_match = std::string();
|
||||
pool.for_each_whatprovides(
|
||||
match_id,
|
||||
[&](solv::ObjSolvableViewConst s)
|
||||
{
|
||||
if (s.installed())
|
||||
{
|
||||
// This will have the effect that channel-specific MatchSpec will always be
|
||||
// reinstalled.
|
||||
// This is not the intended behaviour but an historical artifact on which
|
||||
// ``--force-reinstall`` currently rely.
|
||||
return;
|
||||
}
|
||||
|
||||
assert(ms.channel().has_value());
|
||||
if (auto pkg_url = specs::CondaURL::parse(s.url()))
|
||||
{
|
||||
const auto match = channel_match(ms_channels, *pkg_url);
|
||||
switch (match)
|
||||
{
|
||||
case (specs::Channel::Match::Full):
|
||||
{
|
||||
selected_pkgs.push_back(s.id());
|
||||
break;
|
||||
}
|
||||
case (specs::Channel::Match::InOtherPlatform):
|
||||
{
|
||||
other_subdir_match = s.subdir();
|
||||
break;
|
||||
}
|
||||
case (specs::Channel::Match::No):
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (selected_pkgs.empty())
|
||||
{
|
||||
if (!other_subdir_match.empty())
|
||||
{
|
||||
const auto& filters = ms.channel()->platform_filters();
|
||||
throw std::runtime_error(fmt::format(
|
||||
R"(The package "{}" is not available for the specified platform{} ({}))"
|
||||
R"( but is available on {}.)",
|
||||
ms.str(),
|
||||
filters.size() > 1 ? "s" : "",
|
||||
fmt::join(filters, ", "),
|
||||
other_subdir_match
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(fmt::format(
|
||||
R"(The package "{}" is not found in any loaded channels.)"
|
||||
R"( Try adding more channels or subdirs.)",
|
||||
ms.str()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const solv::StringId repr_id = pool.add_string(repr);
|
||||
// FRAGILE This get deleted when calling ``pool_createwhatprovides`` so care
|
||||
// must be taken to do it before
|
||||
// TODO investigate namespace providers
|
||||
pool.add_to_whatprovides(repr_id, pool.add_to_whatprovides_data(selected_pkgs));
|
||||
return repr_id;
|
||||
}
|
||||
auto get_abused_namespace_callback_args( //
|
||||
solv::ObjPoolView& pool,
|
||||
solv::StringId name,
|
||||
solv::StringId ver
|
||||
) -> std::pair<std::string_view, MatchFlags>
|
||||
{
|
||||
return {
|
||||
pool.get_string(name),
|
||||
MatchFlags::internal_deserialize(pool.get_string(ver)),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto pool_add_matchspec( //
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const specs::ChannelResolveParams& params
|
||||
const specs::MatchSpec& ms
|
||||
) -> expected_t<solv::DependencyId>
|
||||
{
|
||||
auto check_not_zero = [&](solv::DependencyId id) -> expected_t<solv::DependencyId>
|
||||
|
@ -857,21 +749,17 @@ namespace mamba::solver::libsolv
|
|||
return id;
|
||||
};
|
||||
|
||||
if (!ms.channel().has_value())
|
||||
if (ms.is_simple())
|
||||
{
|
||||
return check_not_zero(pool.add_conda_dependency(ms.conda_build_form()));
|
||||
}
|
||||
|
||||
// Working around shortcomings of ``pool_conda_matchspec``
|
||||
// The channels are not processed.
|
||||
// TODO Fragile! Installing this matchspec will always trigger a reinstall
|
||||
return add_channel_specific_matchspec(pool, ms, params).and_then(check_not_zero);
|
||||
const auto [first, second] = make_abused_namespace_dep_args(pool, ms.str());
|
||||
return check_not_zero(pool.add_dependency(first, REL_NAMESPACE, second));
|
||||
}
|
||||
|
||||
auto pool_add_pin( //
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& pin,
|
||||
const specs::ChannelResolveParams& params
|
||||
const specs::MatchSpec& pin
|
||||
) -> expected_t<solv::ObjSolvableView>
|
||||
{
|
||||
// In libsolv, locking means that a package keeps the same state: if it is installed,
|
||||
|
@ -924,37 +812,36 @@ namespace mamba::solver::libsolv
|
|||
return repo;
|
||||
}();
|
||||
|
||||
return pool_add_matchspec(pool, pin, params)
|
||||
.transform(
|
||||
[&](solv::DependencyId cons)
|
||||
{
|
||||
// Add dummy solvable with a constraint on the pin (not installed if not
|
||||
// present)
|
||||
auto [cons_solv_id, cons_solv] = installed.add_solvable();
|
||||
const std::string cons_solv_name = fmt::format(
|
||||
"pin-{}",
|
||||
util::generate_random_alphanumeric_string(10)
|
||||
);
|
||||
cons_solv.set_name(cons_solv_name);
|
||||
cons_solv.set_version("1");
|
||||
return pool_add_matchspec(pool, pin).transform(
|
||||
[&](solv::DependencyId cons)
|
||||
{
|
||||
// Add dummy solvable with a constraint on the pin (not installed if not
|
||||
// present)
|
||||
auto [cons_solv_id, cons_solv] = installed.add_solvable();
|
||||
const std::string cons_solv_name = fmt::format(
|
||||
"pin-{}",
|
||||
util::generate_random_alphanumeric_string(10)
|
||||
);
|
||||
cons_solv.set_name(cons_solv_name);
|
||||
cons_solv.set_version("1");
|
||||
|
||||
cons_solv.add_constraint(cons);
|
||||
cons_solv.add_constraint(cons);
|
||||
|
||||
// Solvable need to provide itself
|
||||
cons_solv.add_self_provide();
|
||||
// Solvable need to provide itself
|
||||
cons_solv.add_self_provide();
|
||||
|
||||
// Even if we lock it, libsolv may still try to remove it with
|
||||
// `SOLVER_FLAG_ALLOW_UNINSTALL`, so we flag it as not a real package to filter
|
||||
// it out in the transaction
|
||||
cons_solv.set_type(solv::SolvableType::Pin);
|
||||
// Even if we lock it, libsolv may still try to remove it with
|
||||
// `SOLVER_FLAG_ALLOW_UNINSTALL`, so we flag it as not a real package to filter
|
||||
// it out in the transaction
|
||||
cons_solv.set_type(solv::SolvableType::Pin);
|
||||
|
||||
// Necessary for attributes to be properly stored
|
||||
// TODO move this at the end of all job requests
|
||||
installed.internalize();
|
||||
// Necessary for attributes to be properly stored
|
||||
// TODO move this at the end of all job requests
|
||||
installed.internalize();
|
||||
|
||||
return cons_solv;
|
||||
}
|
||||
);
|
||||
return cons_solv;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -1270,15 +1157,34 @@ namespace mamba::solver::libsolv
|
|||
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] auto add_reinstall_job(
|
||||
solv::ObjQueue& jobs,
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const specs::ChannelResolveParams& params
|
||||
) -> expected_t<void>
|
||||
[[nodiscard]] auto match_as_closely(solv::ObjSolvableViewConst s) -> specs::MatchSpec
|
||||
{
|
||||
static constexpr int install_flag = SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES;
|
||||
auto ms = specs::MatchSpec();
|
||||
ms.set_name(specs::MatchSpec::NameSpec(std::string(s.name())));
|
||||
// Ignoring version error, the point is to find a close match
|
||||
specs::Version::parse(s.version())
|
||||
.transform(
|
||||
[&](specs::Version&& ver)
|
||||
{
|
||||
ms.set_version(specs::VersionSpec::from_predicate(
|
||||
specs::VersionPredicate::make_equal_to(std::move(ver))
|
||||
));
|
||||
}
|
||||
);
|
||||
ms.set_build_string(specs::MatchSpec::BuildStringSpec(std::string(s.build_string())));
|
||||
ms.set_build_number(
|
||||
specs::BuildNumberSpec(specs::BuildNumberPredicate::make_equal_to(s.build_number()))
|
||||
);
|
||||
ms.set_md5(std::string(s.md5()));
|
||||
ms.set_sha256(std::string(s.sha256()));
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
add_reinstall_job(solv::ObjQueue& jobs, solv::ObjPool& pool, const specs::MatchSpec& ms)
|
||||
-> expected_t<void>
|
||||
{
|
||||
auto solvable = std::optional<solv::ObjSolvableViewConst>{};
|
||||
|
||||
// the data about the channel is only in the prefix_data unfortunately
|
||||
|
@ -1294,93 +1200,70 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
);
|
||||
|
||||
if (!solvable.has_value() || solvable->channel().empty())
|
||||
if (solvable.has_value())
|
||||
{
|
||||
// We are not reinstalling but simply installing.
|
||||
// Right now, using `--force-reinstall` will send all specs (whether they have
|
||||
// been previously installed or not) down this path, so we need to handle specs
|
||||
// that are not installed.
|
||||
return pool_add_matchspec(pool, ms, params)
|
||||
.transform([&](auto id) { jobs.push_back(install_flag, id); });
|
||||
}
|
||||
|
||||
if (ms.channel().has_value() || !ms.version().is_explicitly_free()
|
||||
|| !ms.build_string().is_free())
|
||||
{
|
||||
Console::stream() << ms.conda_build_form()
|
||||
<< ": overriding channel, version and build from "
|
||||
"installed packages due to --force-reinstall.";
|
||||
}
|
||||
|
||||
auto ms_modified = ms;
|
||||
auto unresolved_chan = specs::UnresolvedChannel::parse(solvable->channel());
|
||||
if (unresolved_chan.has_value())
|
||||
{
|
||||
ms_modified.set_channel(std::move(unresolved_chan).value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_unexpected(
|
||||
std::move(unresolved_chan).error().what(),
|
||||
mamba_error_code::invalid_spec
|
||||
);
|
||||
}
|
||||
auto version_spec = specs::VersionSpec::parse(solvable->version());
|
||||
if (version_spec.has_value())
|
||||
{
|
||||
ms_modified.set_version(std::move(version_spec).value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_unexpected(
|
||||
std::move(version_spec).error().what(),
|
||||
mamba_error_code::invalid_spec
|
||||
// To Reinstall, we add a install job with our custom namespace matcher,
|
||||
// passing a flag to exclude matching installed packages.
|
||||
// This has the effect of reinstalling in libsolv.
|
||||
const auto [first, second] = make_abused_namespace_dep_args(
|
||||
pool,
|
||||
match_as_closely(solvable.value()).str(),
|
||||
{ /* .skip_installed= */ true }
|
||||
);
|
||||
const auto job_id = pool.add_dependency(first, REL_NAMESPACE, second);
|
||||
jobs.push_back(SOLVER_INSTALL, job_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
ms_modified.set_build_string(specs::GlobSpec(std::string(solvable->build_string())));
|
||||
// We are not reinstalling but simply installing.
|
||||
return pool_add_matchspec(pool, ms).transform([&](auto id)
|
||||
{ jobs.push_back(SOLVER_INSTALL, id); });
|
||||
}
|
||||
|
||||
LOG_INFO << "Reinstall " << ms_modified.conda_build_form() << " from channel "
|
||||
<< ms_modified.channel()->str();
|
||||
// TODO Fragile! The only reason why this works is that with a channel specific
|
||||
// matchspec the job will always be reinstalled.
|
||||
return pool_add_matchspec(pool, ms_modified, params)
|
||||
.transform([&](auto id) { jobs.push_back(install_flag, id); });
|
||||
[[nodiscard]] auto has_installed_package( //
|
||||
const solv::ObjPool& pool,
|
||||
const specs::MatchSpec::NameSpec& name_spec
|
||||
) -> bool
|
||||
{
|
||||
bool found = false;
|
||||
pool.for_each_installed_solvable(
|
||||
[&](solv::ObjSolvableViewConst s)
|
||||
{
|
||||
if (name_spec.contains(s.name()))
|
||||
{
|
||||
found = true;
|
||||
return solv::LoopControl::Break;
|
||||
}
|
||||
return solv::LoopControl::Continue;
|
||||
}
|
||||
);
|
||||
return found;
|
||||
}
|
||||
|
||||
template <typename Job>
|
||||
[[nodiscard]] auto add_job(
|
||||
const Job& job,
|
||||
solv::ObjQueue& raw_jobs,
|
||||
solv::ObjPool& pool,
|
||||
const specs::ChannelResolveParams& params,
|
||||
bool force_reinstall
|
||||
) -> expected_t<void>
|
||||
[[nodiscard]] auto
|
||||
add_job(const Job& job, solv::ObjQueue& raw_jobs, solv::ObjPool& pool, bool force_reinstall)
|
||||
-> expected_t<void>
|
||||
{
|
||||
if constexpr (std::is_same_v<Job, Request::Install>)
|
||||
{
|
||||
if (force_reinstall)
|
||||
{
|
||||
return add_reinstall_job(raw_jobs, pool, job.spec, params);
|
||||
return add_reinstall_job(raw_jobs, pool, job.spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
return pool_add_matchspec(pool, job.spec, params)
|
||||
.transform(
|
||||
[&](auto id)
|
||||
{ raw_jobs.push_back(SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES, id); }
|
||||
);
|
||||
return pool_add_matchspec(pool, job.spec)
|
||||
.transform([&](auto id) { raw_jobs.push_back(SOLVER_INSTALL, id); });
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<Job, Request::Remove>)
|
||||
{
|
||||
return pool_add_matchspec(pool, job.spec, params)
|
||||
return pool_add_matchspec(pool, job.spec)
|
||||
.transform(
|
||||
[&](auto id)
|
||||
{
|
||||
[&](auto id) {
|
||||
raw_jobs.push_back(
|
||||
SOLVER_ERASE | SOLVER_SOLVABLE_PROVIDES
|
||||
| (job.clean_dependencies ? SOLVER_CLEANDEPS : 0),
|
||||
SOLVER_ERASE | (job.clean_dependencies ? SOLVER_CLEANDEPS : 0),
|
||||
id
|
||||
);
|
||||
}
|
||||
|
@ -1388,23 +1271,43 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
if constexpr (std::is_same_v<Job, Request::Update>)
|
||||
{
|
||||
return pool_add_matchspec(pool, job.spec, params)
|
||||
return pool_add_matchspec(pool, job.spec)
|
||||
.transform(
|
||||
[&](auto id)
|
||||
{
|
||||
// In libsolv update specs apply to installed packages, not available
|
||||
// ones, as opposed to mamba.
|
||||
// With ``numpy=0.5`` installed, update ``numpy>=1.0`` means update
|
||||
// numpy if a ``numpy>=1.0`` is installed, which would be false.
|
||||
// In Mamba, it means update any installed numpy to a new
|
||||
// ``numpy>=1.0``, leading to an update.
|
||||
// This is especially tricky with channel-specific MatchSpec.
|
||||
|
||||
auto const clean_deps = job.clean_dependencies ? SOLVER_CLEANDEPS : 0;
|
||||
// TODO: ignoring update specs here for now
|
||||
if (!job.spec.is_simple())
|
||||
|
||||
// In this case, libsolv and mamba meanings are the same.
|
||||
if (job.spec.is_only_package_name())
|
||||
{
|
||||
raw_jobs.push_back(
|
||||
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES | clean_deps,
|
||||
id
|
||||
);
|
||||
raw_jobs.push_back(SOLVER_UPDATE | clean_deps, id);
|
||||
}
|
||||
raw_jobs.push_back(
|
||||
SOLVER_UPDATE | SOLVER_SOLVABLE_PROVIDES | clean_deps,
|
||||
id
|
||||
);
|
||||
// Otherwise, we try our ad-hoc solution
|
||||
else if (has_installed_package(pool, job.spec.name()))
|
||||
{
|
||||
// We still need to issue an update command to libsolv, otherwise
|
||||
// the package won't be changed, but we apply it only to the
|
||||
// package name, not the full spec.
|
||||
if (job.spec.name().is_exact())
|
||||
{
|
||||
auto name_id = pool.add_string(job.spec.name().str());
|
||||
raw_jobs.push_back(SOLVER_UPDATE | clean_deps, name_id);
|
||||
}
|
||||
// And we add an install statement to be sure the full spec is
|
||||
// respected.
|
||||
// Unfortunately this breaks ``clean_deps``.
|
||||
raw_jobs.push_back(SOLVER_INSTALL, id);
|
||||
}
|
||||
// Finally there is no such package installed so we simply don't do
|
||||
// anything.
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1419,20 +1322,17 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
if constexpr (std::is_same_v<Job, Request::Freeze>)
|
||||
{
|
||||
return pool_add_matchspec(pool, job.spec, params)
|
||||
return pool_add_matchspec(pool, job.spec)
|
||||
.transform([&](auto id) { raw_jobs.push_back(SOLVER_LOCK, id); });
|
||||
}
|
||||
if constexpr (std::is_same_v<Job, Request::Keep>)
|
||||
{
|
||||
raw_jobs.push_back(
|
||||
SOLVER_USERINSTALLED,
|
||||
pool_add_matchspec(pool, job.spec, params).value()
|
||||
);
|
||||
raw_jobs.push_back(SOLVER_USERINSTALLED, pool_add_matchspec(pool, job.spec).value());
|
||||
return {};
|
||||
}
|
||||
if constexpr (std::is_same_v<Job, Request::Pin>)
|
||||
{
|
||||
return pool_add_pin(pool, job.spec, params)
|
||||
return pool_add_pin(pool, job.spec)
|
||||
.transform(
|
||||
[&](solv::ObjSolvableView pin_solv)
|
||||
{
|
||||
|
@ -1451,10 +1351,9 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
}
|
||||
|
||||
auto request_to_decision_queue(
|
||||
auto request_to_decision_queue( //
|
||||
const Request& request,
|
||||
solv::ObjPool& pool,
|
||||
const specs::ChannelResolveParams& chan_params,
|
||||
bool force_reinstall
|
||||
) -> expected_t<solv::ObjQueue>
|
||||
{
|
||||
|
@ -1468,7 +1367,7 @@ namespace mamba::solver::libsolv
|
|||
{
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(job)>, Request::Pin>)
|
||||
{
|
||||
return add_job(job, solv_jobs, pool, chan_params, force_reinstall);
|
||||
return add_job(job, solv_jobs, pool, force_reinstall);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
@ -1479,9 +1378,8 @@ namespace mamba::solver::libsolv
|
|||
return forward_error(std::move(xpt));
|
||||
}
|
||||
}
|
||||
// Fragile: Pins add solvables to Pol and hence require a call to create_whatprovides.
|
||||
// Channel specific MatchSpec write to whatprovides and hence require it is not modified
|
||||
// afterwards.
|
||||
// Pins add solvables to Pol and hence require a call to create_whatprovides.
|
||||
// For some reason we need to add them first.
|
||||
pool.create_whatprovides();
|
||||
for (const auto& unkown_job : request.jobs)
|
||||
{
|
||||
|
@ -1490,7 +1388,7 @@ namespace mamba::solver::libsolv
|
|||
{
|
||||
if constexpr (!std::is_same_v<std::decay_t<decltype(job)>, Request::Pin>)
|
||||
{
|
||||
return add_job(job, solv_jobs, pool, chan_params, force_reinstall);
|
||||
return add_job(job, solv_jobs, pool, force_reinstall);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
//
|
||||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#ifndef MAMBA_SOLVER_LIBSOLV_HERLPERS
|
||||
#define MAMBA_SOLVER_LIBSOLV_HERLPERS
|
||||
#ifndef MAMBA_SOLVER_LIBSOLV_HELPERS
|
||||
#define MAMBA_SOLVER_LIBSOLV_HELPERS
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
@ -23,6 +23,8 @@
|
|||
#include "solv-cpp/solvable.hpp"
|
||||
#include "solv-cpp/transaction.hpp"
|
||||
|
||||
#include "solver/libsolv/matcher.hpp"
|
||||
|
||||
/**
|
||||
* Solver, repo, and solvable helpers dependent on specifi libsolv logic and objects.
|
||||
*/
|
||||
|
@ -75,16 +77,38 @@ namespace mamba::solver::libsolv
|
|||
|
||||
void add_pip_as_python_dependency(solv::ObjPool& pool, solv::ObjRepoView repo);
|
||||
|
||||
/**
|
||||
* Make parameters to use as a namespace dependency.
|
||||
*
|
||||
* We use these proxy function since we are abusing the two string parameters of namespace
|
||||
* callback to pass our own information.
|
||||
*/
|
||||
[[nodiscard]] auto make_abused_namespace_dep_args(
|
||||
solv::ObjPool& pool,
|
||||
std::string_view dependency,
|
||||
const MatchFlags& flags = {}
|
||||
) -> std::pair<solv::StringId, solv::StringId>;
|
||||
|
||||
/**
|
||||
* Retrieved parameters used in a namespace callback.
|
||||
*
|
||||
* We use these proxy function since we are abusing the two string parameters of namespace
|
||||
* callback to pass our own information.
|
||||
*/
|
||||
[[nodiscard]] auto get_abused_namespace_callback_args( //
|
||||
solv::ObjPoolView& pool,
|
||||
solv::StringId first,
|
||||
solv::StringId second
|
||||
) -> std::pair<std::string_view, MatchFlags>;
|
||||
|
||||
[[nodiscard]] auto pool_add_matchspec( //
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const specs::ChannelResolveParams& params
|
||||
const specs::MatchSpec& ms
|
||||
) -> expected_t<solv::DependencyId>;
|
||||
|
||||
[[nodiscard]] auto pool_add_pin( //
|
||||
solv::ObjPool& pool,
|
||||
const specs::MatchSpec& pin_ms,
|
||||
const specs::ChannelResolveParams& params
|
||||
const specs::MatchSpec& pin_ms
|
||||
) -> expected_t<solv::ObjSolvableView>;
|
||||
|
||||
[[nodiscard]] auto transaction_to_solution_all( //
|
||||
|
@ -125,11 +149,8 @@ namespace mamba::solver::libsolv
|
|||
std::string_view noarch_type
|
||||
) -> Solution;
|
||||
|
||||
[[nodiscard]] auto request_to_decision_queue(
|
||||
const Request& request,
|
||||
solv::ObjPool& pool,
|
||||
const specs::ChannelResolveParams& chan_params,
|
||||
bool force_reinstall
|
||||
) -> expected_t<solv::ObjQueue>;
|
||||
[[nodiscard]] auto
|
||||
request_to_decision_queue(const Request& request, solv::ObjPool& pool, bool force_reinstall)
|
||||
-> expected_t<solv::ObjQueue>;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
// Copyright (c) 2024, 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 <fmt/format.h>
|
||||
|
||||
#include "solver/libsolv/matcher.hpp"
|
||||
|
||||
namespace mamba::solver::libsolv
|
||||
{
|
||||
/**********************************
|
||||
* Implementation of MatchFlags *
|
||||
**********************************/
|
||||
|
||||
auto MatchFlags::internal_deserialize(std::string_view in) -> MatchFlags
|
||||
{
|
||||
auto out = MatchFlags{};
|
||||
if (in.size() >= 1)
|
||||
{
|
||||
out.skip_installed = in[0] == '1';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void MatchFlags::internal_serialize_to(std::string& out) const
|
||||
{
|
||||
// We simply write a bitset for flags
|
||||
out.push_back(skip_installed ? '1' : '0');
|
||||
}
|
||||
|
||||
[[nodiscard]] auto MatchFlags::internal_serialize() const -> std::string
|
||||
{
|
||||
auto out = std::string();
|
||||
internal_serialize_to(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* Implementation of Matcher *
|
||||
*******************************/
|
||||
|
||||
Matcher::Matcher(specs::ChannelResolveParams channel_params)
|
||||
: m_channel_params(std::move(channel_params))
|
||||
{
|
||||
}
|
||||
|
||||
auto Matcher::channel_params() const -> const specs::ChannelResolveParams&
|
||||
{
|
||||
return m_channel_params;
|
||||
}
|
||||
|
||||
auto Matcher::get_matching_packages( //
|
||||
solv::ObjPoolView pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const MatchFlags& flags
|
||||
|
||||
) -> solv::OffsetId
|
||||
{
|
||||
m_packages_buffer.clear(); // Reuse the buffer
|
||||
|
||||
auto add_pkg_if_matching = [&](solv::ObjSolvableViewConst s)
|
||||
{
|
||||
if (flags.skip_installed && s.installed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkg_match_except_channel(pool, s, ms) && pkg_match_channels(s, ms))
|
||||
{
|
||||
m_packages_buffer.push_back(s.id());
|
||||
}
|
||||
};
|
||||
|
||||
if (ms.name().is_exact())
|
||||
{
|
||||
// Name does not have glob so we can use it as index into packages with exact name.
|
||||
auto name_id = pool.add_string(ms.name().str());
|
||||
pool.for_each_whatprovides(name_id, add_pkg_if_matching);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Name is a Glob (e.g. ``py*``) so we have to loop through all packages.
|
||||
pool.for_each_solvable(add_pkg_if_matching);
|
||||
}
|
||||
if (m_packages_buffer.empty())
|
||||
{
|
||||
return 0; // Means not found
|
||||
}
|
||||
return pool.add_to_whatprovides_data(m_packages_buffer);
|
||||
}
|
||||
|
||||
auto Matcher::get_matching_packages( //
|
||||
solv::ObjPoolView pool,
|
||||
std::string_view dep,
|
||||
const MatchFlags& flags
|
||||
) -> solv::OffsetId
|
||||
{
|
||||
return specs::MatchSpec::parse(dep)
|
||||
.transform([&](const specs::MatchSpec& ms)
|
||||
{ return get_matching_packages(pool, ms, flags); })
|
||||
.or_else(
|
||||
[&](const auto& error) -> specs::expected_parse_t<solv::OffsetId>
|
||||
{
|
||||
pool.set_current_error(error.what());
|
||||
return pool.add_to_whatprovides_data({});
|
||||
}
|
||||
)
|
||||
.value();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Map>
|
||||
[[nodiscard]] auto make_cached_version(Map& cache, std::string version)
|
||||
-> specs::expected_parse_t<std::reference_wrapper<const specs::Version>>
|
||||
{
|
||||
if (auto it = cache.find(version); it != cache.cend())
|
||||
{
|
||||
return { std::cref(it->second) };
|
||||
}
|
||||
if (version.empty())
|
||||
{
|
||||
auto [it, inserted] = cache.emplace(std::move(version), specs::Version());
|
||||
assert(inserted);
|
||||
return { std::cref(it->second) };
|
||||
}
|
||||
return specs::Version::parse(version).transform(
|
||||
[&](specs::Version&& ver) -> std::reference_wrapper<const specs::Version>
|
||||
{
|
||||
auto [it, inserted] = cache.emplace(std::move(version), std::move(ver));
|
||||
assert(inserted);
|
||||
return { std::cref(it->second) };
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
auto Matcher::get_pkg_attributes(solv::ObjPoolView pool, solv::ObjSolvableViewConst solv)
|
||||
-> expected_t<Pkg>
|
||||
{
|
||||
auto track_features = specs::MatchSpec::string_set();
|
||||
for (solv::StringId id : solv.track_features())
|
||||
{
|
||||
track_features.insert(std::string(pool.get_string(id)));
|
||||
}
|
||||
|
||||
return make_cached_version(m_version_cache, std::string(solv.version()))
|
||||
.transform(
|
||||
[&](auto ver_ref)
|
||||
{
|
||||
return Pkg{
|
||||
/* .name= */ solv.name(),
|
||||
/* .version= */ ver_ref,
|
||||
/* .build_string= */ solv.build_string(),
|
||||
/* .build_number= */ solv.build_number(),
|
||||
/* .md5= */ solv.md5(),
|
||||
/* .sha256= */ solv.sha256(),
|
||||
/* .license= */ solv.license(),
|
||||
/* .platform= */ std::string(solv.platform()),
|
||||
/* .track_features= */ std::move(track_features),
|
||||
};
|
||||
}
|
||||
)
|
||||
.transform_error( //
|
||||
[](specs::ParseError&& err)
|
||||
{ return mamba_error(err.what(), mamba_error_code::invalid_spec); }
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
auto Matcher::pkg_match_except_channel( //
|
||||
solv::ObjPoolView pool,
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const specs::MatchSpec& ms
|
||||
) -> bool
|
||||
{
|
||||
return get_pkg_attributes(pool, solv)
|
||||
.transform([&](const Pkg& pkg) -> bool { return ms.contains_except_channel(pkg); })
|
||||
.or_else([](const auto&) -> expected_t<bool> { return false; })
|
||||
.value();
|
||||
}
|
||||
|
||||
auto Matcher::get_channels(const specs::UnresolvedChannel& uc)
|
||||
-> expected_t<channel_list_const_ref>
|
||||
{
|
||||
// Channel maps require converting channel to string because unresolved channels are
|
||||
// akward to compare.
|
||||
auto str = uc.str();
|
||||
if (const auto it = m_channel_cache.find(str); it != m_channel_cache.end())
|
||||
{
|
||||
return { std::cref(it->second) };
|
||||
}
|
||||
|
||||
return specs::Channel::resolve(std::move(uc), channel_params())
|
||||
.transform(
|
||||
[&](channel_list&& chan)
|
||||
{
|
||||
auto [it, inserted] = m_channel_cache.emplace(std::move(str), std::move(chan));
|
||||
assert(inserted);
|
||||
return std::cref(it->second);
|
||||
}
|
||||
)
|
||||
.transform_error( //
|
||||
[](specs::ParseError&& err)
|
||||
{ return mamba_error(err.what(), mamba_error_code::invalid_spec); }
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
auto Matcher::get_channels(std::string_view chan) -> expected_t<channel_list_const_ref>
|
||||
{
|
||||
if (const auto it = m_channel_cache.find(std::string(chan)); it != m_channel_cache.end())
|
||||
{
|
||||
return { std::cref(it->second) };
|
||||
}
|
||||
|
||||
return specs::UnresolvedChannel::parse(chan)
|
||||
.transform_error( //
|
||||
[](specs::ParseError&& err)
|
||||
{ return mamba_error(err.what(), mamba_error_code::invalid_spec); }
|
||||
|
||||
)
|
||||
.and_then([&](specs::UnresolvedChannel&& uc) { return get_channels(uc); });
|
||||
}
|
||||
|
||||
auto Matcher::pkg_match_channels( //
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const channel_list& channels
|
||||
) -> bool
|
||||
{
|
||||
// First check the package url
|
||||
if (auto pkg_url = specs::CondaURL::parse(solv.url()))
|
||||
{
|
||||
for (const auto& chan : channels)
|
||||
{
|
||||
if (chan.contains_package(pkg_url.value()) == specs::Channel::Match::Full)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to package channel attribute
|
||||
else if (auto pkg_channels = get_channels(solv.channel()))
|
||||
{
|
||||
for (const auto& ms_chan : channels)
|
||||
{
|
||||
// There should really be only one here.
|
||||
for (const auto& pkg_chan : pkg_channels.value().get())
|
||||
{
|
||||
if (ms_chan.contains_equivalent(pkg_chan))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Matcher::pkg_match_channels( //
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const specs::MatchSpec& ms
|
||||
) -> bool
|
||||
{
|
||||
if (auto uc = ms.channel())
|
||||
{
|
||||
if (auto channels = get_channels(uc.value()))
|
||||
{
|
||||
return pkg_match_channels(solv, channels.value());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) 2024, 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.
|
||||
|
||||
#ifndef MAMBA_SOLVER_LIBSOLV_MATCHER
|
||||
#define MAMBA_SOLVER_LIBSOLV_MATCHER
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "mamba/core/error_handling.hpp"
|
||||
#include "mamba/specs/channel.hpp"
|
||||
#include "mamba/specs/match_spec.hpp"
|
||||
#include "mamba/specs/version.hpp"
|
||||
#include "solv-cpp/pool.hpp"
|
||||
#include "solv-cpp/solvable.hpp"
|
||||
|
||||
namespace mamba::solver::libsolv
|
||||
{
|
||||
struct MatchFlags
|
||||
{
|
||||
bool skip_installed = false;
|
||||
|
||||
/**
|
||||
* Deserialization for internal use mamba use, should not be loaded from disk.
|
||||
*/
|
||||
static auto internal_deserialize(std::string_view) -> MatchFlags;
|
||||
|
||||
/**
|
||||
* Serialization for internal use mamba use, should not be saved to disk.
|
||||
*/
|
||||
void internal_serialize_to(std::string& out) const;
|
||||
[[nodiscard]] auto internal_serialize() const -> std::string;
|
||||
};
|
||||
|
||||
class Matcher
|
||||
{
|
||||
public:
|
||||
|
||||
explicit Matcher(specs::ChannelResolveParams channel_params);
|
||||
|
||||
[[nodiscard]] auto channel_params() const -> const specs::ChannelResolveParams&;
|
||||
|
||||
auto get_matching_packages( //
|
||||
solv::ObjPoolView pool,
|
||||
const specs::MatchSpec& ms,
|
||||
const MatchFlags& flags = {}
|
||||
) -> solv::OffsetId;
|
||||
|
||||
auto get_matching_packages( //
|
||||
solv::ObjPoolView pool,
|
||||
std::string_view dep,
|
||||
const MatchFlags& flags = {}
|
||||
) -> solv::OffsetId;
|
||||
|
||||
private:
|
||||
|
||||
using channel_list = specs::ChannelResolveParams::channel_list;
|
||||
using channel_list_const_ref = std::reference_wrapper<const channel_list>;
|
||||
|
||||
struct Pkg
|
||||
{
|
||||
std::string_view name;
|
||||
std::reference_wrapper<const specs::Version> version;
|
||||
std::string_view build_string;
|
||||
std::size_t build_number;
|
||||
std::string_view md5;
|
||||
std::string_view sha256;
|
||||
std::string_view license;
|
||||
std::string platform;
|
||||
specs::MatchSpec::string_set track_features;
|
||||
};
|
||||
|
||||
auto get_pkg_attributes( //
|
||||
solv::ObjPoolView pool,
|
||||
solv::ObjSolvableViewConst solv
|
||||
) -> expected_t<Pkg>;
|
||||
|
||||
auto pkg_match_except_channel( //
|
||||
solv::ObjPoolView pool,
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const specs::MatchSpec& ms
|
||||
) -> bool;
|
||||
|
||||
auto get_channels(const specs::UnresolvedChannel& uc) -> expected_t<channel_list_const_ref>;
|
||||
auto get_channels(std::string_view chan) -> expected_t<channel_list_const_ref>;
|
||||
|
||||
auto pkg_match_channels( //
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const channel_list& channels
|
||||
) -> bool;
|
||||
|
||||
auto pkg_match_channels( //
|
||||
solv::ObjSolvableViewConst solv,
|
||||
const specs::MatchSpec& ms
|
||||
) -> bool;
|
||||
|
||||
specs::ChannelResolveParams m_channel_params;
|
||||
solv::ObjQueue m_packages_buffer = {};
|
||||
// No need for matchspec cache since they have the same string id they should be handled
|
||||
// by libsolv.
|
||||
std::unordered_map<std::string, specs::Version> m_version_cache = {};
|
||||
std::unordered_map<std::string, channel_list> m_channel_cache = {};
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -53,10 +53,9 @@ namespace mamba::solver::libsolv
|
|||
auto Solver::solve_impl(Database& mpool, const Request& request) -> expected_t<Outcome>
|
||||
{
|
||||
auto& pool = Database::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)
|
||||
return solver::libsolv::request_to_decision_queue(request, pool, flags.force_reinstall)
|
||||
.transform(
|
||||
[&](auto&& jobs) -> Outcome
|
||||
{
|
||||
|
|
|
@ -375,6 +375,7 @@ namespace mamba::solver::libsolv
|
|||
}
|
||||
case SOLVER_RULE_JOB_NOTHING_PROVIDES_DEP:
|
||||
case SOLVER_RULE_JOB_UNKNOWN_PACKAGE:
|
||||
case SOLVER_RULE_JOB_UNSUPPORTED:
|
||||
{
|
||||
// A top level dependency does not exist.
|
||||
// Could be a wrong name or missing channel.
|
||||
|
|
|
@ -941,8 +941,28 @@ namespace mamba::specs
|
|||
|
||||
auto MatchSpec::is_simple() const -> bool
|
||||
{
|
||||
return m_version.is_explicitly_free() && m_build_string.is_free()
|
||||
&& m_build_number.is_explicitly_free();
|
||||
// Based on what libsolv and conda_build_form can handle.
|
||||
// Glob in names and build_string are fine
|
||||
return (version().expression_size() <= 3) // includes op so e.g. ``>3,<4``
|
||||
&& build_number().is_explicitly_free() //
|
||||
&& !channel().has_value() //
|
||||
&& filename().empty() //
|
||||
&& !platforms().has_value() //
|
||||
&& name_space().empty() //
|
||||
&& md5().empty() //
|
||||
&& sha256().empty() //
|
||||
&& license().empty() //
|
||||
&& license_family().empty() //
|
||||
&& features().empty() //
|
||||
&& !track_features().has_value();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto MatchSpec::is_only_package_name() const -> bool
|
||||
{
|
||||
return name().is_exact() //
|
||||
&& version().is_explicitly_free() //
|
||||
&& build_string().is_free() //
|
||||
&& is_simple();
|
||||
}
|
||||
|
||||
auto MatchSpec::contains_except_channel(const PackageInfo& pkg) const -> bool
|
||||
|
|
|
@ -284,6 +284,13 @@ namespace mamba::specs
|
|||
* VersionSpec Implementation *
|
||||
********************************/
|
||||
|
||||
auto VersionSpec::from_predicate(VersionPredicate pred) -> VersionSpec
|
||||
{
|
||||
auto inner_tree = tree_type::tree_type();
|
||||
inner_tree.add_leaf(std::move(pred));
|
||||
return VersionSpec{ tree_type(std::move(inner_tree)) };
|
||||
}
|
||||
|
||||
VersionSpec::VersionSpec(tree_type&& tree) noexcept
|
||||
: m_tree(std::move(tree))
|
||||
{
|
||||
|
|
|
@ -103,6 +103,31 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
CHECK(std::holds_alternative<Solution::Install>(python_actions.front()));
|
||||
}
|
||||
|
||||
SUBCASE("Force reinstall not installed numpy")
|
||||
{
|
||||
auto flags = Request::Flags();
|
||||
flags.force_reinstall = true;
|
||||
const auto request = Request{
|
||||
/* .flags= */ std::move(flags),
|
||||
/* .jobs= */ { Request::Install{ "numpy"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
REQUIRE_FALSE(solution.actions.empty());
|
||||
// Numpy is last because of topological sort
|
||||
CHECK(std::holds_alternative<Solution::Install>(solution.actions.back()));
|
||||
CHECK_EQ(std::get<Solution::Install>(solution.actions.back()).install.name, "numpy");
|
||||
REQUIRE_EQ(find_actions_with_name(solution, "numpy").size(), 1);
|
||||
|
||||
const auto python_actions = find_actions_with_name(solution, "python");
|
||||
REQUIRE_EQ(python_actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Install>(python_actions.front()));
|
||||
}
|
||||
|
||||
SUBCASE("Install numpy without dependencies")
|
||||
{
|
||||
const auto request = Request{
|
||||
|
@ -281,6 +306,45 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Reinstall packages")
|
||||
{
|
||||
auto db = libsolv::Database({});
|
||||
|
||||
// A conda-forge/linux-64 subsample with one version of numpy and pip and their dependencies
|
||||
const auto repo_installed = db.add_repo_from_repodata_json(
|
||||
mambatests::test_data_dir / "repodata/conda-forge-numpy-linux-64.json",
|
||||
"installed",
|
||||
"installed"
|
||||
);
|
||||
REQUIRE(repo_installed.has_value());
|
||||
db.set_installed_repo(repo_installed.value());
|
||||
const auto repo = db.add_repo_from_repodata_json(
|
||||
mambatests::test_data_dir / "repodata/conda-forge-numpy-linux-64.json",
|
||||
"https://conda.anaconda.org/conda-forge/linux-64",
|
||||
"conda-forge"
|
||||
);
|
||||
REQUIRE(repo.has_value());
|
||||
|
||||
SUBCASE("Force reinstall numpy resinstalls it")
|
||||
{
|
||||
auto flags = Request::Flags();
|
||||
flags.force_reinstall = true;
|
||||
const auto request = Request{
|
||||
/* .flags= */ std::move(flags),
|
||||
/* .jobs= */ { Request::Install{ "numpy"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
REQUIRE_EQ(solution.actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Reinstall>(solution.actions.front()));
|
||||
CHECK_EQ(std::get<Solution::Reinstall>(solution.actions.front()).what.name, "numpy");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Solve a existing environment with one repository")
|
||||
{
|
||||
auto db = libsolv::Database({});
|
||||
|
@ -738,7 +802,7 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
pkg2.package_url = "https://conda.anaconda.org/mamba-forge/linux-64/foo-1.0.0-phony.conda";
|
||||
db.add_repo_from_packages(std::array{ pkg2 });
|
||||
|
||||
SUBCASE("conda-forge")
|
||||
SUBCASE("conda-forge::foo")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
|
@ -756,7 +820,7 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
CHECK_EQ(std::get<Solution::Install>(actions.front()).install.build_string, "conda");
|
||||
}
|
||||
|
||||
SUBCASE("mamba-forge")
|
||||
SUBCASE("mamba-forge::foo")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
|
@ -774,18 +838,20 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
CHECK_EQ(std::get<Solution::Install>(actions.front()).install.build_string, "mamba");
|
||||
}
|
||||
|
||||
SUBCASE("pixi-forge")
|
||||
SUBCASE("pixi-forge::foo")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "pixi-forge::foo"_ms } },
|
||||
};
|
||||
|
||||
// TODO should really be an unsolvable state
|
||||
CHECK_THROWS(libsolv::Solver().solve(db, request));
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
CHECK(std::holds_alternative<libsolv::UnSolvable>(outcome.value()));
|
||||
}
|
||||
|
||||
SUBCASE("https://conda.anaconda.org/mamba-forge/")
|
||||
SUBCASE("https://conda.anaconda.org/mamba-forge::foo")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
|
@ -814,36 +880,30 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
);
|
||||
REQUIRE(repo_linux.has_value());
|
||||
|
||||
const auto repo_win = db.add_repo_from_repodata_json(
|
||||
// FIXME the subdir is not overriden here so it is still linux-64 because that's what
|
||||
// is in the json file.
|
||||
// We'de want to pass option to the database to override channel and subsir.
|
||||
const auto repo_noarch = db.add_repo_from_repodata_json(
|
||||
mambatests::test_data_dir / "repodata/conda-forge-numpy-linux-64.json",
|
||||
"https://conda.anaconda.org/conda-forge/noarch",
|
||||
"conda-forge",
|
||||
libsolv::PipAsPythonDependency::No
|
||||
);
|
||||
REQUIRE(repo_win.has_value());
|
||||
REQUIRE(repo_noarch.has_value());
|
||||
|
||||
SUBCASE("conda-forge/noarch")
|
||||
SUBCASE("conda-forge/win-64::numpy")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "conda-forge/noarch::numpy"_ms } },
|
||||
/* .jobs= */ { Request::Install{ "conda-forge/win-64::numpy"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
const auto actions = find_actions_with_name(solution, "numpy");
|
||||
REQUIRE_EQ(actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Install>(actions.front()));
|
||||
CHECK(util::contains(
|
||||
std::get<Solution::Install>(actions.front()).install.package_url,
|
||||
"noarch"
|
||||
));
|
||||
REQUIRE(std::holds_alternative<libsolv::UnSolvable>(outcome.value()));
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge[subdir=linux-64]")
|
||||
SUBCASE("conda-forge::numpy[subdir=linux-64]")
|
||||
{
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
|
@ -865,4 +925,109 @@ TEST_SUITE("solver::libsolv::solver")
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Handle complex matchspecs")
|
||||
{
|
||||
using PackageInfo = specs::PackageInfo;
|
||||
|
||||
auto db = libsolv::Database({});
|
||||
|
||||
SUBCASE("*[md5=0bab699354cbd66959550eb9b9866620]")
|
||||
{
|
||||
auto pkg1 = PackageInfo("foo");
|
||||
pkg1.md5 = "0bab699354cbd66959550eb9b9866620";
|
||||
auto pkg2 = PackageInfo("foo");
|
||||
pkg2.md5 = "bad";
|
||||
|
||||
db.add_repo_from_packages(std::array{ pkg1, pkg2 });
|
||||
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "*[md5=0bab699354cbd66959550eb9b9866620]"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
REQUIRE_EQ(solution.actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Install>(solution.actions.front()));
|
||||
CHECK_EQ(
|
||||
std::get<Solution::Install>(solution.actions.front()).install.md5,
|
||||
"0bab699354cbd66959550eb9b9866620"
|
||||
);
|
||||
}
|
||||
|
||||
SUBCASE("foo[md5=notreallymd5]")
|
||||
{
|
||||
auto pkg1 = PackageInfo("foo");
|
||||
pkg1.md5 = "0bab699354cbd66959550eb9b9866620";
|
||||
|
||||
db.add_repo_from_packages(std::array{ pkg1 });
|
||||
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "foo[md5=notreallymd5]"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<libsolv::UnSolvable>(outcome.value()));
|
||||
}
|
||||
|
||||
SUBCASE("foo[build_string=bld]")
|
||||
{
|
||||
auto pkg1 = PackageInfo("foo");
|
||||
pkg1.build_string = "bad";
|
||||
auto pkg2 = PackageInfo("foo");
|
||||
pkg2.build_string = "bld";
|
||||
|
||||
db.add_repo_from_packages(std::array{ pkg1, pkg2 });
|
||||
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "foo[build=bld]"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
REQUIRE_EQ(solution.actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Install>(solution.actions.front()));
|
||||
CHECK_EQ(std::get<Solution::Install>(solution.actions.front()).install.build_string, "bld");
|
||||
}
|
||||
|
||||
SUBCASE("foo[build_string=bld, build_number='>2']")
|
||||
{
|
||||
auto pkg1 = PackageInfo("foo");
|
||||
pkg1.build_string = "bad";
|
||||
pkg1.build_number = 3;
|
||||
auto pkg2 = PackageInfo("foo");
|
||||
pkg2.build_string = "bld";
|
||||
pkg2.build_number = 2;
|
||||
auto pkg3 = PackageInfo("foo");
|
||||
pkg3.build_string = "bld";
|
||||
pkg3.build_number = 4;
|
||||
|
||||
db.add_repo_from_packages(std::array{ pkg1, pkg2, pkg3 });
|
||||
|
||||
auto request = Request{
|
||||
/* .flags= */ {},
|
||||
/* .jobs= */ { Request::Install{ "foo[build=bld]"_ms } },
|
||||
};
|
||||
const auto outcome = libsolv::Solver().solve(db, request);
|
||||
|
||||
REQUIRE(outcome.has_value());
|
||||
REQUIRE(std::holds_alternative<Solution>(outcome.value()));
|
||||
const auto& solution = std::get<Solution>(outcome.value());
|
||||
|
||||
REQUIRE_EQ(solution.actions.size(), 1);
|
||||
CHECK(std::holds_alternative<Solution::Install>(solution.actions.front()));
|
||||
CHECK_EQ(std::get<Solution::Install>(solution.actions.front()).install.build_string, "bld");
|
||||
CHECK_EQ(std::get<Solution::Install>(solution.actions.front()).install.build_number, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -467,34 +467,39 @@ TEST_SUITE("specs::match_spec")
|
|||
|
||||
TEST_CASE("is_simple")
|
||||
{
|
||||
SUBCASE("libblas")
|
||||
SUBCASE("Positive")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas").value();
|
||||
CHECK(ms.is_simple());
|
||||
for (std::string_view str : {
|
||||
"libblas",
|
||||
"libblas=12.9=abcdef",
|
||||
"libblas=0.15*",
|
||||
"libblas[version=12.2]",
|
||||
"xtensor =0.15*",
|
||||
})
|
||||
{
|
||||
CAPTURE(str);
|
||||
const auto ms = MatchSpec::parse(str).value();
|
||||
CHECK(ms.is_simple());
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("libblas=12.9=abcdef")
|
||||
SUBCASE("Negative")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas=12.9=abcdef").value();
|
||||
CHECK_FALSE(ms.is_simple());
|
||||
}
|
||||
|
||||
SUBCASE("libblas=0.15*")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas=0.15*").value();
|
||||
CHECK_FALSE(ms.is_simple());
|
||||
}
|
||||
|
||||
SUBCASE("libblas[version=12.2]")
|
||||
{
|
||||
auto ms = MatchSpec::parse("libblas[version=12.2]").value();
|
||||
CHECK_FALSE(ms.is_simple());
|
||||
}
|
||||
|
||||
SUBCASE("xtensor =0.15*")
|
||||
{
|
||||
auto ms = MatchSpec::parse("xtensor =0.15*").value();
|
||||
CHECK_FALSE(ms.is_simple());
|
||||
for (std::string_view str : {
|
||||
"pkg[build_number=3]",
|
||||
"pkg[md5=85094328554u9543215123]",
|
||||
"pkg[sha256=0320104934325453]",
|
||||
"pkg[license=MIT]",
|
||||
"pkg[track_features=mkl]",
|
||||
"pkg[version='(>2,<3)|=4']",
|
||||
"conda-forge::pkg",
|
||||
"pypi:pkg",
|
||||
})
|
||||
{
|
||||
CAPTURE(str);
|
||||
const auto ms = MatchSpec::parse(str).value();
|
||||
CHECK_FALSE(ms.is_simple());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -128,6 +128,16 @@ TEST_SUITE("specs::version_spec")
|
|||
CHECK_EQ(spec.str(), "=*");
|
||||
}
|
||||
|
||||
SUBCASE("from_predicate")
|
||||
{
|
||||
const auto v1 = "1.0"_v;
|
||||
const auto v2 = "2.0"_v;
|
||||
auto spec = VersionSpec::from_predicate(VersionPredicate::make_equal_to(v1));
|
||||
CHECK(spec.contains(v1));
|
||||
CHECK_FALSE(spec.contains(v2));
|
||||
CHECK_EQ(spec.str(), "==1.0");
|
||||
}
|
||||
|
||||
SUBCASE("<2.0|(>2.3,<=2.8.0)")
|
||||
{
|
||||
using namespace mamba::util;
|
||||
|
|
|
@ -587,7 +587,12 @@ namespace mambapy
|
|||
.def_readonly_static("glob_suffix_str", &VersionSpec::glob_suffix_str)
|
||||
.def_readonly_static("glob_suffix_token", &VersionSpec::glob_suffix_token)
|
||||
.def_static("parse", &VersionSpec::parse, py::arg("str"))
|
||||
.def_static("from_predicate", &VersionSpec::from_predicate, py::arg("pred"))
|
||||
.def(py::init<>())
|
||||
.def("contains", &VersionSpec::contains, py::arg("point"))
|
||||
.def("is_explicitly_free", &VersionSpec::is_explicitly_free)
|
||||
.def("expression_size", &VersionSpec::expression_size)
|
||||
.def("str_conda_build", &VersionSpec::str_conda_build)
|
||||
.def("__str__", &VersionSpec::str)
|
||||
.def("__copy__", ©<VersionSpec>)
|
||||
.def("__deepcopy__", &deepcopy<VersionSpec>, py::arg("memo"));
|
||||
|
@ -768,7 +773,7 @@ namespace mambapy
|
|||
std::string_view sha256,
|
||||
std::string_view license,
|
||||
std::string& platform,
|
||||
MatchSpec::string_set track_features)
|
||||
const MatchSpec::string_set& track_features)
|
||||
{
|
||||
struct Pkg
|
||||
{
|
||||
|
@ -780,7 +785,7 @@ namespace mambapy
|
|||
std::string_view sha256;
|
||||
std::string_view license;
|
||||
std::reference_wrapper<const std::string> platform;
|
||||
const MatchSpec::string_set track_features;
|
||||
std::reference_wrapper<const MatchSpec::string_set> track_features;
|
||||
};
|
||||
|
||||
return ms.contains_except_channel(Pkg{
|
||||
|
@ -792,7 +797,7 @@ namespace mambapy
|
|||
/* .sha256= */ sha256,
|
||||
/* .license= */ license,
|
||||
/* .platform= */ platform,
|
||||
/* .track_features= */ std::move(track_features),
|
||||
/* .track_features= */ track_features,
|
||||
});
|
||||
},
|
||||
py::arg("name") = "",
|
||||
|
@ -807,6 +812,7 @@ namespace mambapy
|
|||
)
|
||||
.def("is_file", &MatchSpec::is_file)
|
||||
.def("is_simple", &MatchSpec::is_simple)
|
||||
.def("is_only_package_name", &MatchSpec::is_only_package_name)
|
||||
.def("conda_build_form", &MatchSpec::conda_build_form)
|
||||
.def("__str__", &MatchSpec::str)
|
||||
.def("__copy__", ©<MatchSpec>)
|
||||
|
|
|
@ -257,9 +257,9 @@ def test_Solver_UnSolvable():
|
|||
outcome = solver.solve(db, request)
|
||||
|
||||
assert isinstance(outcome, libsolv.UnSolvable)
|
||||
assert "nothing provides" in "\n".join(outcome.problems(db))
|
||||
assert "nothing provides" in outcome.problems_to_str(db)
|
||||
assert "nothing provides" in outcome.all_problems_to_str(db)
|
||||
assert len(outcome.problems(db)) > 0
|
||||
assert isinstance(outcome.problems_to_str(db), str)
|
||||
assert isinstance(outcome.all_problems_to_str(db), str)
|
||||
assert "The following package could not be installed" in outcome.explain_problems(
|
||||
db, libmambapy.Palette.no_color()
|
||||
)
|
||||
|
|
|
@ -696,7 +696,15 @@ def test_VersionSpec():
|
|||
assert isinstance(VersionSpec.glob_suffix_str, str)
|
||||
assert isinstance(VersionSpec.glob_suffix_token, str)
|
||||
|
||||
# Constructor
|
||||
vs = VersionSpec()
|
||||
assert vs.is_explicitly_free()
|
||||
assert vs.expression_size() == 0
|
||||
|
||||
# Parse
|
||||
vs = VersionSpec.parse(">2.0,<3.0")
|
||||
assert not vs.is_explicitly_free()
|
||||
assert vs.expression_size() == 3 # including operator
|
||||
|
||||
# Errors
|
||||
with pytest.raises(libmambapy.specs.ParseError):
|
||||
|
@ -857,6 +865,7 @@ def test_MatchSpec():
|
|||
assert ms.optional
|
||||
assert not ms.is_file()
|
||||
assert not ms.is_simple()
|
||||
assert not ms.is_only_package_name()
|
||||
|
||||
# str
|
||||
assert str(ms) == (
|
||||
|
|
|
@ -691,10 +691,11 @@ def test_spec_with_channel_and_subdir():
|
|||
try:
|
||||
helpers.create("-n", env_name, "conda-forge/noarch::xtensor", "--dry-run")
|
||||
except subprocess.CalledProcessError as e:
|
||||
assert (
|
||||
'critical libmamba The package "conda-forge[noarch]::xtensor" is '
|
||||
"not available for the specified platform (noarch) but is available on"
|
||||
) in e.stderr.decode()
|
||||
# The error message we are getting today is not the most informative but
|
||||
# was needed to unify the solver interface.
|
||||
msg = e.stderr.decode()
|
||||
assert "The following package could not be installed" in msg
|
||||
assert "xtensor" in msg
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
|
||||
|
@ -708,10 +709,11 @@ def test_spec_with_slash_in_channel(tmp_home, tmp_root_prefix):
|
|||
with pytest.raises(subprocess.CalledProcessError) as info:
|
||||
helpers.create("-n", "env1", "pkgs/main/noarch::python", "--dry-run")
|
||||
|
||||
assert info.value.stderr.decode() == (
|
||||
'critical libmamba The package "pkgs/main[noarch]::python" is '
|
||||
"not found in any loaded channels. Try adding more channels or subdirs.\n"
|
||||
)
|
||||
# The error message we are getting today is not the most informative but
|
||||
# was needed to unify the solver interface.
|
||||
msg = info.value.stderr.decode()
|
||||
assert "The following package could not be installed" in msg
|
||||
assert "python" in msg
|
||||
|
||||
os.environ["CONDA_SUBDIR"] = "linux-64"
|
||||
helpers.create("-n", "env2", "pkgs/main/linux-64::python", "--dry-run")
|
||||
|
|
Loading…
Reference in New Issue