From 102017ecaa01a733a0860a62edae60cb0ab0220e Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 25 May 2024 09:45:39 +0200 Subject: solver3: Group work items Our backtracking is chronological, so we will first try alternative choices or skip optional items in later groups. So installing manually installed packages before automatically installed ones allows manually installed packages to remove automatically installed packages easily. If we did automatic packages first, we'd keep back upgrades for manual packages or change choices for their dependencies, or would have to backtrack harder to get back to the right decision level. That's silly. Ordering automatically installed packages last also allows us to calculate autoremovable packages. Since we will have installed all dependencies from manually installed packages by the point where we get to automatically installed packages, everything that will be installed in those Auto groups is inherently garbage. --- apt-pkg/solver3.h | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) (limited to 'apt-pkg/solver3.h') diff --git a/apt-pkg/solver3.h b/apt-pkg/solver3.h index cf2fb2ecc..c3f191b18 100644 --- a/apt-pkg/solver3.h +++ b/apt-pkg/solver3.h @@ -36,6 +36,33 @@ class Solver struct State; struct Work; + // \brief Groups of works, these are ordered. + // + // Later items will be skipped if they are optional, or we will when backtracking, + // try a different choice for them. + enum class Group : uint8_t + { + HoldOrDelete, + NewUnsatRecommends, + Satisfy, + + // My intuition tells me that we should try to schedule upgrades first, then + // any non-obsolete installed packages, and only finally obsolete ones, such + // that newer packages guide resolution of dependencies for older ones, they + // may have more stringent dependencies, like a (>> 2) whereas an obsolete + // package may have a (>> 1), for example. + UpgradeManual, + InstallManual, + ObsoleteManual, + + // Automatically installed packages must come last in the group, this allows + // us to see if they were installed as a dependency of a manually installed package, + // allowing a simple implementation of an autoremoval code. + UpgradeAuto, + KeepAuto, + ObsoleteAuto + }; + // \brief Type to record depth at. This may very well be a 16-bit // unsigned integer, then change Solver::State::Decision to be a // uint16_t class enum as well to get a more compact space. @@ -131,13 +158,13 @@ class Solver Solver(pkgCache &Cache, pkgDepCache::Policy &Policy); // \brief Mark the package for install. This is annoying as it incurs a decision - bool Install(pkgCache::PkgIterator Pkg, Reason reason); + bool Install(pkgCache::PkgIterator Pkg, Reason reason, Group group); // \brief Install a version. - bool Install(pkgCache::VerIterator Ver, Reason reason); + bool Install(pkgCache::VerIterator Ver, Reason reason, Group group); // \brief Do not install this package - bool Reject(pkgCache::PkgIterator Pkg, Reason reason); + bool Reject(pkgCache::PkgIterator Pkg, Reason reason, Group group); // \brief Do not install this version. - bool Reject(pkgCache::VerIterator Ver, Reason reason); + bool Reject(pkgCache::VerIterator Ver, Reason reason, Group group); // \brief Apply the selections from the dep cache to the solver bool FromDepCache(pkgDepCache &depcache); @@ -203,7 +230,8 @@ struct APT::Solver::Work Reason reason; // \brief The depth at which the item has been added depth_type depth; - + // \brief The group we are in + Group group; // \brief Possible solutions to this task, ordered in order of preference. std::vector solutions{}; @@ -228,7 +256,7 @@ struct APT::Solver::Work // \brief Dump the work item to std::cerr void Dump(pkgCache &cache); - inline Work(Reason reason, depth_type depth, bool optional = false, bool upgrade = false) : reason(reason), depth(depth), size(0), optional(optional), upgrade(upgrade), dirty(false) {} + inline Work(Reason reason, depth_type depth, Group group, bool optional = false, bool upgrade = false) : reason(reason), depth(depth), group(group), size(0), optional(optional), upgrade(upgrade), dirty(false) {} }; // \brief This essentially describes the install state in RFC2119 terms. -- cgit v1.2.3-70-g09d2 From 6708203296911800caf94bb94ce204d3a39af0bd Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 6 Jun 2024 11:19:04 +0200 Subject: solver3: Order obsolete choices last This has two aspects: 1. For a dependency A | B | C we order the obsolete packages last, that is, if A is obsolete, this gets reordered to B | C | A, such that we try to pick non-obsolete packages first to ease upgrade calculation. 2. When comparing two dependencies, we order dependencies into three groups: First we satisfy dependencies mentioning only non-installed (NEW) packages, then we satisfy "normal" dependencies, and finally we satisfy any dependencies mentioning obsolete packages. This means for example if you have obsolete libfoo1 and a new libfoo1t64, that we will see Depends: libfoo1t64 before any Depends: libfoo1 (which may expand to libfoo1 | libfoo1t64), so we effectively will have selected "replacement" packages this way already before getting to older packages where we would have to choose between the obsolete package and its replacement. --- apt-pkg/solver3.cc | 49 ++++++++++++++++++++---- apt-pkg/solver3.h | 12 ++++++ test/integration/test-resolver-provider-exchange | 1 + 3 files changed, 54 insertions(+), 8 deletions(-) (limited to 'apt-pkg/solver3.h') diff --git a/apt-pkg/solver3.cc b/apt-pkg/solver3.cc index e86353e43..dc70adbde 100644 --- a/apt-pkg/solver3.cc +++ b/apt-pkg/solver3.cc @@ -26,11 +26,12 @@ #include // FIXME: Helpers stolen from DepCache, please give them back. -struct CompareProviders3 /*{{{*/ +struct APT::Solver::CompareProviders3 /*{{{*/ { pkgCache &Cache; pkgDepCache::Policy &Policy; pkgCache::PkgIterator const Pkg; + APT::Solver &Solver; bool upgrade{_config->FindB("APT::Solver::Upgrade", false)}; bool operator()(pkgCache::Version *AV, pkgCache::Version *BV) @@ -60,6 +61,11 @@ struct CompareProviders3 /*{{{*/ return _system->VS->CmpVersion(AV.VerStr(), BV.VerStr()) > 0; } + // Try obsolete choices only after exhausting non-obsolete choices such that we install + // packages replacing them and don't keep back upgrades depending on the replacement to + // keep the obsolete package installed. + if (auto obsoleteAV = Solver.Obsolete(AV), obsoleteBV = Solver.Obsolete(BV); obsoleteAV != obsoleteBV) + return obsoleteBV; // Prefer MA:same packages if other architectures for it are installed if ((AV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same || (BV->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same) @@ -158,8 +164,9 @@ class DefaultRootSetFunc2 : public pkgDepCache::DefaultRootSetFunc APT::Solver::Solver(pkgCache &cache, pkgDepCache::Policy &policy) : cache(cache), policy(policy), - pkgStates{cache.Head().PackageCount}, - verStates{cache.Head().VersionCount} + pkgStates(cache.Head().PackageCount), + verStates(cache.Head().VersionCount), + verObsolete(cache.Head().VersionCount) { static_assert(sizeof(APT::Solver::State) == 3 * sizeof(int)); static_assert(sizeof(APT::Solver::State) == 3 * sizeof(int)); @@ -239,6 +246,26 @@ std::string APT::Solver::WhyStr(Reason reason) return outstr; } +bool APT::Solver::Obsolete(pkgCache::VerIterator ver) +{ + if (verObsolete[ver->ID] != 0) + return verObsolete[ver->ID] == 2; + for (auto bin = ver.Cache()->FindGrp(ver.SourcePkgName()).VersionsInSource(); not bin.end(); bin = bin.NextInSource()) + if (bin != ver && bin.ParentPkg()->Arch == ver.ParentPkg()->Arch && bin->ParentPkg != ver->ParentPkg && policy.GetCandidateVer(bin.ParentPkg()) == bin && _system->VS->CmpVersion(bin.SourceVerStr(), ver.SourceVerStr()) > 0) + { + verObsolete[ver->ID] = 2; + return true; + } + for (auto file = ver.FileList(); !file.end(); file++) + if ((file.File()->Flags & pkgCache::Flag::NotSource) == 0) + { + verObsolete[ver->ID] = 1; + return false; + } + verObsolete[ver->ID] = 2; + return true; +} + bool APT::Solver::Install(pkgCache::PkgIterator Pkg, Reason reason, Group group) { if ((*this)[Pkg].decision == Decision::MUST) @@ -272,7 +299,7 @@ bool APT::Solver::Install(pkgCache::PkgIterator Pkg, Reason reason, Group group) for (auto ver = Pkg.VersionList(); not ver.end(); ver++) if (IsAllowedVersion(ver)) workItem.solutions.push_back(ver); - std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, Pkg}); + std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, Pkg, *this}); assert(workItem.solutions.size() > 0); if (workItem.solutions.size() > 1 || workItem.optional) @@ -490,7 +517,7 @@ bool APT::Solver::EnqueueOrGroup(pkgCache::DepIterator start, pkgCache::DepItera // FIXME: This is not really true, though, we should fix the CompareProviders to ignore the // installed state if (fixPolicy) - std::stable_sort(workItem.solutions.begin() + begin, workItem.solutions.end(), CompareProviders3{cache, policy, TgtPkg}); + std::stable_sort(workItem.solutions.begin() + begin, workItem.solutions.end(), CompareProviders3{cache, policy, TgtPkg, *this}); if (start == end) break; @@ -498,8 +525,14 @@ bool APT::Solver::EnqueueOrGroup(pkgCache::DepIterator start, pkgCache::DepItera } while (1); if (not fixPolicy) - std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, TgtPkg}); - + std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, TgtPkg, *this}); + + if (std::all_of(workItem.solutions.begin(), workItem.solutions.end(), [this](auto V) -> auto + { return pkgCache::VerIterator(cache, V).ParentPkg()->CurrentVer == 0; })) + workItem.group = Group::SatisfyNew; + if (std::any_of(workItem.solutions.begin(), workItem.solutions.end(), [this](auto V) -> auto + { return Obsolete(pkgCache::VerIterator(cache, V)); })) + workItem.group = Group::SatisfyObsolete; // Try to perserve satisfied Recommends. FIXME: We should check if the Recommends was there in the installed version? if (workItem.optional && start.ParentPkg()->CurrentVer) { @@ -936,7 +969,7 @@ bool APT::Solver::FromDepCache(pkgDepCache &depcache) for (auto V = P.VersionList(); not V.end(); ++V) if (IsAllowedVersion(V)) w.solutions.push_back(V); - std::stable_sort(w.solutions.begin(), w.solutions.end(), CompareProviders3{cache, policy, P}); + std::stable_sort(w.solutions.begin(), w.solutions.end(), CompareProviders3{cache, policy, P, *this}); AddWork(std::move(w)); } } diff --git a/apt-pkg/solver3.h b/apt-pkg/solver3.h index c3f191b18..9a9d67a02 100644 --- a/apt-pkg/solver3.h +++ b/apt-pkg/solver3.h @@ -32,6 +32,7 @@ class Solver enum class Decision : uint16_t; enum class Hint : uint16_t; struct Reason; + struct CompareProviders3; template struct State; struct Work; @@ -44,7 +45,15 @@ class Solver { HoldOrDelete, NewUnsatRecommends, + + // Satisfying dependencies on entirely new packages first is a good idea because + // it may contain replacement packages like libfoo1t64 whereas we later will see + // Depends: libfoo1 where libfoo1t64 Provides libfoo1 and we'd have to choose. + SatisfyNew, Satisfy, + // On a similar note as for SatisfyNew, if the dependency contains obsolete packages + // try it last. + SatisfyObsolete, // My intuition tells me that we should try to schedule upgrades first, then // any non-obsolete installed packages, and only finally obsolete ones, such @@ -95,6 +104,9 @@ class Solver return verStates[V->ID]; } + std::vector verObsolete; + bool Obsolete(pkgCache::VerIterator ver); + // \brief Heap of the remaining work. // // We are using an std::vector with std::make_heap(), std::push_heap(), diff --git a/test/integration/test-resolver-provider-exchange b/test/integration/test-resolver-provider-exchange index 45d936978..2874b88e4 100755 --- a/test/integration/test-resolver-provider-exchange +++ b/test/integration/test-resolver-provider-exchange @@ -8,6 +8,7 @@ allowremovemanual configarchitecture 'amd64' insertinstalledpackage 'fuse' 'all' '2' +insertpackage 'unstable' 'fuse' 'all' '2' insertpackage 'unstable' 'fuse3' 'all' '3' 'Conflicts: fuse Provides: fuse' -- cgit v1.2.3-70-g09d2