From 63795c6b8c3dac119eb765ef8363ca639b072a36 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Tue, 30 Jan 2024 19:12:02 +0000 Subject: [PATCH] PackageQuery: Add `filter_{latest,earliest}_evr_ignore_arch` Resolves https://github.com/rpm-software-management/dnf5/issues/1111. `filter_latest_evr` and `filter_earliest_evr` group packages by architecture and keep the latest package on each architecture. After `filter_latest_evr`, a PackageQuery might have anaconda-1-1.noarch as well as anaconda-2.2.x86_64. This commit adds `filter_latest_evr_ignore_arch` and `filter_earliest_evr_ignore_arch` which filter for packages with the latest versions regardless of their architecture. Filtering the above anaconda example with `filter_latest_evr_ignore_arch` would result in just {anaconda-2.2.x86_64} since it is the latest version of the package for any arch. This is an ABI-breaking change and thus is targeted at dnf5 5.2. --- include/libdnf5/rpm/package_query.hpp | 14 +++++++ libdnf5/rpm/package_query.cpp | 52 ++++++++++++++++++++++-- test/data/repos-solv/solv-multiarch.repo | 13 ++++++ test/libdnf5/rpm/test_package_query.cpp | 50 +++++++++++++++++++++++ test/libdnf5/rpm/test_package_query.hpp | 4 ++ 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 test/data/repos-solv/solv-multiarch.repo diff --git a/include/libdnf5/rpm/package_query.hpp b/include/libdnf5/rpm/package_query.hpp index a0ac087db0..4ed15d2a3b 100644 --- a/include/libdnf5/rpm/package_query.hpp +++ b/include/libdnf5/rpm/package_query.hpp @@ -618,6 +618,13 @@ class PackageQuery : public PackageSet { // @replaces libdnf/sack/query.hpp:method:addFilter(int keyname, int cmp_type, int match) - cmp_type = HY_PKG_LATEST_PER_ARCH void filter_latest_evr(int limit = 1); + /// Group packages by `name`. Then within each group, keep packages that correspond with up to `limit` of (all but) latest `evr`s in the group. + /// + /// @param limit If `limit` > 0, keep `limit` number `evr`s in each group. + /// If `limit` < 0, keep all **but** `limit` last `evr`s in each group. + /// @since 5.2 + void filter_latest_evr_ignore_arch(int limit = 1); + /// Group packages by `name` and `arch`. Then within each group, keep packages that correspond with up to `limit` of (all but) earliest `evr`s in the group. /// /// @param limit If `limit` > 0, keep `limit` number `evr`s in each group. @@ -625,6 +632,13 @@ class PackageQuery : public PackageSet { /// @since 5.0 void filter_earliest_evr(int limit = 1); + /// Group packages by `name`. Then within each group, keep packages that correspond with up to `limit` of (all but) earliest `evr`s in the group. + /// + /// @param limit If `limit` > 0, keep `limit` number `evr`s in each group. + /// If `limit` < 0, keep all **but** `limit` last `evr`s in each group. + /// @since 5.2 + void filter_earliest_evr_ignore_arch(int limit = 1); + /// Group packages by `name` and `arch`. Then within each group, keep packages that belong to a repo with the highest priority (the lowest number). /// The filter works only on available packages, installed packages are not affected. /// diff --git a/libdnf5/rpm/package_query.cpp b/libdnf5/rpm/package_query.cpp index 40441aab2d..74f0f87f35 100644 --- a/libdnf5/rpm/package_query.cpp +++ b/libdnf5/rpm/package_query.cpp @@ -2435,6 +2435,19 @@ static int latest_cmp(const Id * ap, const Id * bp, libdnf5::solv::RpmPool * poo return *ap - *bp; } +static int latest_ignore_arch_cmp(const Id * ap, const Id * bp, libdnf5::solv::RpmPool * pool) { + Solvable * sa = pool->id2solvable(*ap); + Solvable * sb = pool->id2solvable(*bp); + int r; + r = sa->name - sb->name; + if (r) + return r; + r = pool->evrcmp(sb->evr, sa->evr, EVRCMP_COMPARE); + if (r) + return r; + return *ap - *bp; +} + static int earliest_cmp(const Id * ap, const Id * bp, libdnf5::solv::RpmPool * pool) { Solvable * sa = pool->id2solvable(*ap); Solvable * sb = pool->id2solvable(*bp); @@ -2453,11 +2466,32 @@ static int earliest_cmp(const Id * ap, const Id * bp, libdnf5::solv::RpmPool * p return *ap - *bp; } +static int earliest_ignore_arch_cmp(const Id * ap, const Id * bp, libdnf5::solv::RpmPool * pool) { + Solvable * sa = pool->id2solvable(*ap); + Solvable * sb = pool->id2solvable(*bp); + int r; + r = sa->name - sb->name; + if (r) + return r; + r = pool->evrcmp(sb->evr, sa->evr, EVRCMP_COMPARE); + if (r > 0) + return -1; + if (r < 0) + return 1; + return *ap - *bp; +} + +enum class FilterGrouping { + BY_NAME, + BY_NAME_ARCH, +}; + static void filter_first_sorted_by( libdnf5::solv::RpmPool & pool, int limit, int (*cmp)(const Id * a, const Id * b, libdnf5::solv::RpmPool * pool), - libdnf5::solv::SolvMap & data) { + libdnf5::solv::SolvMap & data, + FilterGrouping grouping) { libdnf5::solv::IdQueue samename; for (Id candidate_id : data) { samename.push_back(candidate_id); @@ -2471,7 +2505,8 @@ static void filter_first_sorted_by( int i; for (i = 0; i < samename.size(); ++i) { Solvable * considered = pool.id2solvable(samename[i]); - if (!highest || highest->name != considered->name || highest->arch != considered->arch) { + if (!highest || highest->name != considered->name || + (grouping == FilterGrouping::BY_NAME_ARCH && (highest->arch != considered->arch))) { /* start of a new block */ if (start_block == -1) { highest = considered; @@ -2489,11 +2524,20 @@ static void filter_first_sorted_by( } void PackageQuery::filter_latest_evr(int limit) { - filter_first_sorted_by(get_rpm_pool(p_impl->base), limit, latest_cmp, *p_impl); + filter_first_sorted_by(get_rpm_pool(p_impl->base), limit, latest_cmp, *p_impl, FilterGrouping::BY_NAME_ARCH); +} + +void PackageQuery::filter_latest_evr_ignore_arch(int limit) { + filter_first_sorted_by(get_rpm_pool(p_impl->base), limit, latest_ignore_arch_cmp, *p_impl, FilterGrouping::BY_NAME); } void PackageQuery::filter_earliest_evr(int limit) { - filter_first_sorted_by(get_rpm_pool(p_impl->base), limit, earliest_cmp, *p_impl); + filter_first_sorted_by(get_rpm_pool(p_impl->base), limit, earliest_cmp, *p_impl, FilterGrouping::BY_NAME_ARCH); +} + +void PackageQuery::filter_earliest_evr_ignore_arch(int limit) { + filter_first_sorted_by( + get_rpm_pool(p_impl->base), limit, earliest_ignore_arch_cmp, *p_impl, FilterGrouping::BY_NAME); } static inline bool priority_solvable_cmp_key(const Solvable * first, const Solvable * second) { diff --git a/test/data/repos-solv/solv-multiarch.repo b/test/data/repos-solv/solv-multiarch.repo new file mode 100644 index 0000000000..b966d66a7d --- /dev/null +++ b/test/data/repos-solv/solv-multiarch.repo @@ -0,0 +1,13 @@ +=Ver: 3.0 + +=Pkg: foo 1.2 1 x86_64 +=Prv: foo = 1.2 1 + +=Pkg: foo 1.2 2 noarch +=Prv: foo = 1.2 2 + +=Pkg: bar 4.5 1 noarch +=Prv: bar = 4.5 1 + +=Pkg: bar 4.5 2 x86_64 +=Prv: bar = 4.5 2 diff --git a/test/libdnf5/rpm/test_package_query.cpp b/test/libdnf5/rpm/test_package_query.cpp index f01e02ced6..c7e4215392 100644 --- a/test/libdnf5/rpm/test_package_query.cpp +++ b/test/libdnf5/rpm/test_package_query.cpp @@ -115,6 +115,31 @@ void RpmPackageQueryTest::test_filter_latest_evr() { } } +void RpmPackageQueryTest::test_filter_latest_evr_ignore_arch() { + add_repo_solv("solv-multiarch"); + + { + // Result of filter_latest_evr should include a package from each arch + PackageQuery query(base); + query.filter_latest_evr(1); + std::vector expected = { + get_pkg("foo-0:1.2-1.x86_64"), + get_pkg("foo-0:1.2-2.noarch"), + get_pkg("bar-0:4.5-1.noarch"), + get_pkg("bar-0:4.5-2.x86_64"), + }; + CPPUNIT_ASSERT_EQUAL(expected, to_vector(query)); + } + { + // Result of filter_latest_evr_ignore_arch should include only the latest + // packages, regardless of arch + PackageQuery query(base); + query.filter_latest_evr_ignore_arch(1); + std::vector expected = {get_pkg("foo-0:1.2-2.noarch"), get_pkg("bar-0:4.5-2.x86_64")}; + CPPUNIT_ASSERT_EQUAL(expected, to_vector(query)); + } +} + void RpmPackageQueryTest::test_filter_earliest_evr() { add_repo_solv("solv-repo1"); add_repo_solv("solv-24pkgs"); @@ -173,6 +198,31 @@ void RpmPackageQueryTest::test_filter_earliest_evr() { } } +void RpmPackageQueryTest::test_filter_earliest_evr_ignore_arch() { + add_repo_solv("solv-multiarch"); + + { + // Result of filter_earliest_evr should include a package from each arch + PackageQuery query(base); + query.filter_earliest_evr(1); + std::vector expected = { + get_pkg("foo-0:1.2-1.x86_64"), + get_pkg("foo-0:1.2-2.noarch"), + get_pkg("bar-0:4.5-1.noarch"), + get_pkg("bar-0:4.5-2.x86_64"), + }; + CPPUNIT_ASSERT_EQUAL(expected, to_vector(query)); + } + { + // Result of filter_earliest_evr_ignore_arch should include only the earliest + // packages, regardless of arch + PackageQuery query(base); + query.filter_earliest_evr_ignore_arch(1); + std::vector expected = {get_pkg("foo-0:1.2-1.x86_64"), get_pkg("bar-0:4.5-1.noarch")}; + CPPUNIT_ASSERT_EQUAL(expected, to_vector(query)); + } +} + void RpmPackageQueryTest::test_filter_name() { add_repo_solv("solv-repo1"); diff --git a/test/libdnf5/rpm/test_package_query.hpp b/test/libdnf5/rpm/test_package_query.hpp index 35e4ad1c78..5c4c50f609 100644 --- a/test/libdnf5/rpm/test_package_query.hpp +++ b/test/libdnf5/rpm/test_package_query.hpp @@ -33,7 +33,9 @@ class RpmPackageQueryTest : public BaseTestCase { #ifndef WITH_PERFORMANCE_TESTS CPPUNIT_TEST(test_size); CPPUNIT_TEST(test_filter_latest_evr); + CPPUNIT_TEST(test_filter_latest_evr_ignore_arch); CPPUNIT_TEST(test_filter_earliest_evr); + CPPUNIT_TEST(test_filter_earliest_evr_ignore_arch); CPPUNIT_TEST(test_filter_name); CPPUNIT_TEST(test_filter_name_packgset); CPPUNIT_TEST(test_filter_nevra_packgset); @@ -66,7 +68,9 @@ class RpmPackageQueryTest : public BaseTestCase { void test_size(); void test_filter_latest_evr(); + void test_filter_latest_evr_ignore_arch(); void test_filter_earliest_evr(); + void test_filter_earliest_evr_ignore_arch(); void test_filter_name(); void test_filter_name_packgset(); void test_filter_nevra_packgset();