Internally add flag for switching MatchSpec parser (#3905)

This commit is contained in:
Antoine Prouvost 2025-04-24 14:19:01 +02:00 committed by GitHub
parent 939ed2f038
commit 306e4542a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 556 additions and 149 deletions

View File

@ -8,7 +8,13 @@ cmake_minimum_required(VERSION 3.16)
add_library(
solv-cpp OBJECT
src/pool.cpp src/queue.cpp src/repo.cpp src/solvable.cpp src/solver.cpp src/transaction.cpp
src/pool.cpp
src/queue.cpp
src/repo.cpp
src/solvable.cpp
src/solver.cpp
src/transaction.cpp
src/dependency.cpp
)
target_include_directories(
solv-cpp

View File

@ -0,0 +1,49 @@
// Copyright (c) 2025, 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_SOLV_DEPENDENCY_HPP
#define MAMBA_SOLV_DEPENDENCY_HPP
#include <solv/poolid.h>
#include "solv-cpp/ids.hpp"
namespace solv
{
class ObjDependencyViewConst
{
public:
explicit ObjDependencyViewConst(const ::Reldep& reldep) noexcept;
~ObjDependencyViewConst() noexcept;
[[nodiscard]] auto raw() const -> const ::Reldep*;
/**
* The name field of the dependency.
*
* Can be a string id for simple dependencies, or another dependency id for
* complex depndencies with boolean expressions.
*/
[[nodiscard]] auto name() const -> StringId /* OR DependencyId */;
/**
* The version range field of the dependency.
*
* Can be a string id for simple dependencies, or another dependency id for
* complex depndencies with boolean expressions.
*/
[[nodiscard]] auto version_range() const -> StringId /* OR DependencyId */;
/** The flags of the dependency, such as types. */
[[nodiscard]] auto flags() const -> RelationFlag;
private:
const ::Reldep* m_reldep = nullptr;
};
}
#endif

View File

@ -18,6 +18,7 @@
#include <solv/pool.h>
#include "solv-cpp/dependency.hpp"
#include "solv-cpp/ids.hpp"
#include "solv-cpp/queue.hpp"
#include "solv-cpp/repo.hpp"
@ -107,10 +108,23 @@ namespace solv
auto add_dependency(StringId name_id, RelationFlag flag, StringId version_id) -> DependencyId;
/**
* Parse a dependency from string and add it to the pool.
* Parse a conda dependency from string and add it to the pool.
*
* This is currently the most efficient and stable way of adding dependencies.
* We do not control the MatchSpec parser with this method so it may not be complete.
*/
auto add_conda_dependency(raw_str_view dep) -> DependencyId;
auto add_conda_dependency(const std::string& dep) -> DependencyId;
auto add_legacy_conda_dependency(raw_str_view dep) -> DependencyId;
auto add_legacy_conda_dependency(const std::string& dep) -> DependencyId;
/**
* Get the dependency object associated with the dependency id.
*
* Return nothing if not given a dependency id, which can be the case when string
* ids are used as dependencies.
* Can also be used to check if an id is a dependency id or not.
*/
auto get_dependency(DependencyId /* OR StringId */ id) const
-> std::optional<ObjDependencyViewConst>;
/** Get the registered name of a dependency. */
auto get_dependency_name(DependencyId id) const -> std::string_view;
@ -323,6 +337,8 @@ namespace solv
ObjPool();
~ObjPool();
[[nodiscard]] auto view() const -> ObjPoolView;
using ObjPoolView::raw;
using ObjPoolView::current_error;
using ObjPoolView::set_current_error;
@ -333,7 +349,7 @@ namespace solv
using ObjPoolView::get_string;
using ObjPoolView::find_dependency;
using ObjPoolView::add_dependency;
using ObjPoolView::add_conda_dependency;
using ObjPoolView::add_legacy_conda_dependency;
using ObjPoolView::get_dependency_name;
using ObjPoolView::get_dependency_version;
using ObjPoolView::get_dependency_relation;

View File

@ -0,0 +1,34 @@
#include "solv-cpp/dependency.hpp"
namespace solv
{
ObjDependencyViewConst::ObjDependencyViewConst(const ::Reldep& reldep) noexcept
: m_reldep(&reldep)
{
}
ObjDependencyViewConst::~ObjDependencyViewConst() noexcept
{
m_reldep = nullptr;
}
auto ObjDependencyViewConst::raw() const -> const ::Reldep*
{
return m_reldep;
}
auto ObjDependencyViewConst::name() const -> StringId
{
return m_reldep->name;
}
auto ObjDependencyViewConst::version_range() const -> StringId
{
return m_reldep->evr;
}
auto ObjDependencyViewConst::flags() const -> RelationFlag
{
return m_reldep->flags;
}
}

View File

@ -93,21 +93,15 @@ namespace solv
namespace
{
// This function is only used in `assert()` expressions
// That's why it might get reported as unused in Release builds
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
auto is_reldep(::Id id) -> bool
[[nodiscard]] auto is_reldep(::Id id) -> bool
{
return ISRELDEP(static_cast<std::make_unsigned_t<::Id>>(id)) != 0;
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
[[nodiscard]] auto get_reldep(const ::Pool* pool, ::Id id) -> const ::Reldep*
{
return GETRELDEP(pool, static_cast<std::make_unsigned_t<::Id>>(id));
}
}
auto ObjPoolView::get_string(StringId id) const -> std::string_view
@ -145,14 +139,25 @@ namespace solv
return id;
}
auto ObjPoolView::add_conda_dependency(raw_str_view dep) -> DependencyId
auto ObjPoolView::add_legacy_conda_dependency(raw_str_view dep) -> DependencyId
{
return ::pool_conda_matchspec(raw(), dep);
}
auto ObjPoolView::add_conda_dependency(const std::string& dep) -> DependencyId
auto ObjPoolView::add_legacy_conda_dependency(const std::string& dep) -> DependencyId
{
return add_conda_dependency(dep.c_str());
return add_legacy_conda_dependency(dep.c_str());
}
auto ObjPoolView::get_dependency(DependencyId id) const -> std::optional<ObjDependencyViewConst>
{
if (!is_reldep(id))
{
return {};
}
const auto rel = get_reldep(raw(), id);
assert(rel != nullptr);
return { ObjDependencyViewConst(*rel) };
}
auto ObjPoolView::get_dependency_name(DependencyId id) const -> std::string_view
@ -389,6 +394,11 @@ namespace solv
ObjPool::~ObjPool() = default;
auto ObjPool::view() const -> ObjPoolView
{
return *static_cast<const ObjPoolView*>(this);
}
void ObjPool::set_namespace_callback(UserCallback&& callback)
{
m_user_namespace_callback = std::make_unique<NamespaceCallbackWrapper>();

View File

@ -56,7 +56,7 @@ namespace solv::test
solv.set_version(pkg.version);
for (const auto& dep : pkg.dependencies)
{
solv.add_dependency(pool.add_conda_dependency(dep));
solv.add_dependency(pool.add_legacy_conda_dependency(dep));
}
solv.add_self_provide();
return solv_id;

View File

@ -82,7 +82,7 @@ namespace
SECTION("Parse a conda dependency")
{
const auto id_conda = pool.add_conda_dependency("rattler < 0.1");
const auto id_conda = pool.add_legacy_conda_dependency("rattler < 0.1");
REQUIRE(pool.get_dependency_name(id_conda) == "rattler");
REQUIRE(pool.get_dependency_version(id_conda) == "<0.1");
}

View File

@ -46,7 +46,7 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("a"),
pool.add_legacy_conda_dependency("a"),
};
REQUIRE(solver.solve(pool, jobs));
auto trans = ObjTransaction::from_solver(pool, solver);
@ -58,7 +58,7 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("b==1.0"),
pool.add_legacy_conda_dependency("b==1.0"),
};
REQUIRE(solver.solve(pool, jobs));
auto trans = ObjTransaction::from_solver(pool, solver);
@ -70,7 +70,7 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("b==2.0"),
pool.add_legacy_conda_dependency("b==2.0"),
};
solver.set_flag(SOLVER_FLAG_ALLOW_UNINSTALL, true);
REQUIRE(solver.solve(pool, jobs));
@ -83,7 +83,7 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("c==1.0"),
pool.add_legacy_conda_dependency("c==1.0"),
};
REQUIRE(solver.solve(pool, jobs));
auto trans = ObjTransaction::from_solver(pool, solver);
@ -97,9 +97,9 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_LOCK | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("a"),
pool.add_legacy_conda_dependency("a"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("c==1.0"),
pool.add_legacy_conda_dependency("c==1.0"),
};
solver.set_flag(SOLVER_FLAG_ALLOW_UNINSTALL, true);
REQUIRE_FALSE(solver.solve(pool, jobs));
@ -117,7 +117,7 @@ namespace
{
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("c==2.0"),
pool.add_legacy_conda_dependency("c==2.0"),
};
REQUIRE_FALSE(solver.solve(pool, jobs));
}
@ -129,7 +129,7 @@ namespace
solver.set_flag(flag, true);
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("c==2.0"),
pool.add_legacy_conda_dependency("c==2.0"),
};
REQUIRE(solver.solve(pool, jobs));
auto trans = ObjTransaction::from_solver(pool, solver);

