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: build_homebrew:
name: Build on homebrew name: Build on homebrew
runs-on: macos-13 runs-on: macos-15
steps: steps:
- name: Checkout mamba repository - name: Checkout mamba repository

View File

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

View File

@ -7,12 +7,12 @@
#ifndef MAMBA_CORE_SOLUTION_HPP #ifndef MAMBA_CORE_SOLUTION_HPP
#define MAMBA_CORE_SOLUTION_HPP #define MAMBA_CORE_SOLUTION_HPP
#include <ranges>
#include <type_traits> #include <type_traits>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "mamba/specs/package_info.hpp" #include "mamba/specs/package_info.hpp"
#include "mamba/util/loop_control.hpp"
#include "mamba/util/type_traits.hpp" #include "mamba/util/type_traits.hpp"
namespace mamba::solver namespace mamba::solver
@ -67,23 +67,40 @@ namespace mamba::solver
using action_list = std::vector<Action>; using action_list = std::vector<Action>;
action_list actions = {}; 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 * * Implementation of Solution *
********************************/ ********************************/
@ -112,35 +129,27 @@ namespace mamba::solver
action 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> inline auto Solution::packages_to_remove() const
void for_each_to_remove(Range&& actions, UnaryFunc&& func)
{ {
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 namespace detail
@ -167,35 +176,27 @@ namespace mamba::solver
action 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 inline auto Solution::packages_to_install() const
template <typename Range, typename UnaryFunc>
void for_each_to_install(Range&& actions, UnaryFunc&& func)
{ {
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 namespace detail
@ -218,35 +219,72 @@ namespace mamba::solver
action 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> inline auto Solution::packages() const
void for_each_to_omit(Range&& actions, UnaryFunc&& func)
{ {
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 #endif

View File

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

View File

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

View File

@ -29,80 +29,108 @@ namespace
Solution::Install{ PackageInfo("install") }, 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); auto remove_count = std::size_t(0);
for_each_to_remove( for (const PackageInfo& pkg : as_const(solution).packages_to_remove())
solution.actions,
[&](const PackageInfo& pkg)
{ {
remove_count++; remove_count++;
const auto has_remove = util::ends_with(pkg.name, "remove") const auto has_remove = util::ends_with(pkg.name, "remove")
|| (pkg.name == "reinstall"); || (pkg.name == "reinstall");
REQUIRE(has_remove); REQUIRE(has_remove);
} }
);
REQUIRE(remove_count == 5); REQUIRE(remove_count == 5);
}
SECTION("Packages to install")
{
auto install_count = std::size_t(0); auto install_count = std::size_t(0);
for_each_to_install( for (const PackageInfo& pkg : as_const(solution).packages_to_install())
solution.actions,
[&](const PackageInfo& pkg)
{ {
install_count++; install_count++;
const auto has_install = util::ends_with(pkg.name, "install") const auto has_install = util::ends_with(pkg.name, "install")
|| (pkg.name == "reinstall"); || (pkg.name == "reinstall");
REQUIRE(has_install); REQUIRE(has_install);
} }
);
REQUIRE(install_count == 5); REQUIRE(install_count == 5);
}
SECTION("Packages to omit")
{
auto omit_count = std::size_t(0); auto omit_count = std::size_t(0);
for_each_to_omit( for (const PackageInfo& pkg : as_const(solution).packages_to_omit())
solution.actions,
[&](const PackageInfo& pkg)
{ {
omit_count++; omit_count++;
REQUIRE(util::ends_with(pkg.name, "omit")); REQUIRE(util::ends_with(pkg.name, "omit"));
} }
);
REQUIRE(omit_count == 1); REQUIRE(omit_count == 1);
} }
SECTION("Iterate over packages and break") SECTION("All packages")
{ {
auto remove_count = std::size_t(0); auto count = std::size_t(0);
for_each_to_remove( for (const PackageInfo& pkg : as_const(solution).packages())
solution.actions,
[&](const PackageInfo&)
{ {
remove_count++; count++;
return util::LoopControl::Break; REQUIRE(!pkg.name.empty());
}
REQUIRE(count == 10);
}
} }
);
REQUIRE(remove_count == 1);
auto install_count = std::size_t(0); SECTION("Ref iterate over packages")
for_each_to_install(
solution.actions,
[&](const PackageInfo&)
{ {
install_count++; SECTION("Packages to remove")
return util::LoopControl::Break; {
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); SECTION("Packages to install")
for_each_to_omit(
solution.actions,
[&](const PackageInfo&)
{ {
omit_count++; for (PackageInfo& pkg : solution.packages_to_install())
return util::LoopControl::Break; {
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_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( .def(
"to_install", "to_install",
[](const Solution& solution) -> std::vector<specs::PackageInfo> [](const Solution& solution) -> std::vector<specs::PackageInfo>
{ {
auto out = std::vector<specs::PackageInfo>{}; // TODO(C++23): std::ranges::to
out.reserve(solution.actions.size()); // Upper bound auto range = solution.packages_to_install();
for_each_to_install( return { range.begin(), range.end() };
solution.actions,
[&](const auto& pkg) { out.push_back(pkg); }
);
return out;
} }
) )
.def( .def(
"to_remove", "to_remove",
[](const Solution& solution) -> std::vector<specs::PackageInfo> [](const Solution& solution) -> std::vector<specs::PackageInfo>
{ {
auto out = std::vector<specs::PackageInfo>{}; // TODO(C++23): std::ranges::to
out.reserve(solution.actions.size()); // Upper bound auto range = solution.packages_to_remove();
for_each_to_remove(solution.actions, [&](const auto& pkg) { out.push_back(pkg); }); return { range.begin(), range.end() };
return out;
} }
) )
.def( .def(
"to_omit", "to_omit",
[](const Solution& solution) -> std::vector<specs::PackageInfo> [](const Solution& solution) -> std::vector<specs::PackageInfo>
{ {
auto out = std::vector<specs::PackageInfo>{}; // TODO(C++23): std::ranges::to
out.reserve(solution.actions.size()); // Upper bound auto range = solution.packages_to_omit();
for_each_to_omit(solution.actions, [&](const auto& pkg) { out.push_back(pkg); }); return { range.begin(), range.end() };
return out;
} }
) )
.def("__copy__", &copy<Solution>) .def("__copy__", &copy<Solution>)