summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Andres Klode <julian.klode@canonical.com>2025-02-05 21:31:53 +0100
committerJulian Andres Klode <julian.klode@canonical.com>2025-02-14 19:04:56 +0100
commitb8918cb89ada945d92c720446177f1ef5185b5a5 (patch)
tree4184c110c85e04d55dd71a6f5b0b440624e671a7
parenta9587b39ea6776b1d1324288786176102f65cee5 (diff)
solver3: Reject reverse dependencies natively
Instead of utilizing the reverse depends functionality of the cache and marking all possible reverse dependencies for removal, mark them ourselves by keeping track of reverse-implication-clauses. Notably, this improves the reverse dependency rejection substantially: The previous RejectReverseDependencies() function did not handle Provides. For this to work correctly right now, we need to discover optional clauses too when queuing them. This is somewhat suboptimal as we technically we don't care if they become unsat, we just waste time tracking them. The tests get a bit awkward, but oh well, we use what we can use.
-rw-r--r--apt-pkg/solver3.cc132
-rw-r--r--apt-pkg/solver3.h6
-rwxr-xr-xtest/integration/test-apt-get-build-dep-barbarian6
-rwxr-xr-xtest/integration/test-apt-never-markauto-sections2
-rwxr-xr-xtest/integration/test-bug-618848-always-respect-user-requests2
-rwxr-xr-xtest/integration/test-multiarch-allowed12
-rwxr-xr-xtest/integration/test-prefer-higher-priority-providers5
7 files changed, 37 insertions, 128 deletions
diff --git a/apt-pkg/solver3.cc b/apt-pkg/solver3.cc
index d363cdee7..2d9e92c99 100644
--- a/apt-pkg/solver3.cc
+++ b/apt-pkg/solver3.cc
@@ -383,8 +383,25 @@ bool APT::Solver::Propagate()
if (not AddWork(Work{clause.get(), depth()}))
return false;
}
- else if ((*this)[var].decision == Decision::MUSTNOT && not PropagateReject(var))
- return false;
+ else if ((*this)[var].decision == Decision::MUSTNOT)
+ {
+ for (auto rclause : (*this)[var].rclauses)
+ {
+ if (rclause->negative || rclause->reason.empty() || rclause->optional ||
+ std::any_of(rclause->solutions.begin(), rclause->solutions.end(), [this](auto var)
+ { return (*this)[var].decision != Decision::MUSTNOT; }))
+ continue;
+
+ if ((*this)[rclause->reason].decision == Decision::MUSTNOT)
+ continue;
+
+ if (unlikely(debug >= 3))
+ std::cerr << "Propagate NOT " << var.toString(cache) << " to " << rclause->reason.toString(cache) << " for dep " << const_cast<Clause *>(rclause)->toString(cache) << std::endl;
+
+ if (not Enqueue(rclause->reason, false, var)) // Last version invalidated
+ return false;
+ }
+ }
}
return true;
}
@@ -393,6 +410,9 @@ void APT::Solver::RegisterClause(Clause &&clause)
{
auto &clauses = (*this)[clause.reason].clauses;
clauses.push_back(std::make_unique<Clause>(std::move(clause)));
+ auto const &inserted = clauses.back();
+ for (auto var : inserted->solutions)
+ (*this)[var].rclauses.push_back(inserted.get());
}
void APT::Solver::Discover(Var var)
@@ -460,43 +480,6 @@ void APT::Solver::Discover(Var var)
}
}
-bool APT::Solver::PropagateReject(Var var)
-{
- if (auto Pkg = var.Pkg(cache); not Pkg.end())
- {
- for (auto ver = Pkg.VersionList(); not ver.end(); ver++)
- if (not Enqueue(Var(ver), false, Var(Pkg)))
- return false;
- }
- else if (auto Ver = var.Ver(cache); not Ver.end())
- {
- if (auto pkg = Ver.ParentPkg(); (*this)[pkg].decision != Decision::MUSTNOT)
- {
- bool anyInstallable = false;
- for (auto otherVer = pkg.VersionList(); not otherVer.end(); otherVer++)
- if (otherVer->ID != Ver->ID && (*this)[otherVer].decision != Decision::MUSTNOT)
- anyInstallable = true;
-
- if (anyInstallable)
- ;
- else if ((*this)[pkg].decision == Decision::MUST) // Must install, but none available
- {
- _error->Error("Conflict: %s but no versions are installable",
- WhyStr(Var(pkg)).c_str());
- for (auto otherVer = pkg.VersionList(); not otherVer.end(); otherVer++)
- if ((*this)[otherVer].decision == Decision::MUSTNOT)
- _error->Error("Uninstallable version: %s", WhyStr(Var(otherVer)).c_str());
- return _error->Error("Uninstallable version: %s", WhyStr(Var(Ver)).c_str());
- }
- else if (not Enqueue(Var(Ver.ParentPkg()), false, Var(Ver))) // Last version invalidated
- return false;
- }
- if (not RejectReverseDependencies(Ver))
- return false;
- }
- return true;
-}
-
void APT::Solver::RegisterCommonDependencies(pkgCache::PkgIterator Pkg)
{
for (auto dep = Pkg.VersionList().DependsList(); not dep.end();)
@@ -618,73 +601,6 @@ APT::Solver::Clause APT::Solver::TranslateOrGroup(pkgCache::DepIterator start, p
return clause;
}
-// \brief Find the or group containing the given dependency.
-static void FindOrGroup(pkgCache::DepIterator const &D, pkgCache::DepIterator &start, pkgCache::DepIterator &end)
-{
- for (auto dep = D.ParentVer().DependsList(); not dep.end();)
- {
- dep.GlobOr(start, end); // advances dep
-
- for (auto member = start;;)
- {
- if (member == D)
- return;
- if (member == end)
- break;
- member++;
- }
- }
-
- _error->Fatal("Found a dependency that does not exist in its parent version");
- abort();
-}
-
-// This is the opposite of EnqueueOrDependencies, it rejects the reverse dependencies of the
-// given version iterator.
-bool APT::Solver::RejectReverseDependencies(pkgCache::VerIterator Ver)
-{
- // This checks whether an or group is still satisfiable.
- auto stillPossible = [this](pkgCache::DepIterator start, pkgCache::DepIterator end)
- {
- while (1)
- {
- std::unique_ptr<pkgCache::Version *[]> Ts{start.AllTargets()};
- for (size_t i = 0; Ts[i] != nullptr; ++i)
- if ((*this)[Ts[i]].decision != Decision::MUSTNOT)
- return true;
-
- if (start == end)
- return false;
-
- start++;
- }
- };
-
- for (auto RD = Ver.ParentPkg().RevDependsList(); not RD.end(); ++RD)
- {
- auto RDV = RD.ParentVer();
- if (RD.IsNegative() || not RD.IsCritical() || not RD.IsSatisfied(Ver))
- continue;
-
- if ((*this)[RDV].decision == Decision::MUSTNOT)
- continue;
-
- pkgCache::DepIterator start;
- pkgCache::DepIterator end;
- FindOrGroup(RD, start, end);
-
- if (stillPossible(start, end))
- continue;
-
- if (unlikely(debug >= 3))
- std::cerr << "Propagate NOT " << Ver.ParentPkg().FullName() << "=" << Ver.VerStr() << " to " << RDV.ParentPkg().FullName() << "=" << RDV.VerStr() << " for dependency group starting with" << start.TargetPkg().FullName() << std::endl;
-
- if (not Enqueue(Var(RDV), false, Var(Ver)))
- return false;
- }
- return true;
-}
-
void APT::Solver::Push(Work work)
{
if (unlikely(debug >= 2))
@@ -1019,6 +935,10 @@ bool APT::Solver::FromDepCache(pkgDepCache &depcache)
RegisterClause(std::move(shortcircuit));
if (not AddWork(Work{rootState->clauses.back().get(), depth()}))
return false;
+
+ // Discovery here is needed so the shortcircuit clause can actually become unit.
+ if (P.VersionList() && P.VersionList()->NextVer)
+ Discover(Var(P));
}
}
}
diff --git a/apt-pkg/solver3.h b/apt-pkg/solver3.h
index da75179d5..91b852c98 100644
--- a/apt-pkg/solver3.h
+++ b/apt-pkg/solver3.h
@@ -248,14 +248,10 @@ class Solver
// \brief Enqueue dependencies shared by all versions of the package.
void RegisterCommonDependencies(pkgCache::PkgIterator Pkg);
- // \brief Reject reverse dependencies. Must call std::make_heap() after.
- [[nodiscard]] bool RejectReverseDependencies(pkgCache::VerIterator Ver);
// \brief Translate an or group into a clause object
[[nodiscard]] Clause TranslateOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason);
// \brief Propagate all pending propagations
[[nodiscard]] bool Propagate();
- // \brief Propagate a rejection of a variable
- [[nodiscard]] bool PropagateReject(Var var);
// \brief Return the current depth (choices.size() with casting)
depth_type depth()
@@ -470,6 +466,8 @@ struct APT::Solver::State
// \brief Clauses owned by this package/version
std::vector<std::unique_ptr<Clause>> clauses;
+ // \brief Reverse clauses, that is dependencies (or conflicts) from other packages on this one
+ std::vector<const Clause *> rclauses;
};
/**
diff --git a/test/integration/test-apt-get-build-dep-barbarian b/test/integration/test-apt-get-build-dep-barbarian
index b8ab1211b..e070e96ca 100755
--- a/test/integration/test-apt-get-build-dep-barbarian
+++ b/test/integration/test-apt-get-build-dep-barbarian
@@ -83,7 +83,7 @@ testsuccessequal "$(installsfoosamey 'amd64' 'i386')" apt build-dep cool-foo -s
testsuccessequal "$(installsfoosamey 'i386' 'i386')" apt build-dep bad-amd64-foo -s -a i386
testsuccessequal "$(installsfoosamey 'amd64' 'i386')" apt build-dep bad-armel-foo -s -a i386
testsuccessequal "$(installsfoosamey 'i386' 'i386')" apt build-dep bad-amd64-armel-foo -s -a i386
-testfailuremsg 'E: Conflict: builddeps:bad-amd64-i386-foo:i386=1 -> not foo:i386=1 -> not builddeps:bad-amd64-i386-foo:i386=1 but builddeps:bad-amd64-i386-foo:i386=1' apt build-dep bad-amd64-i386-foo -s -a i386 --solver 3.0
+testfailuremsg 'E: Conflict: builddeps:bad-amd64-i386-foo:i386=1 -> not foo:amd64=1 -> not builddeps:bad-amd64-i386-foo:i386=1 but builddeps:bad-amd64-i386-foo:i386=1' apt build-dep bad-amd64-i386-foo -s -a i386 --solver 3.0
testfailureequal 'Reading package lists...
Reading package lists...
Building dependency tree...
@@ -96,7 +96,7 @@ The following information may help to resolve the situation:
The following packages have unmet dependencies:
builddeps:bad-amd64-i386-foo:i386 : Depends: foo:i386
E: Unable to correct problems, you have held broken packages.' apt build-dep bad-amd64-i386-foo -s -a i386 --solver internal
-testfailuremsg 'E: Conflict: builddeps:bad-amd64-i386-armel-foo:i386=1 -> not foo:i386=1 -> not builddeps:bad-amd64-i386-armel-foo:i386=1 but builddeps:bad-amd64-i386-armel-foo:i386=1' apt build-dep bad-amd64-i386-armel-foo -s -a i386 --solver 3.0
+testfailuremsg 'E: Conflict: builddeps:bad-amd64-i386-armel-foo:i386=1 -> not foo:amd64=1 -> not builddeps:bad-amd64-i386-armel-foo:i386=1 but builddeps:bad-amd64-i386-armel-foo:i386=1' apt build-dep bad-amd64-i386-armel-foo -s -a i386 --solver 3.0
testfailureequal 'Reading package lists...
Reading package lists...
Building dependency tree...
@@ -115,7 +115,7 @@ testsuccessequal "$(installsfoosamey 'i386' 'armel')" apt build-dep bad-amd64-fo
testsuccessequal "$(installsfoosamey 'amd64' 'armel')" apt build-dep bad-armel-foo -s -a armel
testsuccessequal "$(installsfoosamey 'i386' 'armel')" apt build-dep bad-amd64-armel-foo -s -a armel
testsuccessequal "$(installsfoosamey 'armel' 'armel')" apt build-dep bad-amd64-i386-foo -s -a armel
-FAILUREMSG="E: Conflict: builddeps:bad-amd64-i386-armel-foo:armel=1 -> not foo:armel=1 -> not builddeps:bad-amd64-i386-armel-foo:armel=1 but builddeps:bad-amd64-i386-armel-foo:armel=1"
+FAILUREMSG="E: Conflict: builddeps:bad-amd64-i386-armel-foo:armel=1 -> not foo:amd64=1 -> not builddeps:bad-amd64-i386-armel-foo:armel=1 but builddeps:bad-amd64-i386-armel-foo:armel=1"
FAILURE='Reading package lists...
Building dependency tree...
Some packages could not be installed. This may mean that you have
diff --git a/test/integration/test-apt-never-markauto-sections b/test/integration/test-apt-never-markauto-sections
index e60a6e648..4d7324b02 100755
--- a/test/integration/test-apt-never-markauto-sections
+++ b/test/integration/test-apt-never-markauto-sections
@@ -49,7 +49,7 @@ Remv foreignpkg:i386 [1]
Remv nosection [1]
Remv texteditor [1]' aptget autoremove mydesktop -s
-testfailuremsg 'E: Conflict: not texteditor:amd64 -> not texteditor:amd64=1 -> not mydesktop-core:amd64=1 but mydesktop:amd64 -> mydesktop-core:amd64 -> mydesktop-core:amd64=1' aptget autoremove texteditor -s --solver 3.0 #-o Debug::pkgDepCache::AutoInstall=1 -o Debug::pkgProblemResolver=1 -o Debug::pkgDepCache::Marker=1
+testfailuremsg 'E: Conflict: not texteditor:amd64 -> not bad-texteditor:amd64 -> not mydesktop-core:amd64 but mydesktop:amd64 -> mydesktop-core:amd64' aptget autoremove texteditor -s --solver 3.0 #-o Debug::pkgDepCache::AutoInstall=1 -o Debug::pkgProblemResolver=1 -o Debug::pkgDepCache::Marker=1
testsuccessequal 'Reading package lists...
Building dependency tree...
Reading state information...
diff --git a/test/integration/test-bug-618848-always-respect-user-requests b/test/integration/test-bug-618848-always-respect-user-requests
index 8ba750b44..d59491af6 100755
--- a/test/integration/test-bug-618848-always-respect-user-requests
+++ b/test/integration/test-bug-618848-always-respect-user-requests
@@ -14,7 +14,7 @@ insertpackage 'unstable' 'exim4-daemon-heavy' 'all' '1.0' 'Depends: libdb4.8'
setupaptarchive
# This does not work in 3.0 solver: We do not remove manually installed packages.
-testfailuremsg "E: Conflict: exim4-daemon-light:i386 -> libdb4.8:i386 but not libdb4.8:i386" aptget remove libdb4.8 --solver 3.0 -s
+testfailuremsg "E: Conflict: not libdb4.8:i386 -> not exim4-daemon-light:i386 but exim4-daemon-light:i386" aptget remove libdb4.8 --solver 3.0 -s
allowremovemanual
testsuccessequal "Reading package lists...
Building dependency tree...
diff --git a/test/integration/test-multiarch-allowed b/test/integration/test-multiarch-allowed
index 9fc49275a..fb8cb653d 100755
--- a/test/integration/test-multiarch-allowed
+++ b/test/integration/test-multiarch-allowed
@@ -64,24 +64,18 @@ Inst needsfoo:i386 (1 unstable [i386])
Conf foo:i386 (1 unstable [i386])
Conf needsfoo:i386 (1 unstable [i386])' aptget install needsfoo:i386 -s
# FIXME: same problem, but two different unmet dependency messages depending on install order
-testfailuremsg "E: Conflict: needsfoo:i386=1 -> foo:i386 but no versions are installable
-E: Uninstallable version: foo:amd64=1 -> not foo:i386=1
-E: Uninstallable version: foo:amd64=1 -> not foo:i386=1" aptget install needsfoo:i386 foo:amd64 -s --solver 3.0
+testfailuremsg "E: Conflict: foo:amd64=1 -> not foo:i386=1 -> not foo:i386 but needsfoo:i386=1 -> foo:i386" aptget install needsfoo:i386 foo:amd64 -s --solver 3.0
testfailureequal "$BADPREFIX
The following packages have unmet dependencies:
foo : Conflicts: foo:i386 but 1 is to be installed
foo:i386 : Conflicts: foo but 1 is to be installed
E: Unable to correct problems, you have held broken packages." aptget install needsfoo:i386 foo:amd64 -s --solver internal
-testfailuremsg "E: Conflict: needsfoo:i386=1 -> foo:i386 but no versions are installable
-E: Uninstallable version: foo:amd64=1 -> not foo:i386=1
-E: Uninstallable version: foo:amd64=1 -> not foo:i386=1" aptget install foo:amd64 needsfoo:i386 -s --solver 3.0
+testfailuremsg "E: Conflict: foo:amd64=1 -> not foo:i386=1 -> not foo:i386 but needsfoo:i386=1 -> foo:i386" aptget install foo:amd64 needsfoo:i386 -s --solver 3.0
testfailureequal "$BADPREFIX
The following packages have unmet dependencies:
needsfoo:i386 : Depends: foo:i386 but it is not installable
E: Unable to correct problems, you have held broken packages." aptget install foo:amd64 needsfoo:i386 -s --solver internal
-testfailuremsg "E: Conflict: needsfoo:amd64=1 -> foo:amd64 but no versions are installable
-E: Uninstallable version: foo:i386=1 -> not foo:amd64=1
-E: Uninstallable version: foo:i386=1 -> not foo:amd64=1" aptget install needsfoo foo:i386 -s --solver 3.0
+testfailuremsg "E: Conflict: foo:i386=1 -> not foo:amd64=1 -> not foo:amd64 but needsfoo:amd64=1 -> foo:amd64" aptget install needsfoo foo:i386 -s --solver 3.0
testfailureequal "$BADPREFIX
The following packages have unmet dependencies:
foo : Conflicts: foo:i386 but 1 is to be installed
diff --git a/test/integration/test-prefer-higher-priority-providers b/test/integration/test-prefer-higher-priority-providers
index 5fed20a59..76a03b3fb 100755
--- a/test/integration/test-prefer-higher-priority-providers
+++ b/test/integration/test-prefer-higher-priority-providers
@@ -91,10 +91,7 @@ Inst awesome (1 unstable [all])
Conf baz (1 unstable [all])
Conf awesome (1 unstable [all])" aptget install awesome foo- bar- -s
-testfailuremsg "E: Unsatisfiable dependency: awesome:$native=1 -> foo:$native=1 | bar:$native=1 | baz:$native=1
-E: Not considered: foo:$native=1: not foo:$native -> not foo:$native=1
-E: Not considered: bar:$native=1: not bar:$native -> not bar:$native=1
-E: Not considered: baz:$native=1: not baz:$native -> not baz:$native=1" aptget install awesome foo- bar- baz- -s --solver 3.0
+testfailuremsg "E: Conflict: awesome:$native=1 -> baz:$native=1 -> baz:$native but not baz:$native" aptget install awesome foo- bar- baz- -s --solver 3.0
testfailureequal "Reading package lists...
Building dependency tree...
Package 'foo' is not installed, so not removed