diff options
Diffstat (limited to 'apt-pkg/solver3.cc')
| -rw-r--r-- | apt-pkg/solver3.cc | 468 |
1 files changed, 221 insertions, 247 deletions
diff --git a/apt-pkg/solver3.cc b/apt-pkg/solver3.cc index 73f7fd0c4..ed9beb828 100644 --- a/apt-pkg/solver3.cc +++ b/apt-pkg/solver3.cc @@ -43,14 +43,29 @@ struct APT::Solver::CompareProviders3 /*{{{*/ 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) + pkgCache::VerIterator bestVersion(pkgCache::PkgIterator pkg) { - return (*this)(pkgCache::VerIterator(Cache, AV), pkgCache::VerIterator(Cache, BV)); + pkgCache::VerIterator res = pkg.VersionList(); + for (auto v = res; not v.end(); ++v) + res = std::max(res, v, *this); + return res; + } + bool operator()(Var a, Var b) + { + pkgCache::VerIterator va = a.Ver(Cache); + pkgCache::VerIterator vb = b.Ver(Cache); + if (auto pa = a.Pkg(Cache)) + va = bestVersion(pa); + if (auto pb = b.Pkg(Cache)) + vb = bestVersion(pb); + + assert(not va.end() && not vb.end()); + return (*this)(va, vb); } bool operator()(pkgCache::VerIterator const &AV, pkgCache::VerIterator const &BV) { + assert(not AV.end() && not BV.end()); pkgCache::PkgIterator const A = AV.ParentPkg(); pkgCache::PkgIterator const B = BV.ParentPkg(); // Compare versions for the same package. FIXME: Move this to the real implementation @@ -60,9 +75,9 @@ struct APT::Solver::CompareProviders3 /*{{{*/ return false; // Candidate wins in upgrade scenario - if (upgrade) + if (Solver.IsUpgrade) { - auto Cand = Policy.GetCandidateVer(A); + auto Cand = Solver.GetCandidateVer(A); if (AV == Cand || BV == Cand) return (AV == Cand); } @@ -72,7 +87,7 @@ struct APT::Solver::CompareProviders3 /*{{{*/ return (A.CurrentVer() == AV); // Rest is ordered list, first by priority - if (auto pinA = Policy.GetPriority(AV), pinB = Policy.GetPriority(BV); pinA != pinB) + if (auto pinA = Solver.GetPriority(AV), pinB = Solver.GetPriority(BV); pinA != pinB) return pinA > pinB; // Then by version @@ -81,7 +96,7 @@ struct APT::Solver::CompareProviders3 /*{{{*/ // 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 (upgrade) + if (Solver.IsUpgrade) if (auto obsoleteA = Solver.Obsolete(A), obsoleteB = Solver.Obsolete(B); obsoleteA != obsoleteB) return obsoleteB; // Prefer MA:same packages if other architectures for it are installed @@ -182,82 +197,75 @@ 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), - pkgObsolete(cache.Head().PackageCount) + rootState(new State), + pkgStates(cache), + verStates(cache), + pkgObsolete(cache), + priorities(cache), + candidates(cache) { - static_assert(sizeof(APT::Solver::State) == 3 * sizeof(int)); + // Ensure trivially + static_assert(std::is_trivially_destructible_v<Work>); + static_assert(std::is_trivially_destructible_v<Solved>); static_assert(sizeof(APT::Solver::Var) == sizeof(map_pointer<pkgCache::Package>)); static_assert(sizeof(APT::Solver::Var) == sizeof(map_pointer<pkgCache::Version>)); + // Root state is "true". + rootState->decision = Decision::MUST; } // This function determines if a work item is less important than another. bool APT::Solver::Work::operator<(APT::Solver::Work const &b) const { - if ((not optional && size < 2) != (not b.optional && b.size < 2)) - return not b.optional && b.size < 2; - if (group != b.group) - return group > b.group; - if (optional && b.optional) - { - if ((size < 2) != (b.size < 2)) - return b.size < 2; - if (reason.empty() != b.reason.empty()) - return reason.empty(); - } - // An optional item is less important than a required one. - if (optional != b.optional) - return optional; - // We enqueue common dependencies at the package level to avoid choosing versions, so let's solve package items first, - // this improves the implication graph as it now tells you that common dependencies were installed by the package. - if (reason.Pkg() != b.reason.Pkg()) - return reason.Pkg() == 0; - + if ((not clause->optional && size < 2) != (not b.clause->optional && b.size < 2)) + return not b.clause->optional && b.size < 2; + if (clause->optional != b.clause->optional) + return clause->optional; + if (clause->group != b.clause->group) + return clause->group > b.clause->group; + if ((size < 2) != (b.size < 2)) + return b.size < 2; + if (size == 1 && b.size == 1) // Special case: 'shortcircuit' optional packages + return clause->solutions.size() < b.clause->solutions.size(); return false; } -void APT::Solver::Work::Dump(pkgCache &cache) +std::string APT::Solver::Clause::toString(pkgCache &cache) const { - if (erased) - std::cerr << "Erased "; - if (optional) - std::cerr << "Optional "; - std::cerr << "Item (" << ssize_t(size <= solutions.size() ? size : -1) << "@" << depth << (upgrade ? "u" : "") << ") "; + std::string out; if (auto Pkg = reason.Pkg(cache); not Pkg.end()) - std::cerr << Pkg.FullName(); + out.append(Pkg.FullName()); if (auto Ver = reason.Ver(cache); not Ver.end()) - std::cerr << Ver.ParentPkg().FullName() << "=" << Ver.VerStr(); - std::cerr << " -> "; - for (auto sol : solutions) - { - auto Ver = pkgCache::VerIterator(cache, sol); - std::cerr << " | " << Ver.ParentPkg().FullName() << "=" << Ver.VerStr(); - } + out.append(Ver.ParentPkg().FullName()).append("=").append(Ver.VerStr()); + out.append(" -> "); + for (auto var : solutions) + out.append(" | ").append(var.toString(cache)); + return out; +} + +std::string APT::Solver::Work::toString(pkgCache &cache) const +{ + std::ostringstream out; + if (erased) + out << "Erased "; + if (clause->optional) + out << "Optional "; + out << "Item (" << ssize_t(size <= clause->solutions.size() ? size : -1) << "@" << depth << ") "; + out << clause->toString(cache); + return out.str(); } // Prints an implication graph part of the form A -> B -> C, possibly with "not" -std::string APT::Solver::WhyStr(Var reason) +std::string APT::Solver::WhyStr(Var reason) const { std::vector<std::string> out; while (not reason.empty()) { - if (auto Pkg = reason.Pkg(cache); not Pkg.end()) - { - if ((*this)[Pkg].decision == Decision::MUSTNOT) - out.push_back(std::string("not ") + Pkg.FullName()); - else - out.push_back(Pkg.FullName()); - reason = (*this)[Pkg].reason; - } - if (auto Ver = reason.Ver(cache); not Ver.end()) - { - if ((*this)[Ver].decision == Decision::MUSTNOT) - out.push_back(std::string("not ") + Ver.ParentPkg().FullName() + "=" + Ver.VerStr()); - else - out.push_back(Ver.ParentPkg().FullName() + "=" + Ver.VerStr()); - reason = (*this)[Ver].reason; - } + if ((*this)[reason].decision == Decision::MUSTNOT) + out.push_back(std::string("not ") + reason.toString(cache)); + else + out.push_back(reason.toString(cache)); + reason = (*this)[reason].reason; } std::string outstr; @@ -274,7 +282,7 @@ std::string APT::Solver::WhyStr(Var reason) bool APT::Solver::ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) const { const auto pkg = cand.ParentPkg(); - const int candPriority = policy.GetPriority(cand); + const int candPriority = GetPriority(cand); for (auto ver = cand.Cache()->FindGrp(cand.SourcePkgName()).VersionsInSource(); not ver.end(); ver = ver.NextInSource()) { @@ -283,11 +291,11 @@ bool APT::Solver::ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) cons continue; // We also take equal priority here, given that we have a higher version - const int priority = policy.GetPriority(ver, true); + const int priority = GetPriority(ver); if (priority == 0 || priority < candPriority) continue; - pkgObsolete[pkg->ID] = 2; + pkgObsolete[pkg] = 2; if (debug >= 3) std::cerr << "Obsolete: " << cand.ParentPkg().FullName() << "=" << cand.VerStr() << " due to " << ver.ParentPkg().FullName() << "=" << ver.VerStr() << "\n"; return true; @@ -298,10 +306,10 @@ bool APT::Solver::ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) cons bool APT::Solver::Obsolete(pkgCache::PkgIterator pkg) const { - if (pkgObsolete[pkg->ID] != 0) - return pkgObsolete[pkg->ID] == 2; + if (pkgObsolete[pkg] != 0) + return pkgObsolete[pkg] == 2; - auto ver = policy.GetCandidateVer(pkg); + auto ver = GetCandidateVer(pkg); if (ver.end() && not StrictPinning) ver = pkg.VersionList(); @@ -309,7 +317,7 @@ bool APT::Solver::Obsolete(pkgCache::PkgIterator pkg) const { if (debug >= 3) std::cerr << "Obsolete: " << pkg.FullName() << " - not installable\n"; - pkgObsolete[pkg->ID] = 2; + pkgObsolete[pkg] = 2; return true; } @@ -319,12 +327,12 @@ bool APT::Solver::Obsolete(pkgCache::PkgIterator pkg) const for (auto file = ver.FileList(); !file.end(); file++) if ((file.File()->Flags & pkgCache::Flag::NotSource) == 0) { - pkgObsolete[pkg->ID] = 1; + pkgObsolete[pkg] = 1; return false; } if (debug >= 3) std::cerr << "Obsolete: " << ver.ParentPkg().FullName() << "=" << ver.VerStr() << " - not installable\n"; - pkgObsolete[pkg->ID] = 2; + pkgObsolete[pkg] = 2; return true; } bool APT::Solver::Assume(Var var, bool decision, Var reason) @@ -367,62 +375,59 @@ bool APT::Solver::Propagate() { Var var = propQ.front(); propQ.pop(); - if ((*this)[var].decision == Decision::MUST && not PropagateInstall(var)) - return false; + if ((*this)[var].decision == Decision::MUST) + { + Discover(var); + for (auto &clause : (*this)[var].clauses) + if (not AddWork(Work{clause.get(), depth()})) + return false; + } else if ((*this)[var].decision == Decision::MUSTNOT && not PropagateReject(var)) return false; } return true; } -bool APT::Solver::PropagateInstall(Var var) +void APT::Solver::RegisterClause(Clause &&clause) +{ + auto &clauses = (*this)[clause.reason].clauses; + clauses.push_back(std::make_unique<Clause>(std::move(clause))); +} + +void APT::Solver::Discover(Var var) { + auto &state = (*this)[var]; + + if (state.flags.discovered) + return; + + state.flags.discovered = true; + if (auto Pkg = var.Pkg(cache); not Pkg.end()) { - bool anyInstallable = false; - bool anyMust = false; - // Insert the work item. - Work workItem{Var(Pkg), depth(), Group::SelectVersion}; + Clause clause{Var(Pkg), Group::SelectVersion}; for (auto ver = Pkg.VersionList(); not ver.end(); ver++) - { - workItem.solutions.push_back(ver); - if ((*this)[ver].decision != Decision::MUSTNOT) - anyInstallable = true; - if ((*this)[ver].decision == Decision::MUST) - anyMust = true; - } - - if (not anyInstallable) - { - _error->Error("Conflict: %s -> %s but no versions are installable", - WhyStr((*this)[Pkg].reason).c_str(), Pkg.FullName().c_str()); - for (auto ver = Pkg.VersionList(); not ver.end(); ver++) - _error->Error("Uninstallable version: %s", WhyStr(Var(ver)).c_str()); - return false; - } - - std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, Pkg, *this}); - assert(workItem.solutions.size() > 0); + clause.solutions.push_back(Var(ver)); - if (workItem.solutions.size() > 1 || workItem.optional) - AddWork(std::move(workItem)); - else if (not Enqueue(Var(pkgCache::VerIterator(cache, workItem.solutions[0])), true, workItem.reason)) - return false; + std::stable_sort(clause.solutions.begin(), clause.solutions.end(), CompareProviders3{cache, policy, Pkg, *this}); + RegisterClause(std::move(clause)); - // FIXME: We skip enqueuing duplicate common dependencies if we already selected a version, but - // we should not have common dependencies duplicated in the version objects anyway. - if (not anyMust && not EnqueueCommonDependencies(Pkg)) - return false; + RegisterCommonDependencies(Pkg); } else if (auto Ver = var.Ver(cache); not Ver.end()) { - if (not Enqueue(Var(Ver.ParentPkg()), true, Var(Ver))) - return false; + Clause clause{Var(Ver), Group::SelectVersion}; + clause.solutions = {Var(Ver.ParentPkg())}; + RegisterClause(std::move(clause)); for (auto OV = Ver.ParentPkg().VersionList(); not OV.end(); ++OV) { - if (OV != Ver && not Enqueue(Var(OV), false, Var(Ver))) - return false; + if (OV == Ver) + continue; + + Clause clause{Var(Ver), Group::SelectVersion, false, true /* negative */}; + clause.solutions = {Var(OV)}; + RegisterClause(std::move(clause)); } for (auto dep = Ver.DependsList(); not dep.end();) @@ -432,12 +437,11 @@ bool APT::Solver::PropagateInstall(Var var) pkgCache::DepIterator end; dep.GlobOr(start, end); // advances dep - if (not EnqueueOrGroup(start, end, Var(Ver))) - return false; + auto clause = TranslateOrGroup(start, end, Var(Ver)); + + RegisterClause(std::move(clause)); } } - - return true; } bool APT::Solver::PropagateReject(Var var) @@ -477,10 +481,8 @@ bool APT::Solver::PropagateReject(Var var) return true; } -bool APT::Solver::EnqueueCommonDependencies(pkgCache::PkgIterator Pkg) +void APT::Solver::RegisterCommonDependencies(pkgCache::PkgIterator Pkg) { - if (not _config->FindB("APT::Solver::Enqueue-Common-Dependencies", true)) - return false; for (auto dep = Pkg.VersionList().DependsList(); not dep.end();) { pkgCache::DepIterator start; @@ -498,79 +500,66 @@ bool APT::Solver::EnqueueCommonDependencies(pkgCache::PkgIterator Pkg) } if (not allHaveDep) continue; - if (not EnqueueOrGroup(start, end, Var(Pkg))) - return false; + auto clause = TranslateOrGroup(start, end, Var(Pkg)); + RegisterClause(std::move(clause)); } - - return true; } -bool APT::Solver::EnqueueOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason) +APT::Solver::Clause APT::Solver::TranslateOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason) { auto TgtPkg = start.TargetPkg(); auto Ver = start.ParentVer(); - auto fixPolicy = _config->FindB("APT::Get::Fix-Policy-Broken"); // Non-important dependencies can only be installed if they are currently satisfied, see the check further // below once we have calculated all possible solutions. if (start.ParentPkg()->CurrentVer == 0 && not policy.IsImportantDep(start)) - return true; + return Clause{reason, Group::Satisfy, true}; // Replaces and Enhances are not a real dependency. if (start->Type == pkgCache::Dep::Replaces || start->Type == pkgCache::Dep::Enhances) - return true; + return Clause{reason, Group::Satisfy, true}; if (unlikely(debug >= 3)) std::cerr << "Found dependency critical " << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " -> " << start.TargetPkg().FullName() << "\n"; - Work workItem{reason, depth(), Group::Satisfy, not start.IsCritical() /* optional */}; + Clause clause{reason, Group::Satisfy, not start.IsCritical() /* optional */, start.IsNegative()}; + + clause.dep = start; do { - auto begin = workItem.solutions.size(); + auto begin = clause.solutions.size(); auto all = start.AllTargets(); for (auto tgt = all; *tgt; ++tgt) { pkgCache::VerIterator tgti(cache, *tgt); - - if (start.IsNegative()) - { - if (unlikely(debug >= 3)) - std::cerr << "Reject: " << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " -> " << tgti.ParentPkg().FullName() << "=" << tgti.VerStr() << "\n"; - // FIXME: We should be collecting these and marking the heap only once. - if (not Enqueue(Var(pkgCache::VerIterator(cache, *tgt)), false, Var(Ver))) - return false; - } - else - { - if (unlikely(debug >= 3)) - std::cerr << "Adding work to item " << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " -> " << tgti.ParentPkg().FullName() << "=" << tgti.VerStr() << "\n"; - workItem.solutions.push_back(*tgt); - } + if (unlikely(debug >= 3)) + std::cerr << "Adding work to item " << reason.toString(cache) << " -> " << tgti.ParentPkg().FullName() << "=" << tgti.VerStr() << (clause.negative ? " (negative)" : "") << "\n"; + clause.solutions.push_back(Var(pkgCache::VerIterator(cache, *tgt))); } delete[] all; // If we are fixing the policy, we need to sort each alternative in an or group separately // 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, *this}); + if (FixPolicyBroken) + std::stable_sort(clause.solutions.begin() + begin, clause.solutions.end(), CompareProviders3{cache, policy, TgtPkg, *this}); if (start == end) break; ++start; } while (1); - if (not fixPolicy) - std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, policy, TgtPkg, *this}); + if (not FixPolicyBroken) + std::stable_sort(clause.solutions.begin(), clause.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).ParentPkg()); })) - workItem.group = Group::SatisfyObsolete; + if (std::all_of(clause.solutions.begin(), clause.solutions.end(), [this](auto var) -> auto + { return var.CastPkg(cache)->CurrentVer == 0; })) + clause.group = Group::SatisfyNew; + if (std::any_of(clause.solutions.begin(), clause.solutions.end(), [this](auto var) -> auto + { return Obsolete(var.CastPkg(cache)); })) + clause.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) + if (clause.optional && start.ParentPkg()->CurrentVer) { bool important = policy.IsImportantDep(start); bool newOptional = true; @@ -579,60 +568,30 @@ bool APT::Solver::EnqueueOrGroup(pkgCache::DepIterator start, pkgCache::DepItera if (not D.IsCritical() && not D.IsNegative() && D.TargetPkg() == start.TargetPkg()) newOptional = false, wasImportant = policy.IsImportantDep(D); - bool satisfied = std::any_of(workItem.solutions.begin(), workItem.solutions.end(), [this](auto ver) - { return pkgCache::VerIterator(cache, ver).ParentPkg().CurrentVer() == ver; }); + bool satisfied = std::any_of(clause.solutions.begin(), clause.solutions.end(), [this](auto var) + { return Var(var.CastPkg(cache).CurrentVer()) == var; }); if (important && wasImportant && not newOptional && not satisfied) { if (unlikely(debug >= 3)) - { - std::cerr << "Ignoring unsatisfied Recommends "; - workItem.Dump(cache); - std::cerr << "\n"; - } - return true; + std::cerr << "Ignoring unsatisfied Recommends " << clause.toString(cache) << std::endl; + clause.solutions.clear(); } - if (not important && not wasImportant && not newOptional && satisfied) + else if (not important && not wasImportant && not newOptional && satisfied) { if (unlikely(debug >= 3)) - { - std::cerr << "Promoting satisfied Suggests to Recommends: "; - workItem.Dump(cache); - std::cerr << "\n"; - } + std::cerr << "Promoting satisfied Suggests to Recommends: " << clause.toString(cache) << std::endl; important = true; } - if (not important) + else if (not important) { if (unlikely(debug >= 3)) - { - std::cerr << "Ignoring Suggests "; - workItem.Dump(cache); - std::cerr << "\n"; - } - return true; + std::cerr << "Ignoring Suggests " << clause.toString(cache) << std::endl; + return Clause{reason, Group::Satisfy, true}; } } - if (not workItem.solutions.empty()) - { - // std::stable_sort(workItem.solutions.begin(), workItem.solutions.end(), CompareProviders3{cache, TgtPkg}); - if (unlikely(debug >= 3 && workItem.optional)) - { - std::cerr << "Enqueuing Recommends "; - workItem.Dump(cache); - std::cerr << "\n"; - } - if (workItem.optional || workItem.solutions.size() > 1) - AddWork(std::move(workItem)); - else if (not Enqueue(Var(pkgCache::VerIterator(cache, workItem.solutions[0])), true, reason)) - return false; - } - else if (start.IsCritical() && not start.IsNegative()) - { - return _error->Error("Unsatisfiable dependency group %s=%s -> %s", Ver.ParentPkg().FullName().c_str(), Ver.VerStr(), TgtPkg.FullName().c_str()); - } - return true; + return clause; } // \brief Find the or group containing the given dependency. @@ -705,11 +664,7 @@ bool APT::Solver::RejectReverseDependencies(pkgCache::VerIterator Ver) void APT::Solver::Push(Work work) { if (unlikely(debug >= 2)) - { - std::cerr << "Trying choice for "; - work.Dump(cache); - std::cerr << "\n"; - } + std::cerr << "Trying choice for " << work.toString(cache) << std::endl; choices.push_back(solved.size()); solved.push_back(Solved{Var(), std::move(work)}); @@ -742,13 +697,10 @@ void APT::Solver::UndoOne() if (auto work = solvedItem.work) { if (unlikely(debug >= 4)) - { - std::cerr << "Adding work item "; - work->Dump(cache); - std::cerr << "\n"; - } + std::cerr << "Adding work item " << work->toString(cache) << std::endl; - AddWork(std::move(*work)); + if (not AddWork(std::move(*work))) + abort(); } solved.pop_back(); @@ -769,7 +721,7 @@ bool APT::Solver::Pop() assert(choices.back() < solved.size()); int itemsToUndo = solved.size() - choices.back(); - pkgCache::VerIterator choice(cache, solved[choices.back()].work->choice); + auto choice = solved[choices.back()].work->choice; for (; itemsToUndo; --itemsToUndo) UndoOne(); @@ -784,24 +736,43 @@ bool APT::Solver::Pop() std::make_heap(work.begin(), work.end()); if (unlikely(debug >= 2)) - std::cerr << "Backtracking to choice " << choice.ParentPkg().FullName() << "=" << choice.VerStr() << "\n"; + std::cerr << "Backtracking to choice " << choice.toString(cache) << "\n"; // FIXME: There should be a reason! - if (not Enqueue(Var(choice), false, {})) + if (not Enqueue(choice, false, {})) return false; if (unlikely(debug >= 2)) - std::cerr << "Backtracked to choice " << choice.ParentPkg().FullName() << "=" << choice.VerStr() << "\n"; + std::cerr << "Backtracked to choice " << choice.toString(cache) << "\n"; return true; } -void APT::Solver::AddWork(Work &&w) +bool APT::Solver::AddWork(Work &&w) { - w.size = std::count_if(w.solutions.begin(), w.solutions.end(), [this](auto V) - { return (*this)[V].decision != Decision::MUSTNOT; }); - work.push_back(std::move(w)); - std::push_heap(work.begin(), work.end()); + if (w.clause->negative) + { + for (auto var : w.clause->solutions) + if (not Enqueue(var, false, w.clause->reason)) + return false; + } + else if (not w.clause->solutions.empty()) + { + if (unlikely(debug >= 3 && w.clause->optional)) + std::cerr << "Enqueuing Recommends " << w.clause->toString(cache) << std::endl; + if (w.clause->solutions.size() == 1 && not w.clause->optional) + return Enqueue(w.clause->solutions[0], true, w.clause->reason); + + w.size = std::count_if(w.clause->solutions.begin(), w.clause->solutions.end(), [this](auto V) + { return (*this)[V].decision != Decision::MUSTNOT; }); + work.push_back(std::move(w)); + std::push_heap(work.begin(), work.end()); + } + else if (not w.clause->optional && w.clause->dep) + return _error->Error("Unsatisfiable dependency group %s -> %s", w.clause->reason.toString(cache).c_str(), pkgCache::DepIterator(cache, w.clause->dep).TargetPkg().FullName().c_str()); + else if (not w.clause->optional) + return _error->Error("Unsatisfiable dependency group %s", w.clause->reason.toString(cache).c_str()); + return true; } void APT::Solver::RescoreWorkIfNeeded() @@ -815,7 +786,7 @@ void APT::Solver::RescoreWorkIfNeeded() { if (w.erased) continue; - size_t newSize = std::count_if(w.solutions.begin(), w.solutions.end(), [this](auto V) + size_t newSize = std::count_if(w.clause->solutions.begin(), w.clause->solutions.end(), [this](auto V) { return (*this)[V].decision != Decision::MUSTNOT; }); // Notably we only insert the work into the queue if it got smaller. Work that got larger @@ -864,7 +835,7 @@ bool APT::Solver::Solve() } // If our size increased, queue again. - size_t newSize = std::count_if(work.back().solutions.begin(), work.back().solutions.end(), [this](auto V) + size_t newSize = std::count_if(work.back().clause->solutions.begin(), work.back().clause->solutions.end(), [this](auto V) { return (*this)[V].decision != Decision::MUSTNOT; }); if (newSize > work.back().size) @@ -879,60 +850,51 @@ bool APT::Solver::Solve() work.pop_back(); solved.push_back(Solved{Var(), item}); - if (std::any_of(item.solutions.begin(), item.solutions.end(), [this](auto ver) + if (std::any_of(item.clause->solutions.begin(), item.clause->solutions.end(), [this](auto ver) { return (*this)[ver].decision == Decision::MUST; })) { if (unlikely(debug >= 2)) - { - std::cerr << "ELIDED "; - item.Dump(cache); - std::cerr << "\n"; - } + std::cerr << "ELIDED " << item.toString(cache) << std::endl; continue; } if (unlikely(debug >= 1)) - { - item.Dump(cache); - std::cerr << "\n"; - } + std::cerr << item.toString(cache) << std::endl; - assert(item.solutions.size() > 1 || item.optional); + assert(item.clause->solutions.size() > 1 || item.clause->optional); bool foundSolution = false; - for (auto &sol : item.solutions) + for (auto &sol : item.clause->solutions) { - pkgCache::VerIterator ver(cache, sol); - if ((*this)[ver].decision == Decision::MUSTNOT) + if ((*this)[sol].decision == Decision::MUSTNOT) { if (unlikely(debug >= 3)) - std::cerr << "(existing conflict: " << ver.ParentPkg().FullName() << "=" << ver.VerStr() << ")\n"; + std::cerr << "(existing conflict: " << sol.toString(cache) << ")\n"; continue; } - if (item.size > 1 || item.optional) + if (item.size > 1 || item.clause->optional) { - item.choice = ver; + item.choice = sol; Push(item); } if (unlikely(debug >= 3)) - std::cerr << "(try it: " << ver.ParentPkg().FullName() << "=" << ver.VerStr() << ")\n"; - if (not Enqueue(Var(ver), true, item.reason) && not Pop()) + std::cerr << "(try it: " << sol.toString(cache) << ")\n"; + if (not Enqueue(sol, true, item.clause->reason) && not Pop()) return false; foundSolution = true; break; } - if (not foundSolution && not item.optional) + if (not foundSolution && not item.clause->optional) { std::ostringstream dep; - assert(item.solutions.size() > 0); - for (auto &sol : item.solutions) - dep << (dep.tellp() == 0 ? "" : " | ") << pkgCache::VerIterator(cache, sol).ParentPkg().FullName() << "=" << pkgCache::VerIterator(cache, sol).VerStr(); - _error->Error("Unsatisfiable dependency: %s -> %s", WhyStr(item.reason).c_str(), dep.str().c_str()); - for (auto &sol : item.solutions) + assert(item.clause->solutions.size() > 0); + for (auto &sol : item.clause->solutions) + dep << (dep.tellp() == 0 ? "" : " | ") << sol.toString(cache); + _error->Error("Unsatisfiable dependency: %s -> %s", WhyStr(item.clause->reason).c_str(), dep.str().c_str()); + for (auto &sol : item.clause->solutions) if ((*this)[sol].decision == Decision::MUSTNOT) - _error->Error("Not considered: %s=%s: %s", pkgCache::VerIterator(cache, sol).ParentPkg().FullName().c_str(), - pkgCache::VerIterator(cache, sol).VerStr(), - WhyStr(Var(pkgCache::VerIterator(cache, sol))).c_str()); + _error->Error("Not considered: %s: %s", sol.toString(cache).c_str(), + WhyStr(sol).c_str()); if (not Pop()) return false; } @@ -944,7 +906,6 @@ bool APT::Solver::Solve() // \brief Apply the selections from the dep cache to the solver bool APT::Solver::FromDepCache(pkgDepCache &depcache) { - bool AllowRemoveManual = AllowRemove && _config->FindB("APT::Solver::RemoveManual", false); DefaultRootSetFunc2 rootSet(&cache); // Enforce strict pinning rules by rejecting all forbidden versions. @@ -1011,11 +972,24 @@ bool APT::Solver::FromDepCache(pkgDepCache &depcache) } else { - Work w{Var(), depth(), Group, isOptional, Upgrade}; + Clause w{Var(), Group, isOptional}; + w.solutions.push_back(Var(P)); + RegisterClause(std::move(w)); + if (not AddWork(Work{rootState->clauses.back().get(), depth()})) + return false; + + // Given A->A2|A1, B->B1|B2; Bn->An, if we select `not A1`, we + // should try to install A2 before trying B so we end up with + // A2, B2, instead of removing A1 to keep B1 installed. This + // requires some special casing in Work::operator< above. + // Compare test-bug-712116-dpkg-pre-install-pkgs-hook-multiarch + Clause shortcircuit{Var(), Group, isOptional}; for (auto V = P.VersionList(); not V.end(); ++V) - w.solutions.push_back(V); - std::stable_sort(w.solutions.begin(), w.solutions.end(), CompareProviders3{cache, policy, P, *this}); - AddWork(std::move(w)); + shortcircuit.solutions.push_back(Var(V)); + std::stable_sort(shortcircuit.solutions.begin(), shortcircuit.solutions.end(), CompareProviders3{cache, policy, P, *this}); + RegisterClause(std::move(shortcircuit)); + if (not AddWork(Work{rootState->clauses.back().get(), depth()})) + return false; } } } @@ -1023,7 +997,7 @@ bool APT::Solver::FromDepCache(pkgDepCache &depcache) return Propagate(); } -bool APT::Solver::ToDepCache(pkgDepCache &depcache) +bool APT::Solver::ToDepCache(pkgDepCache &depcache) const { pkgDepCache::ActionGroup group(depcache); for (auto P = cache.PkgBegin(); not P.end(); P++) |
