Fix dependencies calculation and virtual resolving.

This commit is contained in:
Aleksei Nikiforov 2017-10-31 14:32:37 +03:00
parent 06dad0da17
commit da435cfeb9
3 changed files with 207 additions and 93 deletions

View File

@ -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);
}

View File

@ -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;};

View File

@ -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. */