View File

@ -44,9 +44,9 @@ namespace
// The job is matched with the ``provides`` field of the solvable
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("menu"),
pool.add_legacy_conda_dependency("menu"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("icons=2.*"),
pool.add_legacy_conda_dependency("icons=2.*"),
};
REQUIRE(solver.solve(pool, jobs));
REQUIRE(solver.problem_count() == 0);
@ -56,9 +56,12 @@ namespace
{
// The job is matched with the ``provides`` field of the solvable
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES, pool.add_conda_dependency("menu"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES, pool.add_conda_dependency("icons=1.*"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES, pool.add_conda_dependency("intl=5.*"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_legacy_conda_dependency("menu"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_legacy_conda_dependency("icons=1.*"),
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_legacy_conda_dependency("intl=5.*"),
};
REQUIRE_FALSE(solver.solve(pool, jobs));
@ -80,7 +83,7 @@ namespace
// The job is matched with the ``provides`` field of the solvable
auto jobs = ObjQueue{
SOLVER_INSTALL | SOLVER_SOLVABLE_PROVIDES,
pool.add_conda_dependency("does-not-exists"),
pool.add_legacy_conda_dependency("does-not-exists"),
};
REQUIRE_FALSE(solver.solve(pool, jobs));

View File

@ -108,7 +108,9 @@ namespace
pool.create_whatprovides();
auto solver = ObjSolver(pool);
REQUIRE(solver.solve(pool, { SOLVER_INSTALL, pool.add_conda_dependency("menu>=1.4") }));
REQUIRE(
solver.solve(pool, { SOLVER_INSTALL, pool.add_legacy_conda_dependency("menu>=1.4") })
);
auto trans = ObjTransaction::from_solver(pool, solver);
REQUIRE_FALSE(trans.empty());
REQUIRE(trans.size() == 4);

View File

@ -19,6 +19,13 @@ namespace mamba::solver::libsolv
Libsolv,
};
enum class MatchSpecParser
{
Mixed,
Mamba,
Libsolv,
};
enum class PipAsPythonDependency : bool
{
No = false,

View File

@ -117,6 +117,11 @@ namespace mamba::specs
*/
[[nodiscard]] auto is_only_package_name() const -> bool;
/**
* Make a new MatchSpec that matches only on the name part.
*/
[[nodiscard]] auto to_named_spec() const -> MatchSpec;
/**
* Check if the MatchSpec matches the given package.
*

View File

@ -170,6 +170,7 @@ namespace mamba::solver::libsolv
std::string(url),
channel_id,
package_types,
MatchSpecParser::Libsolv, // Backward compatibility
verify_artifacts
);
}
@ -240,7 +241,12 @@ namespace mamba::solver::libsolv
{
auto s_repo = solv::ObjRepoView(*repo.m_ptr);
auto [id, solv] = s_repo.add_solvable();
set_solvable(pool(), solv, pkg);
set_solvable(
pool(),
solv,
pkg,
MatchSpecParser::Libsolv // Backward compatibility
);
}
void Database::add_repo_from_packages_impl_post(const RepoInfo& repo, PipAsPythonDependency add)
@ -324,9 +330,10 @@ namespace mamba::solver::libsolv
namespace
{
auto matchspec2id(solv::ObjPool& pool, const specs::MatchSpec& ms) -> solv::DependencyId
auto pool_add_matchspec_throwing(solv::ObjPool& pool, const specs::MatchSpec& ms)
-> solv::DependencyId
{
return pool_add_matchspec(pool, ms)
return pool_add_matchspec(pool, ms, MatchSpecParser::Mixed)
.or_else([](mamba_error&& error) { throw std::move(error); })
.value_or(0);
}
@ -337,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(), ms);
const auto ms_id = pool_add_matchspec_throwing(pool(), ms);
auto solvables = pool().select_solvables({ SOLVER_SOLVABLE_PROVIDES, ms_id });
auto out = std::vector<PackageId>(solvables.size());
std::transform(
@ -354,7 +361,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(), ms);
const auto ms_id = pool_add_matchspec_throwing(pool(), ms);
auto solvables = pool().what_matches_dep(SOLVABLE_REQUIRES, ms_id);
auto out = std::vector<PackageId>(solvables.size());
std::transform(

View File

@ -9,6 +9,7 @@
#include <type_traits>
#include <variant>
#include <fmt/ostream.h>
#include <simdjson.h>
#include <solv/conda.h>
#include <solv/repo.h>
@ -20,8 +21,10 @@
#include "mamba/core/output.hpp"
#include "mamba/core/util.hpp"
#include "mamba/solver/libsolv/parameters.hpp"
#include "mamba/specs/archive.hpp"
#include "mamba/specs/conda_url.hpp"
#include "mamba/specs/match_spec.hpp"
#include "mamba/util/cfile.hpp"
#include "mamba/util/random.hpp"
#include "mamba/util/string.hpp"
@ -41,7 +44,12 @@ namespace mamba::solver::libsolv
// to seconds.
inline constexpr auto MAX_CONDA_TIMESTAMP = 253402300799ULL;
void set_solvable(solv::ObjPool& pool, solv::ObjSolvableView solv, const specs::PackageInfo& pkg)
void set_solvable(
solv::ObjPool& pool,
solv::ObjSolvableView solv,
const specs::PackageInfo& pkg,
MatchSpecParser parser
)
{
solv.set_name(pkg.name);
solv.set_version(pkg.version);
@ -72,16 +80,20 @@ namespace mamba::solver::libsolv
for (const auto& dep : pkg.dependencies)
{
// TODO pool's matchspec2id
const solv::DependencyId dep_id = pool.add_conda_dependency(dep);
const solv::DependencyId dep_id = //
pool_add_matchspec(pool, dep.c_str(), parser)
.or_else([](mamba_error&& err) { throw std::move(err); })
.value();
assert(dep_id);
solv.add_dependency(dep_id);
}
for (const auto& cons : pkg.constrains)
{
// TODO pool's matchspec2id
const solv::DependencyId dep_id = pool.add_conda_dependency(cons);
const solv::DependencyId dep_id = //
pool_add_matchspec(pool, cons.c_str(), parser)
.or_else([](mamba_error&& err) { throw std::move(err); })
.value();
assert(dep_id);
solv.add_constraint(dep_id);
}
@ -193,7 +205,8 @@ namespace mamba::solver::libsolv
const std::string& filename,
JSONObject&& pkg,
const std::optional<nlohmann::json>& signatures,
const std::string& default_subdir
const std::string& default_subdir,
MatchSpecParser parser
) -> bool
{
// Not available from RepoDataPackage
@ -307,11 +320,20 @@ namespace mamba::solver::libsolv
{
if (!elem.error() && elem.is_string())
{
if (const auto dep_id = pool.add_conda_dependency(
std::string(elem.get_string().value_unsafe())
))
const auto ms = std::string(elem.get_string().value_unsafe());
const auto maybe_dep_id = pool_add_matchspec(pool, ms.c_str(), parser);
if (maybe_dep_id)
{
solv.add_dependency(dep_id);
solv.add_dependency(*maybe_dep_id);
}
else
{
fmt::print(
LOG_WARNING,
R"(Found invalid MatchSpec "{}" in "{}")",
ms,
filename
);
}
}
}
@ -323,11 +345,20 @@ namespace mamba::solver::libsolv
{
if (!elem.error() && elem.is_string())
{
if (const auto dep_id = pool.add_conda_dependency(
std::string(elem.get_string().value_unsafe())
))
const auto ms = std::string(elem.get_string().value_unsafe());
const auto maybe_dep_id = pool_add_matchspec(pool, ms.c_str(), parser);
if (maybe_dep_id)
{
solv.add_constraint(dep_id);
solv.add_constraint(*maybe_dep_id);
}
else
{
fmt::print(
LOG_WARNING,
R"(Found invalid MatchSpec "{}" in "{}")",
ms,
filename
);
}
}
}
@ -375,7 +406,8 @@ namespace mamba::solver::libsolv
JSONObject& packages,
const std::optional<nlohmann::json>& signatures,
Filter&& filter,
OnParsed&& on_parsed
OnParsed&& on_parsed,
MatchSpecParser parser
)
{
auto packages_as_object = packages.get_object();
@ -393,7 +425,8 @@ namespace mamba::solver::libsolv
filename,
pkg_field.value(),
signatures,
default_subdir
default_subdir,
parser
);
if (parsed)
{
@ -416,7 +449,8 @@ namespace mamba::solver::libsolv
const std::string& channel_id,
const std::string& default_subdir,
JSONObject& packages,
const std::optional<nlohmann::json>& signatures
const std::optional<nlohmann::json>& signatures,
MatchSpecParser parser
)
{
return set_repo_solvables_impl(
@ -428,7 +462,8 @@ namespace mamba::solver::libsolv
packages,
signatures,
/* filter= */ [](const auto&) { return true; },
/* on_parsed= */ [](const auto&) {}
/* on_parsed= */ [](const auto&) {},
parser
);
}
@ -440,7 +475,8 @@ namespace mamba::solver::libsolv
const std::string& channel_id,
const std::string& default_subdir,
JSONObject& packages,
const std::optional<nlohmann::json>& signatures
const std::optional<nlohmann::json>& signatures,
MatchSpecParser parser
) -> util::flat_set<std::string>
{
auto filenames = util::flat_set<std::string>();
@ -453,8 +489,10 @@ namespace mamba::solver::libsolv
packages,
signatures,
/* filter= */ [](const auto&) { return true; },
/* on_parsed= */ [&](const auto& fn)
{ filenames.insert(std::string(specs::strip_archive_extension(fn))); }
/* on_parsed= */
[&](const auto& fn)
{ filenames.insert(std::string(specs::strip_archive_extension(fn))); },
parser
);
// Sort only once
return filenames;
@ -469,7 +507,8 @@ namespace mamba::solver::libsolv
const std::string& default_subdir,
JSONObject& packages,
const std::optional<nlohmann::json>& signatures,
const SortedStringRange& added
const SortedStringRange& added,
MatchSpecParser parser
)
{
return set_repo_solvables_impl(
@ -480,9 +519,10 @@ namespace mamba::solver::libsolv
default_subdir,
packages,
signatures,
/* filter= */ [&](const auto& fn)
{ return !added.contains(specs::strip_archive_extension(fn)); },
/* on_parsed= */ [&](const auto&) {}
/* filter= */
[&](const auto& fn) { return !added.contains(specs::strip_archive_extension(fn)); },
/* on_parsed= */ [&](const auto&) {},
parser
);
}
}
@ -542,6 +582,7 @@ namespace mamba::solver::libsolv
const std::string& repo_url,
const std::string& channel_id,
PackageTypes package_types,
MatchSpecParser ms_parser,
bool verify_artifacts
) -> expected_t<solv::ObjRepoView>
{
@ -657,7 +698,8 @@ namespace mamba::solver::libsolv
channel_id,
default_subdir,
pkgs,
json_signatures
json_signatures,
ms_parser
);
}
if (auto pkgs = repodata_doc["packages"]; !pkgs.error())
@ -670,7 +712,8 @@ namespace mamba::solver::libsolv
default_subdir,
pkgs,
json_signatures,
added
added,
ms_parser
);
}
}
@ -686,7 +729,8 @@ namespace mamba::solver::libsolv
channel_id,
default_subdir,
pkgs,
json_signatures
json_signatures,
ms_parser
);
}
@ -700,7 +744,8 @@ namespace mamba::solver::libsolv
channel_id,
default_subdir,
pkgs,
json_signatures
json_signatures,
ms_parser
);
}
}
@ -870,8 +915,12 @@ namespace mamba::solver::libsolv
void add_pip_as_python_dependency(solv::ObjPool& pool, solv::ObjRepoView repo)
{
const solv::DependencyId python_id = pool.add_conda_dependency("python");
const solv::DependencyId pip_id = pool.add_conda_dependency("pip");
// These matchspecs are so simple that there should be no surprises in using
// the libsolv parser, or in getting back an error.
const solv::DependencyId python_id = //
pool_add_matchspec(pool, "python", MatchSpecParser::Libsolv).value();
const solv::DependencyId pip_id = //
pool_add_matchspec(pool, "pip", MatchSpecParser::Libsolv).value();
repo.for_each_solvable(
[&](solv::ObjSolvableView s)
{
@ -899,7 +948,7 @@ namespace mamba::solver::libsolv
}
auto get_abused_namespace_callback_args( //
solv::ObjPoolView& pool,
solv::ObjPoolView pool,
solv::StringId name,
solv::StringId ver
) -> std::pair<std::string_view, MatchFlags>
@ -910,34 +959,77 @@ namespace mamba::solver::libsolv
};
}
[[nodiscard]] auto pool_add_matchspec( //
solv::ObjPool& pool,
const specs::MatchSpec& ms
) -> expected_t<solv::DependencyId>
namespace
{
auto check_not_zero = [&](solv::DependencyId id) -> expected_t<solv::DependencyId>
template <typename Func>
[[nodiscard]] auto check_dep_error(solv::DependencyId id, Func get_str)
-> expected_t<solv::DependencyId>
{
if (id == 0)
{
return make_unexpected(
fmt::format(R"(Invalid MatchSpec "{}")", ms.str()),
fmt::format(R"(Invalid MatchSpec "{}")", get_str()),
mamba_error_code::invalid_spec
);
}
return id;
};
}
if (ms.is_simple())
[[nodiscard]] auto pool_add_matchspec( //
solv::ObjPool& pool,
const specs::MatchSpec& ms,
MatchSpecParser parser
) -> expected_t<solv::DependencyId>
{
if (parser == MatchSpecParser::Mixed)
{
return check_not_zero(pool.add_conda_dependency(ms.conda_build_form()));
parser = ms.is_simple() ? MatchSpecParser::Libsolv : MatchSpecParser::Mamba;
}
const auto [first, second] = make_abused_namespace_dep_args(pool, ms.str());
return check_not_zero(pool.add_dependency(first, REL_NAMESPACE, second));
if (parser == MatchSpecParser::Libsolv)
{
return check_dep_error(
pool.add_legacy_conda_dependency(ms.conda_build_form()),
[&]() { return ms.str(); }
);
}
else if (parser == MatchSpecParser::Mamba)
{
const auto [first, second] = make_abused_namespace_dep_args(pool, ms.str());
return check_dep_error(
pool.add_dependency(first, REL_NAMESPACE, second),
[&]() { return ms.str(); }
);
}
return make_unexpected("Invalid parser enum", mamba_error_code::incorrect_usage);
}
[[nodiscard]] auto pool_add_matchspec( //
solv::ObjPool& pool,
const char* ms_str,
MatchSpecParser parser
) -> expected_t<solv::DependencyId>
{
// Avoid at all parsing Matchspecs when using Libsolv
if (parser == MatchSpecParser::Libsolv)
{
return check_dep_error(pool.add_legacy_conda_dependency(ms_str), [&]() { return ms_str; });
}
return specs::MatchSpec::parse(ms_str)
.transform_error( //
[](auto&& err) { return mamba_error(err.what(), mamba_error_code::invalid_spec); }
)
.and_then([&](specs::MatchSpec&& ms) { return pool_add_matchspec(pool, ms, parser); });
}
auto pool_add_pin( //
solv::ObjPool& pool,
const specs::MatchSpec& pin
const specs::MatchSpec& pin,
MatchSpecParser parser
) -> expected_t<solv::ObjSolvableView>
{
// In libsolv, locking means that a package keeps the same state: if it is installed,
@ -990,35 +1082,78 @@ namespace mamba::solver::libsolv
return repo;
}();
return pool_add_matchspec(pool, pin).transform(
[&](solv::DependencyId cons)
return pool_add_matchspec(pool, pin, parser)
.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);
// 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);
// Necessary for attributes to be properly stored
// TODO move this at the end of all job requests
installed.internalize();
return cons_solv;
}
);
}
auto pool_get_matchspec( //
solv::ObjPoolView pool,
solv::DependencyId dep
) -> expected_t<specs::MatchSpec>
{
constexpr auto make_ms = [](const auto& str) -> expected_t<specs::MatchSpec>
{
return specs::MatchSpec::parse(str).transform_error(
[](auto&& err) -> mamba_error
{ return mamba_error(err.what(), mamba_error_code::invalid_spec); }
);
};
const auto dependency = pool.get_dependency(dep);
if (!dependency.has_value())
{
return make_ms(pool.get_string(dep));
}
switch (dependency->flags())
{
case REL_CONDA:
{
// 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);
// 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);
// Necessary for attributes to be properly stored
// TODO move this at the end of all job requests
installed.internalize();
return cons_solv;
return make_ms(pool.dependency_to_string(dep));
}
case REL_NAMESPACE:
{
auto [str, _flags] = get_abused_namespace_callback_args(
pool,
dependency->name(),
dependency->version_range()
);
return make_ms(str);
}
}
return make_unexpected(
fmt::format("An unknown relation ({}) was added to libsolv", dependency->flags()),
mamba_error_code::incorrect_usage
);
}
@ -1362,9 +1497,12 @@ namespace mamba::solver::libsolv
return ms;
}
[[nodiscard]] auto
add_reinstall_job(solv::ObjQueue& jobs, solv::ObjPool& pool, const specs::MatchSpec& ms)
-> expected_t<void>
[[nodiscard]] auto add_reinstall_job(
solv::ObjQueue& jobs,
solv::ObjPool& pool,
const specs::MatchSpec& ms,
MatchSpecParser parser
) -> expected_t<void>
{
auto solvable = std::optional<solv::ObjSolvableViewConst>{};
@ -1397,8 +1535,8 @@ namespace mamba::solver::libsolv
}
// We are not reinstalling but simply installing.
return pool_add_matchspec(pool, ms).transform([&](auto id)
{ jobs.push_back(SOLVER_INSTALL, id); });
return pool_add_matchspec(pool, ms, parser)
.transform([&](auto id) { jobs.push_back(SOLVER_INSTALL, id); });
}
[[nodiscard]] auto has_installed_package( //
@ -1422,25 +1560,29 @@ namespace mamba::solver::libsolv
}
template <typename Job>
[[nodiscard]] auto
add_job(const Job& job, solv::ObjQueue& raw_jobs, solv::ObjPool& pool, bool force_reinstall)
-> expected_t<void>
[[nodiscard]] auto add_job(
const Job& job,
solv::ObjQueue& raw_jobs,
solv::ObjPool& pool,
bool force_reinstall,
MatchSpecParser parser
) -> expected_t<void>
{
if constexpr (std::is_same_v<Job, Request::Install>)
{
if (force_reinstall)
{
return add_reinstall_job(raw_jobs, pool, job.spec);
return add_reinstall_job(raw_jobs, pool, job.spec, parser);
}
else
{
return pool_add_matchspec(pool, job.spec)
return pool_add_matchspec(pool, job.spec, parser)
.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)
return pool_add_matchspec(pool, job.spec, parser)
.transform(
[&](auto id)
{
@ -1453,7 +1595,7 @@ namespace mamba::solver::libsolv
}
if constexpr (std::is_same_v<Job, Request::Update>)
{
return pool_add_matchspec(pool, job.spec)
return pool_add_matchspec(pool, job.spec, parser)
.transform(
[&](auto id)
{
@ -1504,17 +1646,18 @@ namespace mamba::solver::libsolv
}
if constexpr (std::is_same_v<Job, Request::Freeze>)
{
return pool_add_matchspec(pool, job.spec)
return pool_add_matchspec(pool, job.spec, parser)
.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).value());
return {};
return pool_add_matchspec(pool, job.spec, parser)
.transform([&](auto id) { raw_jobs.push_back(SOLVER_USERINSTALLED, id); });
}
if constexpr (std::is_same_v<Job, Request::Pin>)
{
return pool_add_pin(pool, job.spec)
// WARNING pins are not working with namespace dependencies so far
return pool_add_pin(pool, job.spec, MatchSpecParser::Libsolv)
.transform(
[&](solv::ObjSolvableView pin_solv)
{
@ -1536,7 +1679,8 @@ namespace mamba::solver::libsolv
auto request_to_decision_queue( //
const Request& request,
solv::ObjPool& pool,
bool force_reinstall
bool force_reinstall,
MatchSpecParser parser
) -> expected_t<solv::ObjQueue>
{
auto solv_jobs = solv::ObjQueue();
@ -1549,7 +1693,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, force_reinstall);
return add_job(job, solv_jobs, pool, force_reinstall, parser);
}
return {};
},
@ -1570,7 +1714,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, force_reinstall);
return add_job(job, solv_jobs, pool, force_reinstall, parser);
}
return {};
},

View File

@ -36,7 +36,12 @@ namespace mamba::fs
namespace mamba::solver::libsolv
{
void set_solvable(solv::ObjPool& pool, solv::ObjSolvableView solv, const specs::PackageInfo& pkg);
void set_solvable(
solv::ObjPool& pool,
solv::ObjSolvableView solv,
const specs::PackageInfo& pkg,
MatchSpecParser parser
);
auto make_package_info(const solv::ObjPool& pool, solv::ObjSolvableViewConst s)
-> specs::PackageInfo;
@ -55,6 +60,7 @@ namespace mamba::solver::libsolv
const std::string& repo_url,
const std::string& channel_id,
PackageTypes types,
MatchSpecParser parser,
bool verify_artifacts
) -> expected_t<solv::ObjRepoView>;
@ -96,21 +102,34 @@ namespace mamba::solver::libsolv
* callback to pass our own information.
*/
[[nodiscard]] auto get_abused_namespace_callback_args( //
solv::ObjPoolView& pool,
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::MatchSpec& ms,
MatchSpecParser parser
) -> expected_t<solv::DependencyId>;
[[nodiscard]] auto pool_add_matchspec( //
solv::ObjPool& pool,
const char* ms,
MatchSpecParser parser
) -> expected_t<solv::DependencyId>;
[[nodiscard]] auto pool_add_pin( //
solv::ObjPool& pool,
const specs::MatchSpec& pin_ms
const specs::MatchSpec& pin_ms,
MatchSpecParser parser
) -> expected_t<solv::ObjSolvableView>;
[[nodiscard]] auto pool_get_matchspec( //
solv::ObjPoolView pool,
solv::DependencyId dep
) -> expected_t<specs::MatchSpec>;
[[nodiscard]] auto transaction_to_solution_all( //
const solv::ObjPool& pool,
const solv::ObjTransaction& trans
@ -149,8 +168,12 @@ namespace mamba::solver::libsolv
std::string_view noarch_type
) -> Solution;
[[nodiscard]] auto
request_to_decision_queue(const Request& request, solv::ObjPool& pool, bool force_reinstall)
-> expected_t<solv::ObjQueue>;
[[nodiscard]] auto request_to_decision_queue(
const Request& request,
solv::ObjPool& pool,
bool force_reinstall,
MatchSpecParser parser
) -> expected_t<solv::ObjQueue>;
}
#endif

