recursedeps: include cyclic dependencies

Cyclic dependencies (A depends on B, B depends on A) were not selected
because neither package could be removed individually, so
can_remove_package would always return false for both.  By preselecting
all dependencies then filtering back out any dependencies still required
by any packages that will not be uninstalled, groups of unneeded cyclic
dependencies can be found.

Fixes FS#41031

Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com>
Signed-off-by: Allan McRae <allan@archlinux.org>
This commit is contained in:
Andrew Gregory 2016-05-23 08:27:29 -04:00 committed by Allan McRae
parent 7a9d8b7001
commit 6ac2ee21b3
3 changed files with 94 additions and 52 deletions

View File

@ -551,44 +551,29 @@ error:
return NULL; return NULL;
} }
/* These parameters are messy. We check if this package, given a list of /** Move package dependencies from one list to another
* targets and a db is safe to remove. We do NOT remove it if it is in the * @param from list to scan for dependencies
* target list, or if the package was explicitly installed and * @param to list to add dependencies to
* include_explicit == 0 */ * @param pkg package whose dependencies are moved
static int can_remove_package(alpm_db_t *db, alpm_pkg_t *pkg, * @param explicit if 0, explicitly installed packages are not moved
alpm_list_t *targets, int include_explicit) */
static void _alpm_select_depends(alpm_list_t **from, alpm_list_t **to,
alpm_pkg_t *pkg, int explicit)
{ {
alpm_list_t *i; alpm_list_t *i, *next;
if(!alpm_pkg_get_depends(pkg)) {
if(alpm_pkg_find(targets, pkg->name)) { return;
return 0;
} }
for(i = *from; i; i = next) {
if(!include_explicit) { alpm_pkg_t *deppkg = i->data;
/* see if it was explicitly installed */ next = i->next;
if(alpm_pkg_get_reason(pkg) == ALPM_PKG_REASON_EXPLICIT) { if((explicit || alpm_pkg_get_reason(deppkg) != ALPM_PKG_REASON_EXPLICIT)
_alpm_log(db->handle, ALPM_LOG_DEBUG, && _alpm_pkg_depends_on(pkg, deppkg)) {
"excluding %s -- explicitly installed\n", pkg->name); *to = alpm_list_add(*to, deppkg);
return 0; *from = alpm_list_remove_item(*from, i);
free(i);
} }
} }
/* TODO: checkdeps could be used here, it handles multiple providers
* better, but that also makes it slower.
* Also this would require to first add the package to the targets list,
* then call checkdeps with it, then remove the package from the targets list
* if checkdeps detected it would break something */
/* see if other packages need it */
for(i = _alpm_db_get_pkgcache(db); i; i = i->next) {
alpm_pkg_t *lpkg = i->data;
if(_alpm_pkg_depends_on(lpkg, pkg) && !alpm_pkg_find(targets, lpkg->name)) {
return 0;
}
}
/* it's ok to remove */
return 1;
} }
/** /**
@ -604,31 +589,46 @@ static int can_remove_package(alpm_db_t *db, alpm_pkg_t *pkg,
*/ */
int _alpm_recursedeps(alpm_db_t *db, alpm_list_t **targs, int include_explicit) int _alpm_recursedeps(alpm_db_t *db, alpm_list_t **targs, int include_explicit)
{ {
alpm_list_t *i, *j; alpm_list_t *i, *keep, *rem = NULL;
if(db == NULL || targs == NULL) { if(db == NULL || targs == NULL) {
return -1; return -1;
} }
keep = alpm_list_copy(_alpm_db_get_pkgcache(db));
for(i = *targs; i; i = i->next) { for(i = *targs; i; i = i->next) {
alpm_pkg_t *pkg = i->data; keep = alpm_list_remove(keep, i->data, _alpm_pkg_cmp, NULL);
for(j = _alpm_db_get_pkgcache(db); j; j = j->next) {
alpm_pkg_t *deppkg = j->data;
if(_alpm_pkg_depends_on(pkg, deppkg)
&& can_remove_package(db, deppkg, *targs, include_explicit)) {
alpm_pkg_t *copy = NULL;
_alpm_log(db->handle, ALPM_LOG_DEBUG, "adding '%s' to the targets\n",
deppkg->name);
/* add it to the target list */
if(_alpm_pkg_dup(deppkg, &copy)) {
/* we return memory on "non-fatal" error in _alpm_pkg_dup */
_alpm_pkg_free(copy);
return -1;
}
*targs = alpm_list_add(*targs, copy);
}
}
} }
/* recursively select all dependencies for removal */
for(i = *targs; i; i = i->next) {
_alpm_select_depends(&keep, &rem, i->data, include_explicit);
}
for(i = rem; i; i = i->next) {
_alpm_select_depends(&keep, &rem, i->data, include_explicit);
}
/* recursively select any still needed packages to keep */
for(i = keep; i && rem; i = i->next) {
_alpm_select_depends(&rem, &keep, i->data, 1);
}
alpm_list_free(keep);
/* copy selected packages into the target list */
for(i = rem; i; i = i->next) {
alpm_pkg_t *pkg = i->data, *copy = NULL;
_alpm_log(db->handle, ALPM_LOG_DEBUG,
"adding '%s' to the targets\n", pkg->name);
if(_alpm_pkg_dup(pkg, &copy)) {
/* we return memory on "non-fatal" error in _alpm_pkg_dup */
_alpm_pkg_free(copy);
alpm_list_free(rem);
return -1;
}
*targs = alpm_list_add(*targs, copy);
}
alpm_list_free(rem);
return 0; return 0;
} }

