Fix dependencies calculation and virtual resolving.
This commit is contained in:
parent
06dad0da17
commit
da435cfeb9
@ -32,6 +32,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
/*}}}*/
|
||||
using namespace std;
|
||||
|
||||
@ -577,66 +578,82 @@ bool pkgMinimizeUpgrade(pkgDepCache &Cache)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool all_revdeps_may_be_removed(
|
||||
const pkgDepCache &Cache,
|
||||
static bool find_all_required_dependencies(
|
||||
pkgDepCache &Cache,
|
||||
const pkgCache::PkgIterator &pkg_iter,
|
||||
const std::multimap<const char*, pkgCache::PkgIterator> &requires_map,
|
||||
const std::multimap<const char*, pkgCache::PkgIterator> &provides_map,
|
||||
std::map<const char*, bool> &states_cache_map)
|
||||
std::set<const char*> &kept_packages,
|
||||
std::map<const char*, std::set<pkgCache::PkgIterator> > &unresolved_virtual_dependencies,
|
||||
const std::map<const char*, std::set<pkgCache::PkgIterator> > &virtual_provides_map)
|
||||
{
|
||||
// Don't try to recursively traverse dependency loops
|
||||
// don't try doing infinite loop on cyclic dependencies
|
||||
if (kept_packages.find(pkg_iter.Name()) != kept_packages.end())
|
||||
{
|
||||
auto iter = states_cache_map.find(pkg_iter.Name());
|
||||
if (iter != states_cache_map.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mark_auto = Cache.getMarkAuto(pkg_iter);
|
||||
kept_packages.insert(pkg_iter.Name());
|
||||
|
||||
// set auto mark to current state (this may be used to get out of infinite dependencies loop
|
||||
states_cache_map[pkg_iter.Name()] = mark_auto;
|
||||
auto version_iter = pkg_iter.CurrentVer();
|
||||
|
||||
// recursively check all dependencies, including possible virtual ones
|
||||
if (mark_auto)
|
||||
for (auto deps_iter = version_iter.DependsList(); not deps_iter.end(); ++deps_iter)
|
||||
{
|
||||
for (pkgCache::DepIterator dep_iter = pkg_iter.RevDependsList(); mark_auto && (not dep_iter.end()); ++dep_iter)
|
||||
// only process specific dependencies types
|
||||
switch (deps_iter->Type)
|
||||
{
|
||||
// Skip packages not installed
|
||||
if (dep_iter.ParentPkg()->CurrentState == pkgCache::State::Installed)
|
||||
case pkgCache::Dep::Depends:
|
||||
case pkgCache::Dep::PreDepends:
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dependency_pkg = Cache.FindPkg(deps_iter.TargetPkg().Name());
|
||||
if (dependency_pkg.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dependency_pkg->CurrentState == pkgCache::State::Installed)
|
||||
{
|
||||
if (!find_all_required_dependencies(Cache, dependency_pkg, kept_packages, unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
mark_auto &= all_revdeps_may_be_removed(Cache, dep_iter.ParentPkg(), requires_map, provides_map, states_cache_map);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mark_auto)
|
||||
else
|
||||
{
|
||||
// format: virtual dependency = depending package
|
||||
//requires_map.insert(std::make_pair(deps_iter.TargetPkg().Name(), pkg_iter));
|
||||
// format: containing package = virtual dependency
|
||||
//provides_map.insert(std::make_pair(provs_iter.OwnerPkg().Name(), pkg_iter));
|
||||
|
||||
// loop over all provides of this package
|
||||
for (auto provides_map_iter = provides_map.find(pkg_iter.Name());
|
||||
mark_auto && (provides_map_iter != provides_map.end()) && (strcmp(provides_map_iter->first, pkg_iter.Name()) == 0);
|
||||
++provides_map_iter)
|
||||
// probably a virtual package, check it
|
||||
auto virtual_provides_map_iter = virtual_provides_map.find(dependency_pkg.Name());
|
||||
if (virtual_provides_map_iter == virtual_provides_map.end())
|
||||
{
|
||||
// loop over all dependent packages and check them
|
||||
for (auto requires_map_iter = requires_map.find(provides_map_iter->second.Name());
|
||||
mark_auto && (requires_map_iter != requires_map.end()) && (strcmp(requires_map_iter->first, provides_map_iter->second.Name()) == 0);
|
||||
++requires_map_iter)
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (virtual_provides_map_iter->second.size())
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (!find_all_required_dependencies(Cache, *(virtual_provides_map_iter->second.begin()), kept_packages, unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
mark_auto &= all_revdeps_may_be_removed(Cache, requires_map_iter->second, requires_map, provides_map, states_cache_map);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
for (auto virtuals_iter = virtual_provides_map_iter->second.begin(); virtuals_iter != virtual_provides_map_iter->second.end(); ++virtuals_iter)
|
||||
{
|
||||
unresolved_virtual_dependencies[dependency_pkg.Name()].insert(*virtuals_iter);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// update mark to the final value
|
||||
states_cache_map[pkg_iter.Name()] = mark_auto;
|
||||
}
|
||||
|
||||
return mark_auto;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pkgAutoremove(pkgDepCache &Cache)
|
||||
@ -648,62 +665,180 @@ bool pkgAutoremove(pkgDepCache &Cache)
|
||||
|
||||
pkgProblemResolver Fix(&Cache);
|
||||
|
||||
// First collect requires of installed packages into a map, including virtual requires
|
||||
// And also collect virtual provides map
|
||||
std::multimap<const char*, pkgCache::PkgIterator> requires_map;
|
||||
std::multimap<const char*, pkgCache::PkgIterator> provides_map;
|
||||
|
||||
// if package is still needed, put it into this set
|
||||
std::set<const char*> kept_packages;
|
||||
|
||||
// save unresolved virtual dependencies here to try resolving it
|
||||
std::map<const char*, std::set<pkgCache::PkgIterator> > unresolved_virtual_dependencies;
|
||||
|
||||
std::map<const char*, std::set<pkgCache::PkgIterator> > virtual_provides_map;
|
||||
|
||||
// First gather all virtual provides
|
||||
for (pkgCache::PkgIterator pkg_iter = Cache.PkgBegin(); not pkg_iter.end(); ++pkg_iter)
|
||||
{
|
||||
// Skip packages not installed
|
||||
if (pkg_iter->CurrentState == pkgCache::State::Installed)
|
||||
{
|
||||
auto version_iter = pkg_iter.CurrentVer();
|
||||
|
||||
for (auto deps_iter = version_iter.DependsList(); not deps_iter.end(); ++deps_iter)
|
||||
{
|
||||
// format: virtual dependency = depending package
|
||||
requires_map.insert(std::make_pair(deps_iter.TargetPkg().Name(), pkg_iter));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto provs_iter = pkg_iter.ProvidesList(); not provs_iter.end(); ++provs_iter)
|
||||
{
|
||||
if (provs_iter.OwnerPkg()->CurrentState == pkgCache::State::Installed)
|
||||
{
|
||||
// format: containing package = virtual dependency
|
||||
provides_map.insert(std::make_pair(provs_iter.OwnerPkg().Name(), pkg_iter));
|
||||
// format: virtual dependency = providing package
|
||||
virtual_provides_map[pkg_iter.Name()].insert(provs_iter.OwnerPkg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check every installed package
|
||||
for (pkgCache::PkgIterator pkg_iter = Cache.PkgBegin(); not pkg_iter.end(); ++pkg_iter)
|
||||
{
|
||||
// Skip packages not installed, and automatically installed ones too
|
||||
if ((pkg_iter->CurrentState == pkgCache::State::Installed) && (!Cache.getMarkAuto(pkg_iter)))
|
||||
{
|
||||
if (!find_all_required_dependencies(Cache, pkg_iter, kept_packages, unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
return _error->Error(_("Dependencies check failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!unresolved_virtual_dependencies.empty())
|
||||
{
|
||||
std::map<const char*, std::set<pkgCache::PkgIterator> > new_unresolved_virtual_dependencies;
|
||||
|
||||
// process every unresolved virtual dependency
|
||||
for (auto virtual_iter = unresolved_virtual_dependencies.begin();
|
||||
virtual_iter != unresolved_virtual_dependencies.end();
|
||||
++virtual_iter)
|
||||
{
|
||||
// first check if any of providing packages already installed
|
||||
{
|
||||
auto provide_iter = virtual_iter->second.begin();
|
||||
|
||||
for ( ;
|
||||
provide_iter != virtual_iter->second.end();
|
||||
++provide_iter)
|
||||
{
|
||||
if (kept_packages.find(provide_iter->Name()) != kept_packages.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (provide_iter != virtual_iter->second.end())
|
||||
{
|
||||
// one or more dependencies are already installed, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// now remove dependencies which have unsatisfied dependencies themselves
|
||||
{
|
||||
auto provide_iter = virtual_iter->second.begin();
|
||||
|
||||
while (provide_iter != virtual_iter->second.end())
|
||||
{
|
||||
std::set<const char*> kept_packages_copy = kept_packages;
|
||||
std::map<const char*, std::set<pkgCache::PkgIterator> > temp_unresolved_virtual_dependencies;
|
||||
|
||||
if (!find_all_required_dependencies(Cache, *provide_iter, kept_packages_copy, temp_unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
provide_iter = virtual_iter->second.erase(provide_iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
++provide_iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no package may be kept due to unsolved dependencies, fail
|
||||
if (virtual_iter->second.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finally, choose one of remaining dependencies.
|
||||
// How about package with highest version?
|
||||
// If everything else fails, let's just pick first one (alphabetically)
|
||||
// TODO: consider implementing smarter choosing algorithm, some options, or even ask user to choose one
|
||||
{
|
||||
auto provide_iter = virtual_iter->second.begin();
|
||||
|
||||
auto max_version = provide_iter->CurrentVer();
|
||||
size_t max_version_count = 1;
|
||||
++provide_iter;
|
||||
|
||||
while (provide_iter != virtual_iter->second.end())
|
||||
{
|
||||
auto compare_version_result = max_version.CompareVer(provide_iter->CurrentVer());
|
||||
|
||||
if (compare_version_result < 0)
|
||||
{
|
||||
max_version = provide_iter->CurrentVer();
|
||||
max_version_count = 1;
|
||||
}
|
||||
else if (compare_version_result == 0)
|
||||
{
|
||||
++max_version_count;
|
||||
}
|
||||
|
||||
++provide_iter;
|
||||
}
|
||||
|
||||
if (max_version_count == 1)
|
||||
{
|
||||
provide_iter = virtual_iter->second.begin();
|
||||
|
||||
for ( ;
|
||||
provide_iter != virtual_iter->second.end();
|
||||
++provide_iter)
|
||||
{
|
||||
if (provide_iter->CurrentVer().CompareVer(max_version) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (provide_iter != virtual_iter->second.end())
|
||||
{
|
||||
if (!find_all_required_dependencies(Cache, *provide_iter, kept_packages, new_unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
// For some reason failed when it should not
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!find_all_required_dependencies(Cache, *(virtual_iter->second.begin()), kept_packages, new_unresolved_virtual_dependencies, virtual_provides_map))
|
||||
{
|
||||
// For some reason failed when it should not
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unresolved_virtual_dependencies = new_unresolved_virtual_dependencies;
|
||||
}
|
||||
|
||||
// Finally, go through all packages once more and mark them
|
||||
for (pkgCache::PkgIterator pkg_iter = Cache.PkgBegin(); not pkg_iter.end(); ++pkg_iter)
|
||||
{
|
||||
// Skip packages not installed
|
||||
if (pkg_iter->CurrentState == pkgCache::State::Installed)
|
||||
{
|
||||
// Keep a cache of dependencies for current package.
|
||||
// Need to create clean cache for each package,
|
||||
// otherwise when processing cyclic dependencies
|
||||
// it may report invalid result and remove actually still needed package
|
||||
std::map<const char*, bool> states_cache_map;
|
||||
|
||||
// TODO: process situation when two or more auto packages provide same virtual dependency, and only one may be kept while others removed
|
||||
if (all_revdeps_may_be_removed(Cache, pkg_iter, requires_map, provides_map, states_cache_map))
|
||||
{
|
||||
Cache.MarkDelete(pkg_iter, _config->FindB("APT::Get::Purge",false));
|
||||
}
|
||||
else
|
||||
if (kept_packages.find(pkg_iter.Name()) != kept_packages.end())
|
||||
{
|
||||
// Package is needed, protect it to prohibit automatic removal
|
||||
Cache.MarkKeep(pkg_iter);
|
||||
Fix.Protect(pkg_iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cache.MarkDelete(pkg_iter, _config->FindB("APT::Get::Purge",false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a package if it's still needed (for example, via virtual dependency)
|
||||
return Fix.Resolve(false);
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,6 @@ class pkgCache::PkgIterator
|
||||
// Comparison
|
||||
inline bool operator ==(const PkgIterator &B) const {return Pkg == B.Pkg;};
|
||||
inline bool operator !=(const PkgIterator &B) const {return Pkg != B.Pkg;};
|
||||
bool operator <(const PkgIterator &B) const;
|
||||
|
||||
// Accessors
|
||||
inline Package *operator ->() {return Pkg;};
|
||||
|
@ -277,26 +277,6 @@ void pkgCache::PrvIterator::_dummy() {}
|
||||
/*}}}*/
|
||||
|
||||
|
||||
bool pkgCache::PkgIterator::operator <(const PkgIterator &B) const
|
||||
{
|
||||
if (*this == B)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (this->Pkg == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (B.Pkg == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (strcmp(this->Name(), B.Name()) < 0);
|
||||
}
|
||||
}
|
||||
|
||||
// PkgIterator::operator ++ - Postfix incr /*{{{*/
|
||||
// ---------------------------------------------------------------------
|
||||
/* This will advance to the next logical package in the hash table. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user