From d188a1b9677ec0cd8c2298ae94e2c7c26f4c93b3 Mon Sep 17 00:00:00 2001 From: q66 Date: Mon, 18 Nov 2019 01:44:17 +0100 Subject: [PATCH] xbps: add patch to properly prune alternatives on updates Required for https://github.com/void-linux/void-packages/pull/16458. The next OpenJDK8 update will involve renaming to `openjdk8`. With current xbps that will result in failures as we need to create a transitional package that, unlike the real one, has no alternatives, and xbps currently does not account for that. --- srcpkgs/xbps/patches/85b8b3b.patch | 355 +++++++++++++++++++++++++++++ srcpkgs/xbps/template | 2 +- 2 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 srcpkgs/xbps/patches/85b8b3b.patch diff --git a/srcpkgs/xbps/patches/85b8b3b.patch b/srcpkgs/xbps/patches/85b8b3b.patch new file mode 100644 index 0000000000..9dc84257c1 --- /dev/null +++ b/srcpkgs/xbps/patches/85b8b3b.patch @@ -0,0 +1,355 @@ +From 85b8b3bbb72ab6de6f4d72c9a2ca3f2be053eeb1 Mon Sep 17 00:00:00 2001 +From: q66 +Date: Thu, 14 Nov 2019 17:40:54 +0100 +Subject: [PATCH] lib/package_alternatives.c: prune obsolete alternatives + groups + +In the edge case when an updated package has different (or no) +alternatives groups, make sure to prune those that are in pkgdb +but not in the newly installed package. + +A potentially common case of this is when a package that formerly +had alternatives gets removed and a transitional metapackage +takes its place (which has no alternatives). + +When the new package has no dependencies, oldest next possible +alternatives group will be used. This is because that indicates +a removed package. When there are dependencies, the newest one +will be used; as this indicates a transitional package. +--- + lib/package_alternatives.c | 155 +++++++++++++++++----- + tests/xbps/xbps-alternatives/main_test.sh | 93 +++++++++++++ + 2 files changed, 218 insertions(+), 30 deletions(-) + +diff --git a/lib/package_alternatives.c b/lib/package_alternatives.c +index 5e4f2b36..ecdc40ae 100644 +--- lib/package_alternatives.c ++++ lib/package_alternatives.c +@@ -309,6 +309,22 @@ xbps_alternatives_set(struct xbps_handle *xhp, const char *pkgname, + return rv; + } + ++static int ++switch_alt_group(struct xbps_handle *xhp, const char *grpn, const char *pkgn, ++ xbps_dictionary_t *pkg_alternatives) ++{ ++ xbps_dictionary_t curpkgd, pkgalts; ++ ++ curpkgd = xbps_pkgdb_get_pkg(xhp, pkgn); ++ assert(curpkgd); ++ ++ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL, ++ "Switched '%s' alternatives group to '%s'", grpn, pkgn); ++ pkgalts = xbps_dictionary_get(curpkgd, "alternatives"); ++ if (pkg_alternatives) *pkg_alternatives = pkgalts; ++ return create_symlinks(xhp, xbps_dictionary_get(pkgalts, grpn), grpn); ++} ++ + int + xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd) + { +@@ -339,7 +355,6 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd) + for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) { + xbps_array_t array; + xbps_object_t keysym; +- xbps_dictionary_t curpkgd = pkgd; + bool current = false; + const char *first = NULL, *keyname; + +@@ -377,15 +392,7 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd) + continue; + + /* get the new alternative group package */ +- curpkgd = xbps_pkgdb_get_pkg(xhp, first); +- assert(curpkgd); +- xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL, +- "Switched '%s' alternatives group to '%s'", keyname, first); +- pkg_alternatives = xbps_dictionary_get(curpkgd, "alternatives"); +- rv = create_symlinks(xhp, +- xbps_dictionary_get(pkg_alternatives, keyname), +- keyname); +- if (rv != 0) ++ if (switch_alt_group(xhp, keyname, first, &pkg_alternatives) != 0) + break; + } + xbps_object_release(allkeys); +@@ -394,25 +401,119 @@ xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd) + return rv; + } + ++/* ++ * Prune the alternatives group from the db. This will first unregister ++ * it for the package and if there's no other package left providing the ++ * same, also ditch the whole group. When this is called, it is guranteed ++ * that what is happening is an upgrade, because it's only invoked when ++ * the repo and installed alternatives sets differ for a specific package. ++ */ ++static void ++prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod, ++ char *pkgname, const char *pkgver, const char *keyname) { ++ const char *newpkg, *curpkg; ++ xbps_array_t array; ++ xbps_dictionary_t alternatives; ++ xbps_string_t kstr; ++ unsigned int grp_count; ++ bool current = false; ++ ++ xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL, ++ "%s: unregistered '%s' alternatives group", pkgver, keyname); ++ ++ alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_"); ++ assert(alternatives); ++ array = xbps_dictionary_get(alternatives, keyname); ++ ++ /* if using alt group from another package, we won't switch anything */ ++ xbps_array_get_cstring_nocopy(array, 0, &curpkg); ++ current = (strcmp(pkgname, curpkg) == 0); ++ ++ /* actually prune the alt group for the current package */ ++ xbps_remove_string_from_array(array, pkgname); ++ grp_count = xbps_array_count(array); ++ if (grp_count == 0) { ++ /* it was the last one, ditch the whole thing */ ++ xbps_dictionary_remove(alternatives, keyname); ++ return; ++ } ++ if (!current) { ++ /* not the last one, and ours wasn't the one being used */ ++ return; ++ } ++ ++ if (xbps_array_count(xbps_dictionary_get(repod, "run_depends")) == 0 && ++ xbps_array_count(xbps_dictionary_get(repod, "shlib-requires")) == 0) { ++ /* ++ * Empty dependencies indicate a removed package (pure meta), ++ * use the first available group after ours has been pruned ++ */ ++ xbps_array_get_cstring_nocopy(array, 0, &newpkg); ++ switch_alt_group(xhp, keyname, newpkg, NULL); ++ return; ++ } ++ ++ /* ++ * Use the last group, as this indicates that a transitional metapackage ++ * is replacing the original and therefore a new package has registered ++ * a replacement group, which should be last in the array (most recent). ++ */ ++ xbps_array_get_cstring_nocopy(array, grp_count - 1, &newpkg); ++ ++ /* put the new package as head */ ++ kstr = xbps_string_create_cstring(newpkg); ++ xbps_remove_string_from_array(array, newpkg); ++ xbps_array_add_first(array, kstr); ++ xbps_array_get_cstring_nocopy(array, 0, &newpkg); ++ xbps_object_release(kstr); ++ ++ switch_alt_group(xhp, keyname, newpkg, NULL); ++} ++ ++ + static void +-remove_obsoletes(struct xbps_handle *xhp, xbps_dictionary_t pkgd, xbps_dictionary_t repod) ++remove_obsoletes(struct xbps_handle *xhp, char *pkgname, const char *pkgver, ++ xbps_dictionary_t repod) + { + xbps_array_t allkeys; ++ xbps_dictionary_t pkgd, pkgd_alts, repod_alts; + +- allkeys = xbps_dictionary_all_keys(pkgd); ++ pkgd = xbps_pkgdb_get_pkg(xhp, pkgname); ++ if (xbps_object_type(pkgd) != XBPS_TYPE_DICTIONARY) { ++ return; ++ } ++ ++ pkgd_alts = xbps_dictionary_get(pkgd, "alternatives"); ++ repod_alts = xbps_dictionary_get(repod, "alternatives"); ++ ++ if (xbps_object_type(pkgd_alts) != XBPS_TYPE_DICTIONARY) { ++ return; ++ } ++ ++ allkeys = xbps_dictionary_all_keys(pkgd_alts); + for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) { + xbps_array_t array, array_repo; + xbps_object_t keysym; + const char *keyname; + + keysym = xbps_array_get(allkeys, i); +- array = xbps_dictionary_get_keysym(pkgd, keysym); ++ array = xbps_dictionary_get_keysym(pkgd_alts, keysym); + keyname = xbps_dictionary_keysym_cstring_nocopy(keysym); + +- array_repo = xbps_dictionary_get(repod, keyname); ++ array_repo = xbps_dictionary_get(repod_alts, keyname); + if (!xbps_array_equals(array, array_repo)) { + remove_symlinks(xhp, array, keyname); + } ++ ++ /* ++ * There is nothing left in the alternatives group, which means ++ * the package is being upgraded and is removing it; if we don't ++ * prune it, the system will keep it set after removal of its ++ * parent package, but it will be empty and invalid... ++ */ ++ if (xbps_array_count(array_repo) == 0) { ++ prune_altgroup(xhp, repod, pkgname, pkgver, keyname); ++ } + } + xbps_object_release(allkeys); + } +@@ -421,7 +522,7 @@ int + xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod) + { + xbps_array_t allkeys; +- xbps_dictionary_t alternatives, pkg_alternatives, pkgd, pkgd_alts; ++ xbps_dictionary_t alternatives, pkg_alternatives; + const char *pkgver; + char *pkgname; + int rv = 0; +@@ -431,10 +532,6 @@ xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod) + if (xhp->pkgdb == NULL) + return EINVAL; + +- pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives"); +- if (!xbps_dictionary_count(pkg_alternatives)) +- return 0; +- + alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_"); + if (alternatives == NULL) { + alternatives = xbps_dictionary_create(); +@@ -449,17 +546,15 @@ xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod) + if (pkgname == NULL) + return EINVAL; + +- pkgd = xbps_pkgdb_get_pkg(xhp, pkgname); +- if (xbps_object_type(pkgd) == XBPS_TYPE_DICTIONARY) { +- /* +- * Compare alternatives from pkgdb and repo and +- * then remove obsolete symlinks. +- */ +- pkgd_alts = xbps_dictionary_get(pkgd, "alternatives"); +- if (xbps_object_type(pkgd_alts) == XBPS_TYPE_DICTIONARY) { +- remove_obsoletes(xhp, pkgd_alts, pkg_alternatives); +- } +- } ++ /* ++ * Compare alternatives from pkgdb and repo and then remove obsolete ++ * symlinks, also remove obsolete (empty) alternatives groups. ++ */ ++ remove_obsoletes(xhp, pkgname, pkgver, pkg_repod); ++ ++ pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives"); ++ if (!xbps_dictionary_count(pkg_alternatives)) ++ return 0; + + allkeys = xbps_dictionary_all_keys(pkg_alternatives); + for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) { +diff --git a/tests/xbps/xbps-alternatives/main_test.sh b/tests/xbps/xbps-alternatives/main_test.sh +index fd66bcbf..4eddc411 100644 +--- tests/xbps/xbps-alternatives/main_test.sh ++++ tests/xbps/xbps-alternatives/main_test.sh +@@ -674,6 +674,98 @@ respect_current_provider_body() { + atf_check_equal $rv 0 + } + ++atf_test_case prune_leftover_groups ++ ++prune_leftover_groups_head() { ++ atf_set "descr" "xbps-alternatives: prune leftover groups on upgrades" ++} ++prune_leftover_groups_body() { ++ mkdir -p repo pkg_A/usr/bin pkg_B/usr/bin ++ touch pkg_A/usr/bin/fileA pkg_B/usr/bin/fileB ++ cd repo ++ xbps-create -A noarch -n A-1.1_1 -s "A pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileA" ../pkg_A ++ atf_check_equal $? 0 ++ xbps-create -A noarch -n B-1.1_1 -s "B pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileB" ../pkg_B ++ atf_check_equal $? 0 ++ xbps-rindex -d -a $PWD/*.xbps ++ atf_check_equal $? 0 ++ cd .. ++ ++ # A is the current provider now ++ xbps-install -r root --repository=repo -ydv A ++ atf_check_equal $? 0 ++ ++ out=$(xbps-query -r root -p pkgver A) ++ atf_check_equal $out A-1.1_1 ++ ++ # C will replace it via a transitional package ++ mkdir -p pkg_C/usr/bin ++ touch pkg_C/usr/bin/fileC ++ rm pkg_A/usr/bin/fileA ++ cd repo ++ xbps-create -A noarch -n C-1.2_1 -s "C pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileC" ../pkg_C ++ atf_check_equal $? 0 ++ xbps-create -A noarch -n A-1.2_1 -s "A pkg" --dependencies "C>=1.2_1" ../pkg_A ++ atf_check_equal $? 0 ++ xbps-rindex -d -a $PWD/*.xbps ++ atf_check_equal $? 0 ++ cd .. ++ ++ # C is now the current provider, via upgraded A ++ # also install B, to make sure it doesn't get that first ++ xbps-install -r root --repository=repo -ydv B A ++ ++ out=$(xbps-query -r root -p pkgver A) ++ atf_check_equal $out A-1.2_1 ++ out=$(xbps-query -r root -p pkgver B) ++ atf_check_equal $out B-1.1_1 ++ out=$(xbps-query -r root -p pkgver C) ++ atf_check_equal $out C-1.2_1 ++ ++ lnk=$(readlink -f root/usr/bin/file) ++ rv=1 ++ if [ "$lnk" = "$PWD/root/usr/bin/fileC" ]; then ++ rv=0 ++ fi ++ echo "lnk: $lnk" ++ atf_check_equal $rv 0 ++ ++ # Create a new provider, D; then make C a removed package ++ mkdir -p pkg_D/usr/bin ++ touch pkg_D/usr/bin/fileD ++ rm pkg_C/usr/bin/fileC ++ cd repo ++ xbps-create -A noarch -n D-1.4_1 -s "D pkg" --alternatives "file:/usr/bin/file:/usr/bin/fileD" ../pkg_D ++ atf_check_equal $? 0 ++ xbps-create -A noarch -n C-1.3_1 -s "C pkg" ../pkg_C ++ atf_check_equal $? 0 ++ xbps-rindex -d -a $PWD/*.xbps ++ atf_check_equal $? 0 ++ cd .. ++ ++ # B is now the current provider, as it's the first group available after ++ # pruning C; the system special cases packages without dependencies as ++ # removed packages, so it will not assume a renamed replacement ++ xbps-install -r root --repository=repo -ydv C D ++ ++ out=$(xbps-query -r root -p pkgver A) ++ atf_check_equal $out A-1.2_1 ++ out=$(xbps-query -r root -p pkgver B) ++ atf_check_equal $out B-1.1_1 ++ out=$(xbps-query -r root -p pkgver C) ++ atf_check_equal $out C-1.3_1 ++ out=$(xbps-query -r root -p pkgver D) ++ atf_check_equal $out D-1.4_1 ++ ++ lnk=$(readlink -f root/usr/bin/file) ++ rv=1 ++ if [ "$lnk" = "$PWD/root/usr/bin/fileB" ]; then ++ rv=0 ++ fi ++ echo "lnk: $lnk" ++ atf_check_equal $rv 0 ++} ++ + atf_init_test_cases() { + atf_add_test_case register_one + atf_add_test_case register_one_dangling +@@ -692,4 +784,5 @@ atf_init_test_cases() { + atf_add_test_case useless_switch + atf_add_test_case remove_current_provider + atf_add_test_case respect_current_provider ++ atf_add_test_case prune_leftover_groups + } diff --git a/srcpkgs/xbps/template b/srcpkgs/xbps/template index 65e456f2ba..755d4af6ac 100644 --- a/srcpkgs/xbps/template +++ b/srcpkgs/xbps/template @@ -1,7 +1,7 @@ # Template file for 'xbps' pkgname=xbps version=0.57.1 -revision=2 +revision=3 bootstrap=yes build_style=configure short_desc="XBPS package system utilities"