View File

@ -109,6 +109,7 @@ TESTS += test/pacman/tests/querycheck002.py
TESTS += test/pacman/tests/querycheck_fast_file_type.py TESTS += test/pacman/tests/querycheck_fast_file_type.py
TESTS += test/pacman/tests/reason001.py TESTS += test/pacman/tests/reason001.py
TESTS += test/pacman/tests/remove-assumeinstalled.py TESTS += test/pacman/tests/remove-assumeinstalled.py
TESTS += test/pacman/tests/remove-recursive-cycle.py
TESTS += test/pacman/tests/remove001.py TESTS += test/pacman/tests/remove001.py
TESTS += test/pacman/tests/remove002.py TESTS += test/pacman/tests/remove002.py
TESTS += test/pacman/tests/remove010.py TESTS += test/pacman/tests/remove010.py

View File

@ -0,0 +1,41 @@
self.description = "Recursively remove a package with cyclical dependencies"
lpkg1 = pmpkg('pkg1')
self.addpkg2db('local', lpkg1)
lpkg1.depends = [ 'dep1' ]
lpkg2 = pmpkg('pkg2')
self.addpkg2db('local', lpkg2)
lpkg2.depends = [ 'dep3' ]
# cyclic dependency 1
ldep1 = pmpkg('dep1')
self.addpkg2db('local', ldep1)
ldep1.depends = [ 'dep2', 'dep3', 'dep4' ]
ldep1.reason = 1
# cyclic dependency 2
ldep2 = pmpkg('dep2')
self.addpkg2db('local', ldep2)
ldep2.depends = [ 'dep1' ]
ldep2.reason = 1
# dependency required by another package
ldep3 = pmpkg('dep3')
self.addpkg2db('local', ldep3)
ldep3.reason = 1
# explicitly installed dependency
ldep4 = pmpkg('dep4')
self.addpkg2db('local', ldep4)
ldep4.reason = 0
self.args = "-Rs pkg1"
self.addrule("PACMAN_RETCODE=0")
self.addrule("PKG_EXIST=pkg2")
self.addrule("PKG_EXIST=dep3")
self.addrule("PKG_EXIST=dep4")
self.addrule("!PKG_EXIST=pkg1")
self.addrule("!PKG_EXIST=dep1")
self.addrule("!PKG_EXIST=dep2")