View File

@ -55,7 +55,12 @@ namespace mamba::solver::libsolv
auto& pool = Database::Impl::get(mpool);
const auto& flags = request.flags;
return solver::libsolv::request_to_decision_queue(request, pool, flags.force_reinstall)
return solver::libsolv::request_to_decision_queue(
request,
pool,
flags.force_reinstall,
MatchSpecParser::Mixed
)
.transform(
[&](auto&& jobs) -> Outcome
{

View File

@ -338,7 +338,15 @@ namespace mamba::solver::libsolv
void ProblemsGraphCreator::parse_problems()
{
// TODO Throwing error for now but we should use expected in UnSolvable API
auto make_match_spec = [&](std::string_view str) -> specs::MatchSpec
auto make_match_spec = [&](solv::DependencyId dep) -> specs::MatchSpec
{
return pool_get_matchspec(m_pool.view(), dep)
.or_else([](auto&& err) { throw std::move(err); })
.value();
};
// Re-create a MatchSpec from string. This is not the prefer way, as libsolv
// representation may not be the same as ours.
auto make_match_spec_str = [&](std::string_view str) -> specs::MatchSpec
{
return specs::MatchSpec::parse(str)
.or_else([](specs::ParseError&& err) { throw std::move(err); })
@ -392,9 +400,9 @@ namespace mamba::solver::libsolv
);
node_id cons_id = add_solvable(
problem.dep_id,
ConstraintNode{ make_match_spec(dep.value()) }
ConstraintNode{ make_match_spec(problem.dep_id) }
);
auto edge = make_match_spec(dep.value());
auto edge = make_match_spec(problem.dep_id);
m_graph.add_edge(src_id, cons_id, std::move(edge));
add_conflict(cons_id, tgt_id);
break;
@ -414,7 +422,7 @@ namespace mamba::solver::libsolv
problem.source_id,
PackageNode{ fixup_pkg(std::move(source).value()) }
);
auto edge = make_match_spec(dep.value());
auto edge = make_match_spec(problem.dep_id);
bool added = add_expanded_deps_edges(src_id, problem.dep_id, edge);
if (!added)
{
@ -433,7 +441,7 @@ namespace mamba::solver::libsolv
warn_unexpected_problem(problem);
break;
}
auto edge = make_match_spec(dep.value());
auto edge = make_match_spec(problem.dep_id);
bool added = add_expanded_deps_edges(m_root_node, problem.dep_id, edge);
if (!added)
{
@ -453,10 +461,10 @@ namespace mamba::solver::libsolv
warn_unexpected_problem(problem);
break;
}
auto edge = make_match_spec(dep.value());
auto edge = make_match_spec(problem.dep_id);
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{ make_match_spec(dep.value()) }
UnresolvedDependencyNode{ make_match_spec(problem.dep_id) }
);
m_graph.add_edge(m_root_node, dep_id, std::move(edge));
break;
@ -472,14 +480,14 @@ namespace mamba::solver::libsolv
warn_unexpected_problem(problem);
break;
}
auto edge = make_match_spec(dep.value());
auto edge = make_match_spec(problem.dep_id);
node_id src_id = add_solvable(
problem.source_id,
PackageNode{ fixup_pkg(std::move(source).value()) }
);
node_id dep_id = add_solvable(
problem.dep_id,
UnresolvedDependencyNode{ make_match_spec(dep.value()) }
UnresolvedDependencyNode{ make_match_spec(problem.dep_id) }
);
m_graph.add_edge(src_id, dep_id, std::move(edge));
break;
@ -522,7 +530,7 @@ namespace mamba::solver::libsolv
// how the solver is handling this package, as this is resolved in term of
// installed packages and solver flags (allow downgrade...) rather than a
// dependency.
auto edge = make_match_spec(source.value().name);
auto edge = make_match_spec_str(source.value().name);
// The package cannot exist without its name in the pool
assert(m_pool.find_string(edge.name().str()).has_value());
const auto dep_id = m_pool.find_string(edge.name().str()).value();

