mirror of https://github.com/mamba-org/mamba.git
parent
e49116bfff
commit
e749bbe18b
|
@ -196,6 +196,9 @@ set(
|
|||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/repo_info.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/solver.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/libsolv/unsolvable.cpp
|
||||
# Solver resolvo implementation
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/resolvo/database.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/solver/resolvo/solver.cpp
|
||||
# Artifacts validation
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/errors.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/keys.cpp
|
||||
|
@ -349,6 +352,9 @@ set(
|
|||
${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/repo_info.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/solver.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/unsolvable.hpp
|
||||
# Solver resolvo implementation
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/solver/resolvo/database.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/solver/resolvo/solver.hpp
|
||||
# Artifacts validation
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/errors.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/keys.hpp
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define MAMBA_API_CHANNEL_LOADER_HPP
|
||||
|
||||
#include "mamba/core/error_handling.hpp"
|
||||
#include "mamba/solver/resolvo/database.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
@ -15,6 +16,11 @@ namespace mamba
|
|||
{
|
||||
class Database;
|
||||
}
|
||||
|
||||
namespace solver::resolvo
|
||||
{
|
||||
class Database;
|
||||
}
|
||||
class Context;
|
||||
class ChannelContext;
|
||||
class MultiPackageCache;
|
||||
|
@ -30,7 +36,7 @@ namespace mamba
|
|||
auto load_channels(
|
||||
Context& ctx,
|
||||
ChannelContext& channel_context,
|
||||
solver::libsolv::Database& database,
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database>& database,
|
||||
MultiPackageCache& package_caches
|
||||
) -> expected_t<void, mamba_aggregated_error>;
|
||||
|
||||
|
|
|
@ -257,6 +257,8 @@ namespace mamba
|
|||
bool repodata_use_zst = true;
|
||||
std::vector<std::string> repodata_has_zst = { "https://conda.anaconda.org/conda-forge" };
|
||||
|
||||
bool use_resolvo_solver = false;
|
||||
|
||||
// FIXME: Should not be stored here
|
||||
// Notice that we cannot build this map directly from mirrored_channels,
|
||||
// since we need to add a single "mirror" for non mirrored channels
|
||||
|
|
|
@ -9,19 +9,24 @@
|
|||
|
||||
#include "mamba/core/error_handling.hpp"
|
||||
#include "mamba/solver/libsolv/repo_info.hpp"
|
||||
#include "mamba/solver/resolvo/database.hpp"
|
||||
#include "mamba/specs/channel.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
class Context;
|
||||
class PrefixData;
|
||||
class SubdirData;
|
||||
|
||||
namespace solver::libsolv
|
||||
{
|
||||
class Database;
|
||||
}
|
||||
|
||||
namespace solver::resolvo
|
||||
{
|
||||
class Database;
|
||||
}
|
||||
class Context;
|
||||
class PrefixData;
|
||||
class SubdirData;
|
||||
|
||||
void add_spdlog_logger_to_database(solver::libsolv::Database& database);
|
||||
|
||||
auto load_subdir_in_database( //
|
||||
|
@ -32,8 +37,8 @@ namespace mamba
|
|||
|
||||
auto load_installed_packages_in_database(
|
||||
const Context& ctx,
|
||||
solver::libsolv::Database& database,
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database>& database,
|
||||
const PrefixData& prefix
|
||||
) -> solver::libsolv::RepoInfo;
|
||||
) -> expected_t<void>;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// 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_DATABASE_HPP
|
||||
#define MAMBA_SOLVER_DATABASE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/specs/channel.hpp"
|
||||
#include "mamba/specs/package_info.hpp"
|
||||
|
||||
namespace mamba::solver
|
||||
{
|
||||
class Database
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~Database() = default;
|
||||
|
||||
virtual void add_repo_from_repodata_json(
|
||||
const fs::u8path& filename,
|
||||
const std::string& repo_url,
|
||||
const std::string& channel_id,
|
||||
bool verify_artifacts = false
|
||||
) = 0;
|
||||
|
||||
virtual void add_repo_from_packages(
|
||||
const std::vector<specs::PackageInfo>& packages,
|
||||
const std::string& repo_name,
|
||||
bool pip_as_python_dependency = false
|
||||
) = 0;
|
||||
|
||||
virtual void set_installed_repo(const std::string& repo_name) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MAMBA_SOLVER_DATABASE_HPP
|
|
@ -0,0 +1,262 @@
|
|||
// 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_RESOLVO_DATABASE_HPP
|
||||
#define MAMBA_SOLVER_RESOLVO_DATABASE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <resolvo/resolvo.h>
|
||||
#include <resolvo/resolvo_dependency_provider.h>
|
||||
#include <resolvo/resolvo_pool.h>
|
||||
|
||||
#include "mamba/solver/database.hpp"
|
||||
#include "mamba/specs/match_spec.hpp"
|
||||
#include "mamba/specs/package_info.hpp"
|
||||
#include "mamba/specs/version.hpp"
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<::resolvo::NameId>
|
||||
{
|
||||
size_t operator()(const ::resolvo::NameId& id) const noexcept
|
||||
{
|
||||
return static_cast<size_t>(id.id);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<::resolvo::VersionSetId>
|
||||
{
|
||||
size_t operator()(const ::resolvo::VersionSetId& id) const noexcept
|
||||
{
|
||||
return static_cast<size_t>(id.id);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<::resolvo::SolvableId>
|
||||
{
|
||||
size_t operator()(const ::resolvo::SolvableId& id) const noexcept
|
||||
{
|
||||
return static_cast<size_t>(id.id);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<::resolvo::StringId>
|
||||
{
|
||||
size_t operator()(const ::resolvo::StringId& id) const noexcept
|
||||
{
|
||||
return static_cast<size_t>(id.id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mamba::solver::resolvo
|
||||
{
|
||||
// Create a template Pool class that maps a key to a set of values
|
||||
template <typename ID, typename T>
|
||||
struct Mapping
|
||||
{
|
||||
/**
|
||||
* Adds the value to the Mapping and returns its associated id. If the
|
||||
* value is already in the Mapping, returns the id associated with it.
|
||||
*/
|
||||
ID alloc(T value)
|
||||
{
|
||||
if (auto element = value_to_id.find(value); element != value_to_id.end())
|
||||
{
|
||||
return element->second;
|
||||
}
|
||||
auto id = ID{ static_cast<uint32_t>(id_to_value.size()) };
|
||||
id_to_value[id] = value;
|
||||
value_to_id[value] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given id.
|
||||
*/
|
||||
T operator[](ID id)
|
||||
{
|
||||
return id_to_value[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id associated with the given value.
|
||||
*/
|
||||
ID operator[](T value)
|
||||
{
|
||||
return value_to_id[value];
|
||||
}
|
||||
|
||||
// Iterator for the Mapping
|
||||
auto begin()
|
||||
{
|
||||
return id_to_value.begin();
|
||||
}
|
||||
|
||||
auto end()
|
||||
{
|
||||
return id_to_value.end();
|
||||
}
|
||||
|
||||
auto begin() const
|
||||
{
|
||||
return id_to_value.begin();
|
||||
}
|
||||
|
||||
auto end() const
|
||||
{
|
||||
return id_to_value.end();
|
||||
}
|
||||
|
||||
auto cbegin()
|
||||
{
|
||||
return id_to_value.cbegin();
|
||||
}
|
||||
|
||||
auto cend()
|
||||
{
|
||||
return id_to_value.cend();
|
||||
}
|
||||
|
||||
auto cbegin() const
|
||||
{
|
||||
return id_to_value.cbegin();
|
||||
}
|
||||
|
||||
auto cend() const
|
||||
{
|
||||
return id_to_value.cend();
|
||||
}
|
||||
|
||||
auto find(T value)
|
||||
{
|
||||
return value_to_id.find(value);
|
||||
}
|
||||
|
||||
auto begin_ids()
|
||||
{
|
||||
return value_to_id.begin();
|
||||
}
|
||||
|
||||
auto end_ids()
|
||||
{
|
||||
return value_to_id.end();
|
||||
}
|
||||
|
||||
auto begin_ids() const
|
||||
{
|
||||
return value_to_id.begin();
|
||||
}
|
||||
|
||||
auto end_ids() const
|
||||
{
|
||||
return value_to_id.end();
|
||||
}
|
||||
|
||||
auto cbegin_ids()
|
||||
{
|
||||
return value_to_id.cbegin();
|
||||
}
|
||||
|
||||
auto cend_ids()
|
||||
{
|
||||
return value_to_id.cend();
|
||||
}
|
||||
|
||||
auto cbegin_ids() const
|
||||
{
|
||||
return value_to_id.cbegin();
|
||||
}
|
||||
|
||||
auto cend_ids() const
|
||||
{
|
||||
return value_to_id.cend();
|
||||
}
|
||||
|
||||
auto size() const
|
||||
{
|
||||
return id_to_value.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::unordered_map<T, ID> value_to_id;
|
||||
std::unordered_map<ID, T> id_to_value;
|
||||
};
|
||||
|
||||
class Database
|
||||
: public mamba::solver::Database
|
||||
, public ::resolvo::DependencyProvider
|
||||
{
|
||||
public:
|
||||
|
||||
Database();
|
||||
~Database() override = default;
|
||||
|
||||
// Implementation of mamba::solver::Database interface
|
||||
void add_repo_from_repodata_json(
|
||||
const fs::u8path& filename,
|
||||
const std::string& repo_url,
|
||||
const std::string& channel_id,
|
||||
bool verify_artifacts = false
|
||||
) override;
|
||||
|
||||
void add_repo_from_packages(
|
||||
const std::vector<specs::PackageInfo>& packages,
|
||||
const std::string& repo_name,
|
||||
bool pip_as_python_dependency = false
|
||||
) override;
|
||||
|
||||
void set_installed_repo(const std::string& repo_name) override;
|
||||
|
||||
// Implementation of resolvo::DependencyProvider interface
|
||||
::resolvo::String display_solvable(::resolvo::SolvableId solvable) override;
|
||||
::resolvo::String display_solvable_name(::resolvo::SolvableId solvable) override;
|
||||
::resolvo::String
|
||||
display_merged_solvables(::resolvo::Slice<::resolvo::SolvableId> solvable) override;
|
||||
::resolvo::String display_name(::resolvo::NameId name) override;
|
||||
::resolvo::String display_version_set(::resolvo::VersionSetId version_set) override;
|
||||
::resolvo::String display_string(::resolvo::StringId string) override;
|
||||
::resolvo::NameId version_set_name(::resolvo::VersionSetId version_set_id) override;
|
||||
::resolvo::NameId solvable_name(::resolvo::SolvableId solvable_id) override;
|
||||
::resolvo::Candidates get_candidates(::resolvo::NameId package) override;
|
||||
void sort_candidates(::resolvo::Slice<::resolvo::SolvableId> solvables) override;
|
||||
::resolvo::Vector<::resolvo::SolvableId> filter_candidates(
|
||||
::resolvo::Slice<::resolvo::SolvableId> candidates,
|
||||
::resolvo::VersionSetId version_set_id,
|
||||
bool inverse
|
||||
) override;
|
||||
::resolvo::Dependencies get_dependencies(::resolvo::SolvableId solvable_id) override;
|
||||
|
||||
// Public access to pools and helper methods
|
||||
::resolvo::VersionSetId alloc_version_set(std::string_view raw_match_spec);
|
||||
::resolvo::SolvableId alloc_solvable(specs::PackageInfo package_info);
|
||||
std::pair<specs::Version, size_t>
|
||||
find_highest_version(::resolvo::VersionSetId version_set_id);
|
||||
|
||||
// Pools for mapping between resolvo IDs and mamba types
|
||||
Mapping<::resolvo::NameId, ::resolvo::String> name_pool;
|
||||
Mapping<::resolvo::StringId, ::resolvo::String> string_pool;
|
||||
Mapping<::resolvo::VersionSetId, specs::MatchSpec> version_set_pool;
|
||||
Mapping<::resolvo::SolvableId, specs::PackageInfo> solvable_pool;
|
||||
|
||||
private:
|
||||
|
||||
// Maps for quick lookups
|
||||
std::unordered_map<::resolvo::NameId, ::resolvo::Vector<::resolvo::SolvableId>> name_to_solvable;
|
||||
std::unordered_map<::resolvo::VersionSetId, std::pair<specs::Version, size_t>>
|
||||
version_set_to_max_version_and_track_features_numbers;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MAMBA_SOLVER_RESOLVO_DATABASE_HPP
|
|
@ -0,0 +1,32 @@
|
|||
// 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_RESOLVO_SOLVER_HPP
|
||||
#define MAMBA_SOLVER_RESOLVO_SOLVER_HPP
|
||||
|
||||
#include "mamba/core/error_handling.hpp"
|
||||
#include "mamba/solver/request.hpp"
|
||||
#include "mamba/solver/solution.hpp"
|
||||
|
||||
namespace mamba::solver::resolvo
|
||||
{
|
||||
class Database;
|
||||
|
||||
class Solver
|
||||
{
|
||||
public:
|
||||
|
||||
using Outcome = std::variant<Solution>;
|
||||
|
||||
[[nodiscard]] auto solve(Database& database, Request&& request) -> expected_t<Outcome>;
|
||||
[[nodiscard]] auto solve(Database& database, const Request& request) -> expected_t<Outcome>;
|
||||
|
||||
private:
|
||||
|
||||
auto solve_impl(Database& database, const Request& request) -> expected_t<Outcome>;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,39 @@
|
|||
// 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_SOLVER_FACTORY_HPP
|
||||
#define MAMBA_SOLVER_SOLVER_FACTORY_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mamba/core/context.hpp"
|
||||
#include "mamba/solver/libsolv/database.hpp"
|
||||
#include "mamba/solver/libsolv/solver.hpp"
|
||||
#include "mamba/solver/resolvo/database.hpp"
|
||||
#include "mamba/solver/resolvo/solver.hpp"
|
||||
|
||||
namespace mamba::solver
|
||||
{
|
||||
/**
|
||||
* Create a solver based on the configuration.
|
||||
*
|
||||
* @param ctx The context containing the configuration.
|
||||
* @return A unique pointer to the appropriate solver.
|
||||
*/
|
||||
template <typename Database>
|
||||
auto create_solver(const Context& ctx)
|
||||
{
|
||||
if (ctx.use_resolvo_solver)
|
||||
{
|
||||
return std::make_unique<resolvo::Solver>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_unique<libsolv::Solver>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -125,7 +125,7 @@ namespace mamba
|
|||
auto load_channels_impl(
|
||||
Context& ctx,
|
||||
ChannelContext& channel_context,
|
||||
solver::libsolv::Database& database,
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database>& database,
|
||||
MultiPackageCache& package_caches,
|
||||
bool is_retry
|
||||
) -> expected_t<void, mamba_aggregated_error>
|
||||
|
@ -196,7 +196,14 @@ namespace mamba
|
|||
|
||||
if (!packages.empty())
|
||||
{
|
||||
database.add_repo_from_packages(packages, "packages");
|
||||
if (auto* libsolv_db = std::get_if<solver::libsolv::Database>(&database))
|
||||
{
|
||||
libsolv_db->add_repo_from_packages(packages, "packages");
|
||||
}
|
||||
else if (auto* resolvo_db = std::get_if<solver::resolvo::Database>(&database))
|
||||
{
|
||||
resolvo_db->add_repo_from_packages(packages, "packages");
|
||||
}
|
||||
}
|
||||
|
||||
expected_t<void> download_res;
|
||||
|
@ -243,7 +250,15 @@ namespace mamba
|
|||
LOG_INFO << "Creating repo from pkgs_dir for offline";
|
||||
for (const auto& c : ctx.pkgs_dirs)
|
||||
{
|
||||
create_repo_from_pkgs_dir(ctx, channel_context, database, c);
|
||||
if (auto* libsolv_db = std::get_if<solver::libsolv::Database>(&database))
|
||||
{
|
||||
create_repo_from_pkgs_dir(ctx, channel_context, *libsolv_db, c);
|
||||
}
|
||||
else if (auto* resolvo_db = std::get_if<solver::resolvo::Database>(&database))
|
||||
{
|
||||
// TODO: Implement this for resolvo
|
||||
throw std::runtime_error("Offline mode not supported with resolvo solver yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string prev_channel;
|
||||
|
@ -251,43 +266,41 @@ namespace mamba
|
|||
for (std::size_t i = 0; i < subdirs.size(); ++i)
|
||||
{
|
||||
auto& subdir = subdirs[i];
|
||||
if (!subdir.valid_cache_found())
|
||||
auto channel = subdir.channel();
|
||||
if (channel != prev_channel)
|
||||
{
|
||||
if (!ctx.offline && subdir.is_noarch())
|
||||
prev_channel = channel;
|
||||
if (i != 0)
|
||||
{
|
||||
error_list.push_back(mamba_error(
|
||||
"Subdir " + subdir.name() + " not loaded!",
|
||||
mamba_error_code::subdirdata_not_loaded
|
||||
));
|
||||
Console::instance().print("\n");
|
||||
}
|
||||
continue;
|
||||
Console::instance().print(fmt::format("{} {}", channel, subdir.platform()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console::instance().print(fmt::format(" {}", subdir.platform()));
|
||||
}
|
||||
|
||||
load_subdir_in_database(ctx, database, subdir)
|
||||
.transform([&](solver::libsolv::RepoInfo&& repo)
|
||||
{ database.set_repo_priority(repo, priorities[i]); })
|
||||
.or_else(
|
||||
[&](const auto&)
|
||||
{
|
||||
if (is_retry)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Could not load repodata.json for " << subdir.name()
|
||||
<< " after retry." << "Please check repodata source. Exiting."
|
||||
<< std::endl;
|
||||
error_list.push_back(
|
||||
mamba_error(ss.str(), mamba_error_code::repodata_not_loaded)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << "Could not load repodata.json for " << subdir.name()
|
||||
<< ". Deleting cache, and retrying.";
|
||||
subdir.clear_cache_files();
|
||||
loading_failed = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
if (auto* libsolv_db = std::get_if<solver::libsolv::Database>(&database))
|
||||
{
|
||||
auto exp_repo = load_subdir_in_database(ctx, *libsolv_db, subdir);
|
||||
if (!exp_repo)
|
||||
{
|
||||
error_list.push_back(exp_repo.error());
|
||||
loading_failed = true;
|
||||
continue;
|
||||
}
|
||||
auto repo = exp_repo.value();
|
||||
if (i < priorities.size())
|
||||
{
|
||||
libsolv_db->set_repo_priority(repo, priorities[i]);
|
||||
}
|
||||
}
|
||||
else if (auto* resolvo_db = std::get_if<solver::resolvo::Database>(&database))
|
||||
{
|
||||
// TODO: Implement this for resolvo
|
||||
throw std::runtime_error("Loading subdirs not supported with resolvo solver yet");
|
||||
}
|
||||
}
|
||||
|
||||
if (loading_failed)
|
||||
|
@ -312,7 +325,7 @@ namespace mamba
|
|||
auto load_channels(
|
||||
Context& ctx,
|
||||
ChannelContext& channel_context,
|
||||
solver::libsolv::Database& database,
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database>& database,
|
||||
MultiPackageCache& package_caches
|
||||
) -> expected_t<void, mamba_aggregated_error>
|
||||
{
|
||||
|
|
|
@ -1259,10 +1259,7 @@ namespace mamba
|
|||
out << YAML::Comment(std::string(54, '#'));
|
||||
}
|
||||
|
||||
void dump_configurable(nl::json& node, const Configurable& c, const std::string& name)
|
||||
{
|
||||
c.dump_json(node, name);
|
||||
}
|
||||
void dump_configurable(nl::json& node, const Configurable& c, const std::string& name);
|
||||
}
|
||||
|
||||
/********************************
|
||||
|
@ -1630,6 +1627,17 @@ namespace mamba
|
|||
}
|
||||
));
|
||||
|
||||
insert(Configurable("experimental_resolvo_solver", false)
|
||||
.group("Solver")
|
||||
.set_rc_configurable()
|
||||
.set_env_var_names()
|
||||
.description("Use the experimental resolvo solver instead of libsolv")
|
||||
.long_description(unindent(R"(
|
||||
When enabled, use the experimental resolvo solver instead of libsolv.
|
||||
This is an experimental feature and may not be fully functional.)"))
|
||||
.set_post_merge_hook<bool>([&](bool& value)
|
||||
{ m_context.use_resolvo_solver = value; }));
|
||||
|
||||
insert(Configurable("explicit_install", false)
|
||||
.group("Solver")
|
||||
.description("Use explicit install instead of solving environment"));
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "mamba/download/downloader.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/solver/libsolv/solver.hpp"
|
||||
#include "mamba/solver/resolvo/solver.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
|
@ -471,14 +472,22 @@ namespace mamba
|
|||
LOG_WARNING << "No 'channels' specified";
|
||||
}
|
||||
|
||||
solver::libsolv::Database db{
|
||||
channel_context.params(),
|
||||
{
|
||||
ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba
|
||||
: solver::libsolv::MatchSpecParser::Libsolv,
|
||||
},
|
||||
};
|
||||
add_spdlog_logger_to_database(db);
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database> db;
|
||||
if (ctx.use_resolvo_solver)
|
||||
{
|
||||
db.emplace<solver::resolvo::Database>();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.emplace<solver::libsolv::Database>(
|
||||
channel_context.params(),
|
||||
{
|
||||
ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba
|
||||
: solver::libsolv::MatchSpecParser::Libsolv,
|
||||
},
|
||||
);
|
||||
add_spdlog_logger_to_database(std::get<solver::libsolv::Database>(db));
|
||||
}
|
||||
|
||||
auto exp_load = load_channels(ctx, channel_context, db, package_caches);
|
||||
if (!exp_load)
|
||||
|
@ -495,7 +504,6 @@ namespace mamba
|
|||
|
||||
load_installed_packages_in_database(ctx, db, prefix_data);
|
||||
|
||||
|
||||
auto request = create_install_request(prefix_data, raw_specs, freeze_installed);
|
||||
add_pins_to_request(request, ctx, prefix_data, raw_specs, no_pin, no_py_pin);
|
||||
request.flags = ctx.solver_flags;
|
||||
|
@ -506,20 +514,40 @@ namespace mamba
|
|||
// Console stream prints on destruction
|
||||
}
|
||||
|
||||
auto outcome = solver::libsolv::Solver()
|
||||
.solve(
|
||||
db,
|
||||
request,
|
||||
ctx.experimental_matchspec_parsing
|
||||
? solver::libsolv::MatchSpecParser::Mamba
|
||||
: solver::libsolv::MatchSpecParser::Mixed
|
||||
)
|
||||
.value();
|
||||
auto outcome =
|
||||
[&]() -> expected_t<std::variant<solver::Solution, solver::libsolv::UnSolvable>>
|
||||
{
|
||||
if (ctx.use_resolvo_solver)
|
||||
{
|
||||
auto resolvo_outcome = solver::resolvo::Solver().solve(
|
||||
std::get<solver::resolvo::Database>(db),
|
||||
request
|
||||
);
|
||||
if (!resolvo_outcome)
|
||||
{
|
||||
return make_unexpected(resolvo_outcome.error());
|
||||
}
|
||||
return std::get<solver::Solution>(resolvo_outcome.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return solver::libsolv::Solver().solve(
|
||||
std::get<solver::libsolv::Database>(db),
|
||||
request,
|
||||
ctx.experimental_matchspec_parsing ? solver::libsolv::MatchSpecParser::Mamba : solver::libsolv::MatchSpecParser::Mixed
|
||||
);
|
||||
}
|
||||
}();
|
||||
|
||||
if (auto* unsolvable = std::get_if<solver::libsolv::UnSolvable>(&outcome))
|
||||
if (!outcome)
|
||||
{
|
||||
throw std::runtime_error(outcome.error().what());
|
||||
}
|
||||
|
||||
if (auto* unsolvable = std::get_if<solver::libsolv::UnSolvable>(&outcome.value()))
|
||||
{
|
||||
unsolvable->explain_problems_to(
|
||||
db,
|
||||
std::get<solver::libsolv::Database>(db),
|
||||
LOG_ERROR,
|
||||
{
|
||||
/* .unavailable= */ ctx.graphics_params.palette.failure,
|
||||
|
@ -549,7 +577,9 @@ namespace mamba
|
|||
if (ctx.output_params.json)
|
||||
{
|
||||
Console::instance().json_write(
|
||||
{ { "success", false }, { "solver_problems", unsolvable->problems(db) } }
|
||||
{ { "success", false },
|
||||
{ "solver_problems",
|
||||
unsolvable->problems(std::get<solver::libsolv::Database>(db)) } }
|
||||
);
|
||||
}
|
||||
throw mamba_error(
|
||||
|
@ -579,7 +609,7 @@ namespace mamba
|
|||
ctx,
|
||||
database,
|
||||
request,
|
||||
std::get<solver::Solution>(outcome),
|
||||
std::get<solver::Solution>(outcome.value()),
|
||||
package_caches
|
||||
);
|
||||
}(std::move(db));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <solv/evr.h>
|
||||
|
@ -138,9 +139,9 @@ namespace mamba
|
|||
|
||||
auto load_installed_packages_in_database(
|
||||
const Context& ctx,
|
||||
solver::libsolv::Database& database,
|
||||
std::variant<solver::libsolv::Database, solver::resolvo::Database>& database,
|
||||
const PrefixData& prefix
|
||||
) -> solver::libsolv::RepoInfo
|
||||
) -> expected_t<void>
|
||||
{
|
||||
// TODO(C++20): We could do a PrefixData range that returns packages without storing them.
|
||||
auto pkgs = prefix.sorted_records();
|
||||
|
@ -152,12 +153,25 @@ namespace mamba
|
|||
|
||||
// Not adding Pip dependency since it might needlessly make the installed/active environment
|
||||
// broken if pip is not already installed (debatable).
|
||||
auto repo = database.add_repo_from_packages(
|
||||
pkgs,
|
||||
"installed",
|
||||
solver::libsolv::PipAsPythonDependency::No
|
||||
);
|
||||
database.set_installed_repo(repo);
|
||||
return repo;
|
||||
if (auto* libsolv_db = std::get_if<solver::libsolv::Database>(&database))
|
||||
{
|
||||
auto repo = libsolv_db->add_repo_from_packages(
|
||||
pkgs,
|
||||
"installed",
|
||||
solver::libsolv::PipAsPythonDependency::No
|
||||
);
|
||||
libsolv_db->set_installed_repo(repo);
|
||||
return {};
|
||||
}
|
||||
else if (auto* resolvo_db = std::get_if<solver::resolvo::Database>(&database))
|
||||
{
|
||||
resolvo_db->add_repo_from_packages(pkgs, "installed");
|
||||
resolvo_db->set_installed_repo("installed");
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
return make_unexpected("Unknown database type", mamba_error_code::unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,676 @@
|
|||
// 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 <simdjson.h>
|
||||
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/solver/libsolv/parameters.hpp"
|
||||
#include "mamba/solver/resolvo/database.hpp"
|
||||
#include "mamba/specs/channel.hpp"
|
||||
#include "mamba/specs/package_info.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
namespace mamba::solver::resolvo
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// TODO: reuse it from `mamba/solver/libsolv/helpers.cpp`
|
||||
auto lsplit_track_features(std::string_view features)
|
||||
{
|
||||
constexpr auto is_sep = [](char c) -> bool { return (c == ',') || util::is_space(c); };
|
||||
auto [_, tail] = util::lstrip_if_parts(features, is_sep);
|
||||
return util::lstrip_if_parts(tail, [&](char c) { return !is_sep(c); });
|
||||
}
|
||||
|
||||
// TODO: factorise with the implementation from `set_solvable` in
|
||||
// `mamba/solver/libsolv/helpers.cpp`
|
||||
bool parse_packageinfo_json(
|
||||
const std::string_view& filename,
|
||||
const simdjson::dom::element& pkg,
|
||||
const specs::CondaURL& repo_url,
|
||||
const std::string& channel_id,
|
||||
Database& database
|
||||
)
|
||||
{
|
||||
specs::PackageInfo package_info;
|
||||
|
||||
package_info.channel = channel_id;
|
||||
package_info.filename = filename;
|
||||
package_info.package_url = (repo_url / filename).str(specs::CondaURL::Credentials::Show);
|
||||
|
||||
if (auto fn = pkg["fn"].get_string(); !fn.error())
|
||||
{
|
||||
package_info.name = fn.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback from key entry
|
||||
package_info.name = filename;
|
||||
}
|
||||
|
||||
if (auto name = pkg["name"].get_string(); !name.error())
|
||||
{
|
||||
package_info.name = name.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << R"(Found invalid name in ")" << filename << R"(")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto version = pkg["version"].get_string(); !version.error())
|
||||
{
|
||||
package_info.version = version.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << R"(Found invalid version in ")" << filename << R"(")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto build_string = pkg["build"].get_string(); !build_string.error())
|
||||
{
|
||||
package_info.build_string = build_string.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << R"(Found invalid build in ")" << filename << R"(")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto build_number = pkg["build_number"].get_uint64(); !build_number.error())
|
||||
{
|
||||
package_info.build_number = build_number.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << R"(Found invalid build_number in ")" << filename << R"(")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto subdir = pkg["subdir"].get_c_str(); !subdir.error())
|
||||
{
|
||||
package_info.platform = subdir.value_unsafe();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << R"(Found invalid subdir in ")" << filename << R"(")";
|
||||
}
|
||||
|
||||
if (auto size = pkg["size"].get_uint64(); !size.error())
|
||||
{
|
||||
package_info.size = size.value_unsafe();
|
||||
}
|
||||
|
||||
if (auto md5 = pkg["md5"].get_c_str(); !md5.error())
|
||||
{
|
||||
package_info.md5 = md5.value_unsafe();
|
||||
}
|
||||
|
||||
if (auto sha256 = pkg["sha256"].get_c_str(); !sha256.error())
|
||||
{
|
||||
package_info.sha256 = sha256.value_unsafe();
|
||||
}
|
||||
|
||||
if (auto elem = pkg["noarch"]; !elem.error())
|
||||
{
|
||||
// TODO: is the following right?
|
||||
if (auto val = elem.get_bool(); !val.error() && val.value_unsafe())
|
||||
{
|
||||
package_info.noarch = specs::NoArchType::No;
|
||||
}
|
||||
else if (auto noarch = elem.get_c_str(); !noarch.error())
|
||||
{
|
||||
package_info.noarch = specs::NoArchType::No;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto license = pkg["license"].get_c_str(); !license.error())
|
||||
{
|
||||
package_info.license = license.value_unsafe();
|
||||
}
|
||||
|
||||
// TODO conda timestamp are not Unix timestamp.
|
||||
// Libsolv normalize them this way, we need to do the same here otherwise the current
|
||||
// package may get arbitrary priority.
|
||||
if (auto timestamp = pkg["timestamp"].get_uint64(); !timestamp.error())
|
||||
{
|
||||
const auto time = timestamp.value_unsafe();
|
||||
// TODO: reuse it from `mamba/solver/libsolv/helpers.cpp`
|
||||
constexpr auto MAX_CONDA_TIMESTAMP = 253402300799ULL;
|
||||
package_info.timestamp = (time > MAX_CONDA_TIMESTAMP) ? (time / 1000) : time;
|
||||
}
|
||||
|
||||
if (auto depends = pkg["depends"].get_array(); !depends.error())
|
||||
{
|
||||
for (auto elem : depends)
|
||||
{
|
||||
if (auto dep = elem.get_c_str(); !dep.error())
|
||||
{
|
||||
package_info.dependencies.emplace_back(dep.value_unsafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto constrains = pkg["constrains"].get_array(); !constrains.error())
|
||||
{
|
||||
for (auto elem : constrains)
|
||||
{
|
||||
if (auto cons = elem.get_c_str(); !cons.error())
|
||||
{
|
||||
package_info.constrains.emplace_back(cons.value_unsafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto obj = pkg["track_features"]; !obj.error())
|
||||
{
|
||||
if (auto track_features_arr = obj.get_array(); !track_features_arr.error())
|
||||
{
|
||||
for (auto elem : track_features_arr)
|
||||
{
|
||||
if (auto feat = elem.get_string(); !feat.error())
|
||||
{
|
||||
package_info.track_features.emplace_back(feat.value_unsafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto track_features_str = obj.get_string(); !track_features_str.error())
|
||||
{
|
||||
auto splits = lsplit_track_features(track_features_str.value_unsafe());
|
||||
while (!splits[0].empty())
|
||||
{
|
||||
package_info.track_features.emplace_back(splits[0]);
|
||||
splits = lsplit_track_features(splits[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.alloc_solvable(package_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Database::Database()
|
||||
: name_pool(Mapping<::resolvo::NameId, ::resolvo::String>())
|
||||
{
|
||||
}
|
||||
|
||||
void Database::add_repo_from_repodata_json(
|
||||
const fs::u8path& filename,
|
||||
const std::string& repo_url,
|
||||
const std::string& channel_id,
|
||||
bool verify_artifacts
|
||||
)
|
||||
{
|
||||
auto parser = simdjson::dom::parser();
|
||||
const auto lock = LockFile(filename);
|
||||
const auto repodata = parser.load(filename);
|
||||
|
||||
// An override for missing package subdir is found at the top level
|
||||
auto default_subdir = std::string();
|
||||
if (auto subdir = repodata.at_pointer("/info/subdir").get_string(); !subdir.error())
|
||||
{
|
||||
default_subdir = std::string(subdir.value_unsafe());
|
||||
}
|
||||
|
||||
// Get `base_url` in case 'repodata_version': 2
|
||||
// cf. https://github.com/conda-incubator/ceps/blob/main/cep-15.md
|
||||
auto base_url = repo_url;
|
||||
if (auto repodata_version = repodata["repodata_version"].get_int64();
|
||||
!repodata_version.error())
|
||||
{
|
||||
if (repodata_version.value_unsafe() == 2)
|
||||
{
|
||||
if (auto url = repodata.at_pointer("/info/base_url").get_string(); !url.error())
|
||||
{
|
||||
base_url = std::string(url.value_unsafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto parsed_url = specs::CondaURL::parse(base_url)
|
||||
.or_else([](specs::ParseError&& err) { throw std::move(err); })
|
||||
.value();
|
||||
|
||||
auto signatures = std::optional<simdjson::dom::object>(std::nullopt);
|
||||
if (auto maybe_sigs = repodata["signatures"].get_object();
|
||||
!maybe_sigs.error() && verify_artifacts)
|
||||
{
|
||||
signatures = std::move(maybe_sigs).value();
|
||||
}
|
||||
|
||||
auto added = util::flat_set<std::string_view>();
|
||||
if (auto pkgs = repodata["packages.conda"].get_object(); !pkgs.error())
|
||||
{
|
||||
for (auto [key, value] : pkgs.value())
|
||||
{
|
||||
parse_packageinfo_json(key, value, parsed_url, channel_id, *this);
|
||||
}
|
||||
}
|
||||
if (auto pkgs = repodata["packages"].get_object(); !pkgs.error())
|
||||
{
|
||||
for (auto [key, value] : pkgs.value())
|
||||
{
|
||||
parse_packageinfo_json(key, value, parsed_url, channel_id, *this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::add_repo_from_packages(
|
||||
const std::vector<specs::PackageInfo>& packages,
|
||||
const std::string& repo_name,
|
||||
bool pip_as_python_dependency
|
||||
)
|
||||
{
|
||||
for (const auto& package : packages)
|
||||
{
|
||||
alloc_solvable(package);
|
||||
}
|
||||
}
|
||||
|
||||
void Database::set_installed_repo(const std::string& repo_name)
|
||||
{
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
::resolvo::String Database::display_solvable(::resolvo::SolvableId solvable)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable];
|
||||
return ::resolvo::String{ package_info.long_str() };
|
||||
}
|
||||
|
||||
::resolvo::String Database::display_solvable_name(::resolvo::SolvableId solvable)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable];
|
||||
return ::resolvo::String{ package_info.name };
|
||||
}
|
||||
|
||||
::resolvo::String
|
||||
Database::display_merged_solvables(::resolvo::Slice<::resolvo::SolvableId> solvable)
|
||||
{
|
||||
std::string result;
|
||||
for (auto& solvable_id : solvable)
|
||||
{
|
||||
result += solvable_pool[solvable_id].long_str();
|
||||
}
|
||||
return ::resolvo::String{ result };
|
||||
}
|
||||
|
||||
::resolvo::String Database::display_name(::resolvo::NameId name)
|
||||
{
|
||||
return name_pool[name];
|
||||
}
|
||||
|
||||
::resolvo::String Database::display_version_set(::resolvo::VersionSetId version_set)
|
||||
{
|
||||
const specs::MatchSpec match_spec = version_set_pool[version_set];
|
||||
return ::resolvo::String{ match_spec.str() };
|
||||
}
|
||||
|
||||
::resolvo::String Database::display_string(::resolvo::StringId string)
|
||||
{
|
||||
return string_pool[string];
|
||||
}
|
||||
|
||||
::resolvo::NameId Database::version_set_name(::resolvo::VersionSetId version_set_id)
|
||||
{
|
||||
const specs::MatchSpec match_spec = version_set_pool[version_set_id];
|
||||
return name_pool[::resolvo::String{ match_spec.name().str() }];
|
||||
}
|
||||
|
||||
::resolvo::NameId Database::solvable_name(::resolvo::SolvableId solvable_id)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable_id];
|
||||
return name_pool[::resolvo::String{ package_info.name }];
|
||||
}
|
||||
|
||||
::resolvo::Candidates Database::get_candidates(::resolvo::NameId package)
|
||||
{
|
||||
::resolvo::Candidates candidates{};
|
||||
candidates.favored = nullptr;
|
||||
candidates.locked = nullptr;
|
||||
candidates.candidates = name_to_solvable[package];
|
||||
return candidates;
|
||||
}
|
||||
|
||||
void Database::sort_candidates(::resolvo::Slice<::resolvo::SolvableId> solvables)
|
||||
{
|
||||
std::sort(
|
||||
solvables.begin(),
|
||||
solvables.end(),
|
||||
[&](const ::resolvo::SolvableId& a, const ::resolvo::SolvableId& b)
|
||||
{
|
||||
const specs::PackageInfo& package_info_a = solvable_pool[a];
|
||||
const specs::PackageInfo& package_info_b = solvable_pool[b];
|
||||
|
||||
// If track features are present, prefer the solvable having the least of them.
|
||||
if (package_info_a.track_features.size() != package_info_b.track_features.size())
|
||||
{
|
||||
return package_info_a.track_features.size()
|
||||
< package_info_b.track_features.size();
|
||||
}
|
||||
|
||||
const auto a_version = specs::Version::parse(package_info_a.version).value();
|
||||
const auto b_version = specs::Version::parse(package_info_b.version).value();
|
||||
|
||||
if (a_version != b_version)
|
||||
{
|
||||
return a_version > b_version;
|
||||
}
|
||||
|
||||
if (package_info_a.build_number != package_info_b.build_number)
|
||||
{
|
||||
return package_info_a.build_number > package_info_b.build_number;
|
||||
}
|
||||
|
||||
// Compare the dependencies of the variants.
|
||||
std::unordered_map<::resolvo::NameId, ::resolvo::VersionSetId> a_deps;
|
||||
std::unordered_map<::resolvo::NameId, ::resolvo::VersionSetId> b_deps;
|
||||
for (auto dep_a : package_info_a.dependencies)
|
||||
{
|
||||
// TODO: have a VersionID to NameID mapping instead
|
||||
specs::MatchSpec ms = specs::MatchSpec::parse(dep_a).value();
|
||||
const std::string& name = ms.name().str();
|
||||
auto name_id = name_pool.alloc(::resolvo::String{ name });
|
||||
|
||||
a_deps[name_id] = version_set_pool[ms];
|
||||
}
|
||||
for (auto dep_b : package_info_b.dependencies)
|
||||
{
|
||||
// TODO: have a VersionID to NameID mapping instead
|
||||
specs::MatchSpec ms = specs::MatchSpec::parse(dep_b).value();
|
||||
const std::string& name = ms.name().str();
|
||||
auto name_id = name_pool.alloc(::resolvo::String{ name });
|
||||
|
||||
b_deps[name_id] = version_set_pool[ms];
|
||||
}
|
||||
|
||||
auto ordering_score = 0;
|
||||
for (auto [name_id, version_set_id] : a_deps)
|
||||
{
|
||||
if (b_deps.find(name_id) != b_deps.end())
|
||||
{
|
||||
auto [a_tf_version, a_n_track_features] = find_highest_version(version_set_id);
|
||||
auto [b_tf_version, b_n_track_features] = find_highest_version(b_deps[name_id]
|
||||
);
|
||||
|
||||
// Favor the solvable with higher versions of their dependencies
|
||||
if (a_tf_version != b_tf_version)
|
||||
{
|
||||
ordering_score += a_tf_version > b_tf_version ? 1 : -1;
|
||||
}
|
||||
|
||||
// Highly penalize the solvable if a dependencies has more track features
|
||||
if (a_n_track_features != b_n_track_features)
|
||||
{
|
||||
ordering_score += a_n_track_features > b_n_track_features ? -100 : 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ordering_score != 0)
|
||||
{
|
||||
return ordering_score > 0;
|
||||
}
|
||||
|
||||
return package_info_a.timestamp > package_info_b.timestamp;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
::resolvo::Vector<::resolvo::SolvableId> Database::filter_candidates(
|
||||
::resolvo::Slice<::resolvo::SolvableId> candidates,
|
||||
::resolvo::VersionSetId version_set_id,
|
||||
bool inverse
|
||||
)
|
||||
{
|
||||
specs::MatchSpec match_spec = version_set_pool[version_set_id];
|
||||
::resolvo::Vector<::resolvo::SolvableId> filtered;
|
||||
|
||||
if (inverse)
|
||||
{
|
||||
for (auto& solvable_id : candidates)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable_id];
|
||||
|
||||
// Is it an appropriate check? Or must another one be crafted?
|
||||
if (!match_spec.contains_except_channel(package_info))
|
||||
{
|
||||
filtered.push_back(solvable_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& solvable_id : candidates)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable_id];
|
||||
|
||||
// Is it an appropriate check? Or must another one be crafted?
|
||||
if (match_spec.contains_except_channel(package_info))
|
||||
{
|
||||
filtered.push_back(solvable_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
::resolvo::Dependencies Database::get_dependencies(::resolvo::SolvableId solvable_id)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable_id];
|
||||
|
||||
::resolvo::Dependencies dependencies;
|
||||
|
||||
// TODO: do this in O(1)
|
||||
for (auto& dep : package_info.dependencies)
|
||||
{
|
||||
const specs::MatchSpec match_spec = specs::MatchSpec::parse(dep).value();
|
||||
dependencies.requirements.push_back(version_set_pool[match_spec]);
|
||||
}
|
||||
for (auto& constr : package_info.constrains)
|
||||
{
|
||||
// if constr contain " == " replace it with "=="
|
||||
std::string constr2 = constr;
|
||||
while (constr2.find(" == ") != std::string::npos)
|
||||
{
|
||||
constr2 = constr2.replace(constr2.find(" == "), 4, "==");
|
||||
}
|
||||
while (constr2.find(" >= ") != std::string::npos)
|
||||
{
|
||||
constr2 = constr2.replace(constr2.find(" >= "), 4, ">=");
|
||||
}
|
||||
const specs::MatchSpec match_spec = specs::MatchSpec::parse(constr2).value();
|
||||
dependencies.constrains.push_back(version_set_pool[match_spec]);
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
::resolvo::VersionSetId Database::alloc_version_set(std::string_view raw_match_spec)
|
||||
{
|
||||
std::string raw_match_spec_str = std::string(raw_match_spec);
|
||||
// Replace all " v" with simply " " to work around the `v` prefix in some version strings
|
||||
// e.g. `mingw-w64-ucrt-x86_64-crt-git v12.0.0.r2.ggc561118da h707e725_0` in
|
||||
// `inform2w64-sysroot_win-64-v12.0.0.r2.ggc561118da-h707e725_0.conda` while
|
||||
// (raw_match_spec_str.find(" v") != std::string::npos)
|
||||
{
|
||||
raw_match_spec_str = raw_match_spec_str.replace(raw_match_spec_str.find(" v"), 2, " ");
|
||||
}
|
||||
|
||||
// Remove any presence of selector on python version in the match spec
|
||||
// e.g. `pillow-heif >=0.10.0,<1.0.0<py312` -> `pillow-heif >=0.10.0,<1.0.0` in
|
||||
// `infowillow-1.6.3-pyhd8ed1ab_0.conda`
|
||||
for (const auto specifier : { "=py", "<py", ">py", ">=py", "<=py", "!=py" })
|
||||
{
|
||||
while (raw_match_spec_str.find(specifier) != std::string::npos)
|
||||
{
|
||||
raw_match_spec_str = raw_match_spec_str.substr(0, raw_match_spec_str.find(specifier));
|
||||
}
|
||||
}
|
||||
// Remove any white space between version
|
||||
// e.g. `kytea >=0.1.4, 0.2.0` -> `kytea >=0.1.4,0.2.0` in
|
||||
// `infokonoha-4.6.3-pyhd8ed1ab_0.tar.bz2`
|
||||
while (raw_match_spec_str.find(", ") != std::string::npos)
|
||||
{
|
||||
raw_match_spec_str = raw_match_spec_str.replace(raw_match_spec_str.find(", "), 2, ",");
|
||||
}
|
||||
|
||||
// TODO: skip allocation for now if "*.*" is in the match spec
|
||||
if (raw_match_spec_str.find("*.*") != std::string::npos)
|
||||
{
|
||||
return ::resolvo::VersionSetId{ 0 };
|
||||
}
|
||||
|
||||
// NOTE: works around `openblas 0.2.18|0.2.18.*.` from
|
||||
// `dlib==19.0=np110py27_blas_openblas_200` If contains "|", split on it and recurse
|
||||
if (raw_match_spec_str.find("|") != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> match_specs;
|
||||
std::string match_spec;
|
||||
for (char c : raw_match_spec_str)
|
||||
{
|
||||
if (c == '|')
|
||||
{
|
||||
match_specs.push_back(match_spec);
|
||||
match_spec.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
match_spec += c;
|
||||
}
|
||||
}
|
||||
match_specs.push_back(match_spec);
|
||||
std::vector<::resolvo::VersionSetId> version_sets;
|
||||
for (const std::string& ms : match_specs)
|
||||
{
|
||||
alloc_version_set(ms);
|
||||
}
|
||||
// Placeholder return value
|
||||
return ::resolvo::VersionSetId{ 0 };
|
||||
}
|
||||
|
||||
// NOTE: This works around some improperly encoded `constrains` in the test data, e.g.:
|
||||
// `openmpi-4.1.4-ha1ae619_102`'s improperly encoded `constrains`: "cudatoolkit
|
||||
// >= 10.2" `pytorch-1.13.0-cpu_py310h02c325b_0.conda`'s improperly encoded
|
||||
// `constrains`: "pytorch-cpu = 1.13.0", "pytorch-gpu = 99999999"
|
||||
// `fipy-3.4.2.1-py310hff52083_3.tar.bz2`'s improperly encoded `constrains` or `dep`:
|
||||
// ">=4.5.2"
|
||||
// Remove any with space after the binary operators
|
||||
for (const std::string& op : { ">=", "<=", "==", ">", "<", "!=", "=", "==" })
|
||||
{
|
||||
const std::string& bad_op = op + " ";
|
||||
while (raw_match_spec_str.find(bad_op) != std::string::npos)
|
||||
{
|
||||
raw_match_spec_str = raw_match_spec_str.substr(0, raw_match_spec_str.find(bad_op)) + op
|
||||
+ raw_match_spec_str.substr(
|
||||
raw_match_spec_str.find(bad_op) + bad_op.size()
|
||||
);
|
||||
}
|
||||
// If start with binary operator, prepend NONE
|
||||
if (raw_match_spec_str.find(op) == 0)
|
||||
{
|
||||
raw_match_spec_str = "NONE " + raw_match_spec_str;
|
||||
}
|
||||
}
|
||||
|
||||
const specs::MatchSpec match_spec = specs::MatchSpec::parse(raw_match_spec_str).value();
|
||||
// Add the version set to the version set pool
|
||||
auto id = version_set_pool.alloc(match_spec);
|
||||
|
||||
// Add name to the Name and String pools
|
||||
const std::string name = match_spec.name().str();
|
||||
name_pool.alloc(::resolvo::String{ name });
|
||||
string_pool.alloc(::resolvo::String{ name });
|
||||
|
||||
// Add the MatchSpec's string representation to the Name and String pools
|
||||
const std::string match_spec_str = match_spec.str();
|
||||
name_pool.alloc(::resolvo::String{ match_spec_str });
|
||||
string_pool.alloc(::resolvo::String{ match_spec_str });
|
||||
return id;
|
||||
}
|
||||
|
||||
::resolvo::SolvableId Database::alloc_solvable(specs::PackageInfo package_info)
|
||||
{
|
||||
// Add the solvable to the solvable pool
|
||||
auto id = solvable_pool.alloc(package_info);
|
||||
|
||||
// Add name to the Name and String pools
|
||||
const std::string name = package_info.name;
|
||||
name_pool.alloc(::resolvo::String{ name });
|
||||
string_pool.alloc(::resolvo::String{ name });
|
||||
|
||||
// Add the long string representation of the package to the Name and String pools
|
||||
const std::string long_str = package_info.long_str();
|
||||
name_pool.alloc(::resolvo::String{ long_str });
|
||||
string_pool.alloc(::resolvo::String{ long_str });
|
||||
|
||||
for (auto& dep : package_info.dependencies)
|
||||
{
|
||||
alloc_version_set(dep);
|
||||
}
|
||||
for (auto& constr : package_info.constrains)
|
||||
{
|
||||
alloc_version_set(constr);
|
||||
}
|
||||
|
||||
// Add the solvable to the name_to_solvable map
|
||||
const ::resolvo::NameId name_id = name_pool.alloc(::resolvo::String{ package_info.name });
|
||||
name_to_solvable[name_id].push_back(id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
std::pair<specs::Version, size_t>
|
||||
Database::find_highest_version(::resolvo::VersionSetId version_set_id)
|
||||
{
|
||||
// If the version set has already been computed, return it.
|
||||
if (version_set_to_max_version_and_track_features_numbers.find(version_set_id)
|
||||
!= version_set_to_max_version_and_track_features_numbers.end())
|
||||
{
|
||||
return version_set_to_max_version_and_track_features_numbers[version_set_id];
|
||||
}
|
||||
|
||||
const specs::MatchSpec match_spec = version_set_pool[version_set_id];
|
||||
|
||||
const std::string& name = match_spec.name().str();
|
||||
|
||||
auto name_id = name_pool.alloc(::resolvo::String{ name });
|
||||
|
||||
auto solvables = name_to_solvable[name_id];
|
||||
|
||||
auto filtered = filter_candidates(solvables, version_set_id, false);
|
||||
|
||||
specs::Version max_version = specs::Version();
|
||||
size_t max_version_n_track_features = 0;
|
||||
|
||||
for (auto& solvable_id : filtered)
|
||||
{
|
||||
const specs::PackageInfo& package_info = solvable_pool[solvable_id];
|
||||
const auto version = specs::Version::parse(package_info.version).value();
|
||||
if (version == max_version)
|
||||
{
|
||||
max_version_n_track_features = std::min(
|
||||
max_version_n_track_features,
|
||||
package_info.track_features.size()
|
||||
);
|
||||
}
|
||||
if (version > max_version)
|
||||
{
|
||||
max_version = version;
|
||||
max_version_n_track_features = package_info.track_features.size();
|
||||
}
|
||||
}
|
||||
|
||||
auto val = std::make_pair(max_version, max_version_n_track_features);
|
||||
version_set_to_max_version_and_track_features_numbers[version_set_id] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace mamba::solver::resolvo
|
|
@ -0,0 +1,164 @@
|
|||
// 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 "mamba/solver/resolvo/database.hpp"
|
||||
#include "mamba/solver/resolvo/solver.hpp"
|
||||
#include "mamba/util/variant_cmp.hpp"
|
||||
|
||||
namespace mamba::solver::resolvo
|
||||
{
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* An arbitrary comparison function to get determinist output.
|
||||
*/
|
||||
auto make_request_cmp()
|
||||
{
|
||||
return util::make_variant_cmp(
|
||||
/** index_cmp= */
|
||||
[](auto lhs, auto rhs) { return lhs < rhs; },
|
||||
/** alternative_cmp= */
|
||||
[](const auto& lhs, const auto& rhs)
|
||||
{
|
||||
using Itm = std::decay_t<decltype(lhs)>;
|
||||
if constexpr (!std::is_same_v<Itm, Request::UpdateAll>)
|
||||
{
|
||||
return lhs.spec.name().str() < rhs.spec.name().str();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
auto request_to_requirements(const Request& request, Database& database)
|
||||
-> std::vector<::resolvo::VersionSetId>
|
||||
{
|
||||
std::vector<::resolvo::VersionSetId> requirements;
|
||||
requirements.reserve(request.jobs.size());
|
||||
|
||||
for (const auto& job : request.jobs)
|
||||
{
|
||||
std::visit(
|
||||
[&](const auto& j)
|
||||
{
|
||||
using T = std::decay_t<decltype(j)>;
|
||||
if constexpr (std::is_same_v<T, Request::Install>)
|
||||
{
|
||||
requirements.push_back(database.alloc_version_set(j.spec.name().str()));
|
||||
}
|
||||
},
|
||||
job
|
||||
);
|
||||
}
|
||||
return requirements;
|
||||
}
|
||||
|
||||
auto request_to_constraints(const Request& request, Database& database)
|
||||
-> std::vector<::resolvo::VersionSetId>
|
||||
{
|
||||
std::vector<::resolvo::VersionSetId> constraints;
|
||||
constraints.reserve(request.jobs.size());
|
||||
|
||||
for (const auto& job : request.jobs)
|
||||
{
|
||||
std::visit(
|
||||
[&](const auto& j)
|
||||
{
|
||||
using T = std::decay_t<decltype(j)>;
|
||||
if constexpr (std::is_same_v<T, Request::Remove>)
|
||||
{
|
||||
constraints.push_back(database.alloc_version_set(j.spec.name().str()));
|
||||
}
|
||||
},
|
||||
job
|
||||
);
|
||||
}
|
||||
return constraints;
|
||||
}
|
||||
|
||||
auto
|
||||
result_to_solution(const ::resolvo::Vector<::resolvo::SolvableId>& result, Database& database, const Request&)
|
||||
-> Solution
|
||||
{
|
||||
Solution solution;
|
||||
solution.actions.reserve(result.size());
|
||||
|
||||
for (const auto& solvable_id : result)
|
||||
{
|
||||
const auto& solvable = database.solvable_pool[solvable_id];
|
||||
specs::PackageInfo pkg;
|
||||
pkg.name = solvable.name;
|
||||
pkg.version = solvable.version;
|
||||
pkg.build_string = solvable.build_string;
|
||||
pkg.build_number = solvable.build_number;
|
||||
pkg.channel = solvable.channel;
|
||||
pkg.md5 = solvable.md5;
|
||||
pkg.sha256 = solvable.sha256;
|
||||
pkg.track_features = solvable.track_features;
|
||||
pkg.dependencies = solvable.dependencies;
|
||||
pkg.constrains = solvable.constrains;
|
||||
pkg.timestamp = solvable.timestamp;
|
||||
pkg.license = solvable.license;
|
||||
pkg.size = solvable.size;
|
||||
|
||||
solution.actions.emplace_back(Solution::Install{ std::move(pkg) });
|
||||
}
|
||||
|
||||
return solution;
|
||||
}
|
||||
}
|
||||
|
||||
auto Solver::solve_impl(Database& database, const Request& request) -> expected_t<Outcome>
|
||||
{
|
||||
auto requirements = request_to_requirements(request, database);
|
||||
auto constraints = request_to_constraints(request, database);
|
||||
::resolvo::Vector<::resolvo::SolvableId> result;
|
||||
|
||||
::resolvo::Vector<::resolvo::VersionSetId> req_vec;
|
||||
for (const auto& req : requirements)
|
||||
{
|
||||
req_vec.push_back(req);
|
||||
}
|
||||
|
||||
::resolvo::Vector<::resolvo::VersionSetId> constr_vec;
|
||||
for (const auto& constr : constraints)
|
||||
{
|
||||
constr_vec.push_back(constr);
|
||||
}
|
||||
|
||||
::resolvo::String reason = ::resolvo::solve(database, req_vec, constr_vec, result);
|
||||
|
||||
if (reason != "")
|
||||
{
|
||||
// Get the length from a string view of the reason
|
||||
std::string_view reason_str_view = reason;
|
||||
std::string reason_str(reason.data(), reason_str_view.size());
|
||||
return make_unexpected(mamba_error("resolvo", reason_str));
|
||||
}
|
||||
|
||||
return Outcome{ result_to_solution(result, database, request) };
|
||||
}
|
||||
|
||||
auto Solver::solve(Database& database, Request&& request) -> expected_t<Outcome>
|
||||
{
|
||||
if (request.flags.order_request)
|
||||
{
|
||||
std::sort(request.jobs.begin(), request.jobs.end(), make_request_cmp());
|
||||
}
|
||||
return solve_impl(database, request);
|
||||
}
|
||||
|
||||
auto Solver::solve(Database& database, const Request& request) -> expected_t<Outcome>
|
||||
{
|
||||
if (request.flags.order_request)
|
||||
{
|
||||
auto sorted_request = request;
|
||||
std::sort(sorted_request.jobs.begin(), sorted_request.jobs.end(), make_request_cmp());
|
||||
return solve_impl(database, sorted_request);
|
||||
}
|
||||
return solve_impl(database, request);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue