Use range in Solution (#3968)

This commit is contained in:
Antoine Prouvost 2025-06-06 22:34:33 +02:00 committed by GitHub
parent 7d52d6004c
commit f771e520f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 325 additions and 274 deletions

View File

@ -29,7 +29,7 @@ jobs:
build_homebrew:
name: Build on homebrew
runs-on: macos-13
runs-on: macos-15
steps:
- name: Checkout mamba repository

View File

@ -97,8 +97,8 @@ function(mamba_target_add_compile_warnings target)
-Wconversion
# Warn on sign conversions
-Wsign-conversion
# Warn if a null dereference is detected
-Wnull-dereference
# Warn if a null dereference is detected Deactivated because it produced too many false
# positive with ranges while we are not doing many pointer operations. -Wnull-dereference
# Warn if float is implicit promoted to double
-Wdouble-promotion
# Warn on security issues around functions that format output (ie printf)

View File

@ -7,12 +7,12 @@
#ifndef MAMBA_CORE_SOLUTION_HPP
#define MAMBA_CORE_SOLUTION_HPP
#include <ranges>
#include <type_traits>
#include <variant>
#include <vector>
#include "mamba/specs/package_info.hpp"
#include "mamba/util/loop_control.hpp"
#include "mamba/util/type_traits.hpp"
namespace mamba::solver
@ -67,23 +67,40 @@ namespace mamba::solver
using action_list = std::vector<Action>;
action_list actions = {};
/**
* Return a view of all unique packages involved in the solution.
*
* The view is invalidated if @ref actions is modified.
*/
[[nodiscard]] auto packages() const;
[[nodiscard]] auto packages();
/**
* Return a view of all packages that need to be removed.
*
* The view is invalidated if @ref actions is modified.
*/
[[nodiscard]] auto packages_to_remove() const;
[[nodiscard]] auto packages_to_remove();
/**
* Return a view of all packages that need to be installed.
*
* The view is invalidated if @ref actions is modified.
*/
[[nodiscard]] auto packages_to_install() const;
[[nodiscard]] auto packages_to_install();
/**
* Return a view of all packages that are omitted.
*
* The view is invalidated if @ref actions is modified.
*/
[[nodiscard]] auto packages_to_omit() const;
[[nodiscard]] auto packages_to_omit();
};
template <typename Iter, typename UnaryFunc>
void for_each_to_remove(Iter first, Iter last, UnaryFunc&& func);
template <typename Range, typename UnaryFunc>
void for_each_to_remove(Range&& actions, UnaryFunc&& func);
template <typename Iter, typename UnaryFunc>
void for_each_to_install(Iter first, Iter last, UnaryFunc&& func);
template <typename Range, typename UnaryFunc>
void for_each_to_install(Range&& actions, UnaryFunc&& func);
template <typename Iter, typename UnaryFunc>
void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func);
template <typename Range, typename UnaryFunc>
void for_each_to_omit(Range&& actions, UnaryFunc&& func);
/********************************
* Implementation of Solution *
********************************/
@ -112,35 +129,27 @@ namespace mamba::solver
action
);
}
template <std::ranges::range Range>
auto packages_to_remove_impl(Range& actions)
{
namespace views = std::ranges::views;
return actions //
| views::transform([](auto& a) { return detail::to_remove_ptr(a); })
| views::filter([](const auto* ptr) { return ptr != nullptr; })
| views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
}
// TODO(C++20): Poor man's replacement to range filter transform
template <typename Iter, typename UnaryFunc>
void for_each_to_remove(Iter first, Iter last, UnaryFunc&& func)
{
for (; first != last; ++first)
{
if (auto* const ptr = detail::to_remove_ptr(*first))
{
if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
{
if (func(*ptr) == util::LoopControl::Break)
{
break;
}
}
else
{
func(*ptr);
}
}
}
}
template <typename Range, typename UnaryFunc>
void for_each_to_remove(Range&& actions, UnaryFunc&& func)
inline auto Solution::packages_to_remove() const
{
return for_each_to_remove(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
return detail::packages_to_remove_impl(actions);
}
inline auto Solution::packages_to_remove()
{
return detail::packages_to_remove_impl(actions);
}
namespace detail
@ -167,35 +176,27 @@ namespace mamba::solver
action
);
}
template <std::ranges::range Range>
auto packages_to_install_impl(Range& actions)
{
namespace views = std::ranges::views;
return actions //
| views::transform([](auto& a) { return detail::to_install_ptr(a); })
| views::filter([](const auto* ptr) { return ptr != nullptr; })
| views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
}
template <typename Iter, typename UnaryFunc>
void for_each_to_install(Iter first, Iter last, UnaryFunc&& func)
{
for (; first != last; ++first)
{
if (auto* const ptr = detail::to_install_ptr(*first))
{
if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
{
if (func(*ptr) == util::LoopControl::Break)
{
break;
}
}
else
{
func(*ptr);
}
}
}
}
// TODO(C++20): Poor man's replacement to range filter transform
template <typename Range, typename UnaryFunc>
void for_each_to_install(Range&& actions, UnaryFunc&& func)
inline auto Solution::packages_to_install() const
{
return for_each_to_install(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
return detail::packages_to_install_impl(actions);
}
inline auto Solution::packages_to_install()
{
return detail::packages_to_install_impl(actions);
}
namespace detail
@ -218,35 +219,72 @@ namespace mamba::solver
action
);
}
template <std::ranges::range Range>
auto packages_to_omit_impl(Range& actions)
{
namespace views = std::ranges::views;
return actions //
| views::transform([](auto& a) { return detail::to_omit_ptr(a); })
| views::filter([](const auto* ptr) { return ptr != nullptr; })
| views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
}
// TODO(C++20): Poor man's replacement to range filter transform
template <typename Iter, typename UnaryFunc>
void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func)
}
inline auto Solution::packages_to_omit() const
{
for (; first != last; ++first)
return detail::packages_to_omit_impl(actions);
}
inline auto Solution::packages_to_omit()
{
if (auto* const ptr = detail::to_omit_ptr(*first))
return detail::packages_to_omit_impl(actions);
}
namespace detail
{
if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
template <typename Action>
constexpr auto package_unique_ptrs(Action& action)
{
if (func(*ptr) == util::LoopControl::Break)
auto out = std::array{
to_omit_ptr(action),
to_install_ptr(action),
to_remove_ptr(action),
};
for (std::size_t i = 1; i < out.size(); ++i)
{
break;
for (std::size_t j = i + 1; j < out.size(); ++j)
{
if (out[j] == out[i])
{
out[j] = nullptr;
}
}
else
}
return out;
}
template <std::ranges::range Range>
auto packages_impl(Range& actions)
{
func(*ptr);
}
}
namespace views = std::ranges::views;
return actions //
| views::transform([](auto& a) { return package_unique_ptrs(a); }) //
| views::join //
| views::filter([](const auto* ptr) { return ptr != nullptr; }) //
| views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
}
}
template <typename Range, typename UnaryFunc>
void for_each_to_omit(Range&& actions, UnaryFunc&& func)
inline auto Solution::packages() const
{
return for_each_to_omit(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
return detail::packages_impl(actions);
}
inline auto Solution::packages()
{
return detail::packages_impl(actions);
}
}
#endif

View File

@ -7,6 +7,7 @@
#include <algorithm>
#include <iostream>
#include <iterator>
#include <ranges>
#include <stack>
#include <string>
#include <utility>
@ -242,16 +243,13 @@ namespace mamba
else
{
// The specs to install become all the dependencies of the non intstalled specs
for_each_to_omit(
m_solution.actions,
[&](const specs::PackageInfo& pkg)
for (const specs::PackageInfo& pkg : m_solution.packages_to_omit())
{
for (const auto& dep : pkg.dependencies)
{
m_history_entry.update.push_back(dep);
}
}
);
}
using Request = solver::Request;
@ -384,7 +382,7 @@ namespace mamba
// to be URL like (i.e. explicit). Below is a loop to fix the channel of the linked
// packages (fix applied to the unlinked packages to avoid potential bugs). Ideally, this
// should be normalised when reading the data.
const auto fix_channel = [&](specs::PackageInfo& pkg)
for (specs::PackageInfo& pkg : m_solution.packages())
{
auto unresolved_pkg_channel = mamba::specs::UnresolvedChannel::parse(pkg.channel).value();
auto pkg_channel = mamba::specs::Channel::resolve(
@ -395,32 +393,15 @@ namespace mamba
auto channel_url = pkg_channel[0].platform_url(pkg.platform).str();
pkg.channel = channel_url;
};
for_each_to_install(m_solution.actions, fix_channel);
for_each_to_remove(m_solution.actions, fix_channel);
for_each_to_omit(m_solution.actions, fix_channel);
TransactionRollback rollback;
TransactionContext transaction_context(ctx.transaction_params(), m_py_versions, m_requested_specs);
const auto link = [&](const specs::PackageInfo& pkg)
for (const specs::PackageInfo& pkg : m_solution.packages_to_remove())
{
if (is_sig_interrupted())
{
return util::LoopControl::Break;
}
Console::stream() << "Linking " << pkg.str();
const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg, false));
LinkPackage lp(pkg, cache_path, &transaction_context);
lp.execute();
rollback.record(lp);
m_history_entry.link_dists.push_back(pkg.long_str());
return util::LoopControl::Continue;
};
const auto unlink = [&](const specs::PackageInfo& pkg)
{
if (is_sig_interrupted())
{
return util::LoopControl::Break;
break;
}
Console::stream() << "Unlinking " << pkg.str();
const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg));
@ -428,11 +409,21 @@ namespace mamba
up.execute();
rollback.record(up);
m_history_entry.unlink_dists.push_back(pkg.long_str());
return util::LoopControl::Continue;
};
}
for_each_to_remove(m_solution.actions, unlink);
for_each_to_install(m_solution.actions, link);
for (const specs::PackageInfo& pkg : m_solution.packages_to_install())
{
if (is_sig_interrupted())
{
break;
}
Console::stream() << "Linking " << pkg.str();
const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg, false));
LinkPackage lp(pkg, cache_path, &transaction_context);
lp.execute();
rollback.record(lp);
m_history_entry.link_dists.push_back(pkg.long_str());
}
if (is_sig_interrupted())
{
@ -451,25 +442,29 @@ namespace mamba
auto MTransaction::to_conda() -> to_conda_type
{
to_remove_type to_remove_structured = {};
to_remove_structured.reserve(m_solution.actions.size()); // Upper bound
for_each_to_remove(
m_solution.actions,
[&](const auto& pkg)
{
to_remove_structured.emplace_back(pkg.channel, pkg.filename); //
}
);
namespace views = std::ranges::views;
to_install_type to_install_structured = {};
to_install_structured.reserve(m_solution.actions.size()); // Upper bound
for_each_to_install(
m_solution.actions,
[&](const auto& pkg)
auto to_remove_range = m_solution.packages_to_remove() //
| views::transform(
[](const auto& pkg)
{ return to_remove_type::value_type(pkg.channel, pkg.filename); }
);
// TODO(C++23): std::ranges::to
auto to_remove_structured = to_remove_type(to_remove_range.begin(), to_remove_range.end());
auto to_install_range = m_solution.packages_to_install() //
| views::transform(
[](const auto& pkg)
{
to_install_structured.emplace_back(pkg.channel, pkg.filename, nl::json(pkg).dump(4)); //
return to_install_type::value_type(
pkg.channel,
pkg.filename,
nl::json(pkg).dump(4)
);
}
);
// TODO(C++23): std::ranges::to
auto to_install_structured = to_install_type(to_install_range.begin(), to_install_range.end());
to_specs_type specs;
std::get<0>(specs) = m_history_entry.update;
@ -480,27 +475,22 @@ namespace mamba
void MTransaction::log_json()
{
std::vector<nl::json> to_fetch, to_link, to_unlink;
namespace views = std::ranges::views;
for_each_to_install(
m_solution.actions,
[&](const auto& pkg)
{
if (need_pkg_download(pkg, m_multi_cache))
{
to_fetch.push_back(nl::json(pkg));
}
to_link.push_back(nl::json(pkg));
}
);
// TODO(C++23): std::ranges::to
auto to_fetch_range = m_solution.packages_to_install()
| views::filter([this](const auto& pkg)
{ return need_pkg_download(pkg, m_multi_cache); });
auto to_fetch = std::vector<nl::json>(to_fetch_range.begin(), to_fetch_range.end());
for_each_to_remove(
m_solution.actions,
[&](const auto& pkg)
{
to_unlink.push_back(nl::json(pkg)); //
}
);
// TODO(C++23): std::ranges::to
auto to_link_range = m_solution.packages_to_install();
auto to_link = std::vector<nl::json>(to_link_range.begin(), to_link_range.end());
// TODO(C++23): std::ranges::to
auto to_unlink_range = m_solution.packages_to_remove();
auto to_unlink = std::vector<nl::json>(to_unlink_range.begin(), to_unlink_range.end());
auto add_json = [](const auto& jlist, const char* s)
{
@ -540,18 +530,12 @@ namespace mamba
{
LOG_INFO << "Content trust is enabled, package(s) signatures will be verified";
}
for_each_to_install(
solution.actions,
[&](const auto& pkg)
for (const auto& pkg : solution.packages_to_install())
{
if (ctx.validation_params.verify_artifacts)
{
LOG_INFO << "Creating RepoChecker...";
auto repo_checker_store = RepoCheckerStore::make(
ctx,
channel_context,
multi_cache
);
auto repo_checker_store = RepoCheckerStore::make(ctx, channel_context, multi_cache);
for (auto& chan : channel_context.make_channel(pkg.channel))
{
auto repo_checker = repo_checker_store.find_checker(chan);
@ -584,8 +568,7 @@ namespace mamba
{
auto channels = channel_context.make_channel(pkg.package_url);
assert(channels.size() == 1); // A URL can only resolve to one channel
l_pkg.package_url = channels.front().platform_urls().at(0).str(
Credentials::Show
l_pkg.package_url = channels.front().platform_urls().at(0).str(Credentials::Show
);
}
{
@ -600,7 +583,6 @@ namespace mamba
fetchers.emplace_back(pkg, multi_cache);
}
}
);
if (ctx.validation_params.verify_artifacts)
{

View File

@ -4,6 +4,8 @@
//
// The full license is in the file LICENSE, distributed with this software.
#include <ranges>
#include "solver/helpers.hpp"
namespace mamba::solver
@ -11,20 +13,13 @@ namespace mamba::solver
auto find_new_python_in_solution(const Solution& solution)
-> std::optional<std::reference_wrapper<const specs::PackageInfo>>
{
auto out = std::optional<std::reference_wrapper<const specs::PackageInfo>>{};
for_each_to_install(
solution.actions,
[&](const auto& pkg)
auto packages = solution.packages_to_install();
auto it = std::ranges::find_if(packages, [](const auto& pkg) { return pkg.name == "python"; });
if (it != packages.end())
{
if (pkg.name == "python")
{
out = std::cref(pkg);
return util::LoopControl::Break;
return std::cref(*it);
}
return util::LoopControl::Continue;
}
);
return out;
return std::nullopt;
}
auto python_binary_compatible(const specs::Version& older, const specs::Version& newer) -> bool

View File

@ -29,80 +29,108 @@ namespace
Solution::Install{ PackageInfo("install") },
} };
SECTION("Iterate over packages")
constexpr auto as_const = [](const auto& x) -> decltype(auto) { return x; };
SECTION("Const iterate over packages")
{
SECTION("Packages to remove")
{
auto remove_count = std::size_t(0);
for_each_to_remove(
solution.actions,
[&](const PackageInfo& pkg)
for (const PackageInfo& pkg : as_const(solution).packages_to_remove())
{
remove_count++;
const auto has_remove = util::ends_with(pkg.name, "remove")
|| (pkg.name == "reinstall");
REQUIRE(has_remove);
}
);
REQUIRE(remove_count == 5);
}
SECTION("Packages to install")
{
auto install_count = std::size_t(0);
for_each_to_install(
solution.actions,
[&](const PackageInfo& pkg)
for (const PackageInfo& pkg : as_const(solution).packages_to_install())
{
install_count++;
const auto has_install = util::ends_with(pkg.name, "install")
|| (pkg.name == "reinstall");
REQUIRE(has_install);
}
);
REQUIRE(install_count == 5);
}
SECTION("Packages to omit")
{
auto omit_count = std::size_t(0);
for_each_to_omit(
solution.actions,
[&](const PackageInfo& pkg)
for (const PackageInfo& pkg : as_const(solution).packages_to_omit())
{
omit_count++;
REQUIRE(util::ends_with(pkg.name, "omit"));
}
);
REQUIRE(omit_count == 1);
}
SECTION("Iterate over packages and break")
SECTION("All packages")
{
auto remove_count = std::size_t(0);
for_each_to_remove(
solution.actions,
[&](const PackageInfo&)
auto count = std::size_t(0);
for (const PackageInfo& pkg : as_const(solution).packages())
{
remove_count++;
return util::LoopControl::Break;
count++;
REQUIRE(!pkg.name.empty());
}
REQUIRE(count == 10);
}
}
);
REQUIRE(remove_count == 1);
auto install_count = std::size_t(0);
for_each_to_install(
solution.actions,
[&](const PackageInfo&)
SECTION("Ref iterate over packages")
{
install_count++;
return util::LoopControl::Break;
SECTION("Packages to remove")
{
for (PackageInfo& pkg : solution.packages_to_remove())
{
pkg.name = "";
}
for (const PackageInfo& pkg : solution.packages_to_remove())
{
CHECK(pkg.name == "");
}
}
);
REQUIRE(install_count == 1);
auto omit_count = std::size_t(0);
for_each_to_omit(
solution.actions,
[&](const PackageInfo&)
SECTION("Packages to install")
{
omit_count++;
return util::LoopControl::Break;
for (PackageInfo& pkg : solution.packages_to_install())
{
pkg.name = "";
}
for (const PackageInfo& pkg : solution.packages_to_install())
{
CHECK(pkg.name == "");
}
}
SECTION("Packages to omit")
{
for (PackageInfo& pkg : solution.packages_to_omit())
{
pkg.name = "";
}
for (const PackageInfo& pkg : solution.packages_to_omit())
{
CHECK(pkg.name == "");
}
}
SECTION("All packages")
{
for (PackageInfo& pkg : solution.packages())
{
pkg.name = "";
}
for (const PackageInfo& pkg : solution.packages())
{
CHECK(pkg.name == "");
}
}
);
REQUIRE(omit_count == 1);
}
}
}

View File

@ -301,37 +301,45 @@ namespace mambapy
}
))
.def_readwrite("actions", &Solution::actions)
.def(
"packages",
[](const Solution& solution) -> std::vector<specs::PackageInfo>
{
// TODO(C++23): std::ranges::to
auto out = std::vector<specs::PackageInfo>{};
out.reserve(solution.actions.size()); // Lower bound
for (const auto& pkg : solution.packages())
{
out.push_back(pkg);
}
return out;
}
)
.def(
"to_install",
[](const Solution& solution) -> std::vector<specs::PackageInfo>
{
auto out = std::vector<specs::PackageInfo>{};
out.reserve(solution.actions.size()); // Upper bound
for_each_to_install(
solution.actions,
[&](const auto& pkg) { out.push_back(pkg); }
);
return out;
// TODO(C++23): std::ranges::to
auto range = solution.packages_to_install();
return { range.begin(), range.end() };
}
)
.def(
"to_remove",
[](const Solution& solution) -> std::vector<specs::PackageInfo>
{
auto out = std::vector<specs::PackageInfo>{};
out.reserve(solution.actions.size()); // Upper bound
for_each_to_remove(solution.actions, [&](const auto& pkg) { out.push_back(pkg); });
return out;
// TODO(C++23): std::ranges::to
auto range = solution.packages_to_remove();
return { range.begin(), range.end() };
}
)
.def(
"to_omit",
[](const Solution& solution) -> std::vector<specs::PackageInfo>
{
auto out = std::vector<specs::PackageInfo>{};
out.reserve(solution.actions.size()); // Upper bound
for_each_to_omit(solution.actions, [&](const auto& pkg) { out.push_back(pkg); });
return out;
// TODO(C++23): std::ranges::to
auto range = solution.packages_to_omit();
return { range.begin(), range.end() };
}
)
.def("__copy__", &copy<Solution>)