View File

@ -1038,7 +1038,7 @@ namespace mamba::specs
&& !track_features().has_value();
}
[[nodiscard]] auto MatchSpec::is_only_package_name() const -> bool
auto MatchSpec::is_only_package_name() const -> bool
{
return name().is_exact() //
&& version().is_explicitly_free() //
@ -1046,6 +1046,13 @@ namespace mamba::specs
&& is_simple();
}
auto MatchSpec::to_named_spec() const -> MatchSpec
{
auto out = MatchSpec();
out.m_name = this->m_name;
return out;
}
auto MatchSpec::contains_except_channel(const PackageInfo& pkg) const -> bool
{
struct Pkg

View File

@ -1006,6 +1006,56 @@ namespace
REQUIRE(std::holds_alternative<Solution::Install>(bar_actions.front()));
REQUIRE(std::get<Solution::Install>(bar_actions.front()).install.version == "1.0");
}
SECTION("Unneeded pins are not installed")
{
auto pkg1 = PackageInfo("foo");
pkg1.version = "1.0";
auto pkg2 = PackageInfo("bar");
pkg2.version = "1.0";
db.add_repo_from_packages(std::array{ pkg1, pkg2 });
auto request = Request{
/* .flags= */ {},
/* .jobs= */ { Request::Pin{ "foo=1.0"_ms }, Request::Install{ "bar"_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 foo_actions = find_actions_with_name(solution, "foo");
REQUIRE(foo_actions.empty());
const auto bar_actions = find_actions_with_name(solution, "bar");
REQUIRE(bar_actions.size() == 1);
}
SECTION("Invalid pins are not an error")
{
auto pkg = PackageInfo("bar");
pkg.version = "1.0";
db.add_repo_from_packages(std::array{ pkg });
auto request = Request{
/* .flags= */ {},
/* .jobs= */ { Request::Pin{ "foo=1.0"_ms }, Request::Install{ "bar"_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 foo_actions = find_actions_with_name(solution, "foo");
REQUIRE(foo_actions.empty());
const auto bar_actions = find_actions_with_name(solution, "bar");
REQUIRE(bar_actions.size() == 1);
}
}
TEST_CASE("Handle complex matchspecs")

View File

@ -32,6 +32,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "*");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("xtensor==0.12.3")
@ -40,6 +41,7 @@ namespace
REQUIRE(ms.name().str() == "xtensor");
REQUIRE(ms.version().str() == "==0.12.3");
REQUIRE(ms.str() == "xtensor==0.12.3");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("xtensor >= 0.12.3")
@ -50,6 +52,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "xtensor>=0.12.3");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("python > 3.11")
@ -60,6 +63,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "python>3.11");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("numpy < 2.0")
@ -70,6 +74,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "numpy<2.0");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("pytorch-cpu = 1.13.0")
@ -80,6 +85,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "pytorch-cpu=1.13.0");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("scipy >= 1.5.0, < 2.0.0")
@ -90,6 +96,7 @@ namespace
REQUIRE(ms.build_string().is_explicitly_free());
REQUIRE(ms.build_number().is_explicitly_free());
REQUIRE(ms.str() == "scipy[version=\">=1.5.0,<2.0.0\"]");
REQUIRE_FALSE(ms.is_only_package_name());
}
SECTION("scikit-learn >1.0.0")
@ -194,6 +201,7 @@ namespace
REQUIRE(ms.name().str() == "ipykernel");
REQUIRE(ms.version().is_explicitly_free());
REQUIRE(ms.str() == "ipykernel");
REQUIRE(ms.is_only_package_name());
}
SECTION("ipykernel ")
@ -201,6 +209,7 @@ namespace
auto ms = MatchSpec::parse("ipykernel ").value();
REQUIRE(ms.name().str() == "ipykernel");
REQUIRE(ms.version().is_explicitly_free());
REQUIRE(ms.is_only_package_name());
}
SECTION("disperse=v0.9.24")
@ -793,6 +802,28 @@ namespace
}
}
TEST_CASE("MatchSpec::to_named_spec", "[mamba::specs][mamba::specs::MatchSpec]")
{
SECTION("Alrealy name only")
{
const auto str = GENERATE("foo", "foo ");
const auto ms = MatchSpec::parse(str).value();
const auto ms_named = ms.to_named_spec();
REQUIRE(ms == ms_named);
}
SECTION("With more restrictions")
{
const auto str = GENERATE("foo>1.0", "foo =*", "foo=3=bld");
const auto ms = MatchSpec::parse(str).value();
const auto ms_named = ms.to_named_spec();
REQUIRE(ms_named.name() == ms.name());
REQUIRE(ms_named.version().is_explicitly_free());
REQUIRE(ms_named.build_string().is_explicitly_free());
REQUIRE(ms_named.build_number().is_explicitly_free());
}
}
TEST_CASE("MatchSpec::contains", "[mamba::specs][mamba::specs::MatchSpec]")
{
// Note that tests for individual ``contains`` functions (``VersionSpec::contains``,