From 71d3d399d7de75a96b2911677b74ce8b59579ee9 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 4 Feb 2025 18:22:35 +0100 Subject: Introduce pkgDepCache::Transaction for transactional depcache updates --- apt-pkg/depcache.cc | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ apt-pkg/depcache.h | 38 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/apt-pkg/depcache.cc b/apt-pkg/depcache.cc index 970b4c01d..ec34d010b 100644 --- a/apt-pkg/depcache.cc +++ b/apt-pkg/depcache.cc @@ -2650,3 +2650,74 @@ unsigned long long pkgDepCache::BootSize(bool initrdOnly) return std::accumulate(sizes, sizes + MAX_ARTEFACT, off_t{0}) * BootCount * 110 / 100; } /*}}}*/ +// pkgDepCache::Transaction /*{{{*/ +struct pkgDepCache::Transaction::Private +{ + template + static T *copyArray(T *array, size_t count) + { + auto out = new T[count]; + memcpy(&out[0], &array[0], sizeof(T) * count); + return out; + } + // State information + pkgDepCache &cache; + Behavior behavior; + std::unique_ptr PkgState{copyArray(cache.PkgState, cache.GetCache().Head().PackageCount)}; + std::unique_ptr DepState{copyArray(cache.DepState, cache.GetCache().Head().DependsCount)}; + signed long long iUsrSize{cache.iUsrSize}; + unsigned long long iDownloadSize{cache.iDownloadSize}; + unsigned long iInstCount{cache.iInstCount}; + unsigned long iDelCount{cache.iDelCount}; + unsigned long iKeepCount{cache.iKeepCount}; + unsigned long iBrokenCount{cache.iBrokenCount}; + unsigned long iPolicyBrokenCount{cache.iPolicyBrokenCount}; + unsigned long iBadCount{cache.iBadCount}; + + void rollback() + { + memcpy(&cache.PkgState[0], &PkgState[0], sizeof(PkgState[0]) * cache.GetCache().Head().PackageCount); + memcpy(&cache.DepState[0], &DepState[0], sizeof(DepState[0]) * cache.GetCache().Head().DependsCount); + + cache.iUsrSize = iUsrSize; + cache.iDownloadSize = iDownloadSize; + cache.iInstCount = iInstCount; + cache.iDelCount = iDelCount; + cache.iKeepCount = iKeepCount; + cache.iBrokenCount = iBrokenCount; + cache.iPolicyBrokenCount = iPolicyBrokenCount; + cache.iBadCount = iBadCount; + } +}; + +pkgDepCache::Transaction::Transaction(pkgDepCache &cache, Behavior behavior) : d(new Private{cache, behavior}) {} + +void pkgDepCache::Transaction::temporaryRollback() +{ + d->rollback(); +} + +void pkgDepCache::Transaction::commit() +{ + d.reset(); +} + +void pkgDepCache::Transaction::rollback() +{ + if (d == nullptr) + return; + + temporaryRollback(); + d.reset(); +} + +pkgDepCache::Transaction::~Transaction() +{ + if (not d) + return; + if (d->behavior == Behavior::ROLLBACK || (d->behavior == Behavior::AUTO && d->cache.iBrokenCount > d->iBrokenCount)) + rollback(); + else + commit(); +} + /*}}}*/ diff --git a/apt-pkg/depcache.h b/apt-pkg/depcache.h index 39d34d3e4..d2a883180 100644 --- a/apt-pkg/depcache.h +++ b/apt-pkg/depcache.h @@ -267,6 +267,42 @@ class APT_PUBLIC pkgDepCache : protected pkgCache::Namespace bool InstallSuggests; }; + /** + * \brief Perform changes to the depcache atomically. + * + * The default policy for a transaction is to rollback if the number of broken packages + * increased, otherwise to commit. Call commit() or rollback() to override the default + * policy. + */ + class APT_PUBLIC Transaction final + { + struct Private; + std::unique_ptr d; + + public: + enum class Behavior + { + COMMIT, + ROLLBACK, + AUTO, + }; + + explicit Transaction(pkgDepCache &cache, Behavior behavior); + /** \brief Commit the transaction immediately */ + void commit(); + /** \brief Rollback the transaction immediately */ + void rollback(); + /** \brief Like rollback, but can be called multiple times. + * + * You can for example create a new transaction, then temporarily + * rollback to the state before the previous transaction in that + * transaction. + */ + void temporaryRollback(); + /** \brief Commit or rollback the transaction based on default policy */ + ~Transaction(); + }; + private: /** The number of open "action groups"; certain post-action * operations are suppressed if this number is > 0. @@ -274,6 +310,8 @@ class APT_PUBLIC pkgDepCache : protected pkgCache::Namespace int group_level; friend class ActionGroup; + friend class Transaction; + public: int IncreaseActionGroupLevel(); int DecreaseActionGroupLevel(); -- cgit v1.2.3-70-g09d2 From 1ab3740704f8cf478d01ef95f4ef8152b79d7b87 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 18 Feb 2025 13:37:38 +0100 Subject: depcache: Add a new UpgradeCount() member --- apt-pkg/depcache.cc | 16 ++++++++++++++++ apt-pkg/depcache.h | 1 + 2 files changed, 17 insertions(+) diff --git a/apt-pkg/depcache.cc b/apt-pkg/depcache.cc index ec34d010b..f7f508f85 100644 --- a/apt-pkg/depcache.cc +++ b/apt-pkg/depcache.cc @@ -110,6 +110,7 @@ struct pkgDepCache::Private std::unique_ptr inRootSetFunc; std::unique_ptr IsAVersionedKernelPackage, IsProtectedKernelPackage; std::string machineID; + unsigned long iUpgradeCount{0}; }; pkgDepCache::pkgDepCache(pkgCache *const pCache, Policy *const Plcy) : group_level(0), Cache(pCache), PkgState(0), DepState(0), iUsrSize(0), iDownloadSize(0), iInstCount(0), iDelCount(0), iKeepCount(0), @@ -148,6 +149,7 @@ bool pkgDepCache::CheckConsistency(char const *const msgtag) /*{{{*/ auto const origUsrSize = iUsrSize; auto const origDownloadSize = iDownloadSize; auto const origInstCount = iInstCount; + auto const origUpgradeCount = d->iUpgradeCount; auto const origDelCount = iDelCount; auto const origKeepCount = iKeepCount; auto const origBrokenCount = iBrokenCount; @@ -208,6 +210,7 @@ bool pkgDepCache::CheckConsistency(char const *const msgtag) /*{{{*/ iUsrSize = origUsrSize; iDownloadSize = origDownloadSize; iInstCount = origInstCount; + d->iUpgradeCount = origUpgradeCount; iDelCount = origDelCount; iKeepCount = origKeepCount; iBrokenCount = origBrokenCount; @@ -632,7 +635,11 @@ void pkgDepCache::AddStates(const PkgIterator &Pkg, bool const Invert) else if (State.Mode == ModeKeep) iKeepCount += Add; else if (State.Mode == ModeInstall) + { iInstCount += Add; + if (Pkg->CurrentVer != 0 && State.Status > 0) + d->iUpgradeCount += Add; + } } /*}}}*/ // DepCache::BuildGroupOrs - Generate the Or group dep data /*{{{*/ @@ -762,6 +769,7 @@ void pkgDepCache::PerformDependencyPass(OpProgress * const Prog) iUsrSize = 0; iDownloadSize = 0; iInstCount = 0; + d->iUpgradeCount = 0; iDelCount = 0; iKeepCount = 0; iBrokenCount = 0; @@ -2650,6 +2658,12 @@ unsigned long long pkgDepCache::BootSize(bool initrdOnly) return std::accumulate(sizes, sizes + MAX_ARTEFACT, off_t{0}) * BootCount * 110 / 100; } /*}}}*/ +// pkgDepCache::UpgradeCount /*{{{*/ +unsigned long pkgDepCache::UpgradeCount() +{ + return d->iUpgradeCount; +} + /*}}}*/ // pkgDepCache::Transaction /*{{{*/ struct pkgDepCache::Transaction::Private { @@ -2668,6 +2682,7 @@ struct pkgDepCache::Transaction::Private signed long long iUsrSize{cache.iUsrSize}; unsigned long long iDownloadSize{cache.iDownloadSize}; unsigned long iInstCount{cache.iInstCount}; + unsigned long iUpgradeCount{cache.d->iUpgradeCount}; unsigned long iDelCount{cache.iDelCount}; unsigned long iKeepCount{cache.iKeepCount}; unsigned long iBrokenCount{cache.iBrokenCount}; @@ -2682,6 +2697,7 @@ struct pkgDepCache::Transaction::Private cache.iUsrSize = iUsrSize; cache.iDownloadSize = iDownloadSize; cache.iInstCount = iInstCount; + cache.d->iUpgradeCount = iUpgradeCount; cache.iDelCount = iDelCount; cache.iKeepCount = iKeepCount; cache.iBrokenCount = iBrokenCount; diff --git a/apt-pkg/depcache.h b/apt-pkg/depcache.h index d2a883180..bab524853 100644 --- a/apt-pkg/depcache.h +++ b/apt-pkg/depcache.h @@ -517,6 +517,7 @@ class APT_PUBLIC pkgDepCache : protected pkgCache::Namespace inline unsigned long DelCount() {return iDelCount;}; inline unsigned long KeepCount() {return iKeepCount;}; inline unsigned long InstCount() {return iInstCount;}; + unsigned long UpgradeCount(); inline unsigned long BrokenCount() {return iBrokenCount;}; inline unsigned long PolicyBrokenCount() {return iPolicyBrokenCount;}; inline unsigned long BadCount() {return iBadCount;}; -- cgit v1.2.3-70-g09d2 From 935a9ab334e55a29363f45e7de6338c47337e806 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Tue, 18 Feb 2025 13:06:32 +0100 Subject: Evaluate and fall back to the 3.0 solver Always run the 3.0 solver after the internal solver. If the internal solver failed, and the 3.0 solver did not, use the 3.0 result. If 3.0 solver failed or produced a worse result than the internal solver, write an apport crash dump. We exclude situations which we now the solver can't handle, i.e. removals are forbidden and you requested removals, and stuff like that. --- apt-private/private-install.cc | 309 +++++++++++++++------ doc/examples/configure-index | 1 + .../test-bug-745046-candidate-propagation-fails | 1 + test/integration/test-release-candidate-switching | 1 + test/integration/test-solver3-evaluation | 220 +++++++++++++++ 5 files changed, 446 insertions(+), 86 deletions(-) create mode 100755 test/integration/test-solver3-evaluation diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc index 208a27749..cb9f93e9e 100644 --- a/apt-private/private-install.cc +++ b/apt-private/private-install.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -45,7 +46,7 @@ /*}}}*/ class pkgSourceList; -bool CheckNothingBroken(CacheFile &Cache) /*{{{*/ +static bool CheckNothingBroken(std::ostream &out, CacheFile &Cache) /*{{{*/ { // Now we check the state of the packages, if (Cache->BrokenCount() == 0) @@ -55,11 +56,11 @@ bool CheckNothingBroken(CacheFile &Cache) /*{{{*/ if (_error->PendingError() && _config->Find("APT::Solver") == "dump") return false; - c1out << - _("Some packages could not be installed. This may mean that you have\n" + out << _("Some packages could not be installed. This may mean that you have\n" "requested an impossible situation or if you are using the unstable\n" "distribution that some required packages have not yet been created\n" - "or been moved out of Incoming.") << std::endl; + "or been moved out of Incoming.") + << std::endl; /* if (Packages == 1) { @@ -71,15 +72,68 @@ bool CheckNothingBroken(CacheFile &Cache) /*{{{*/ } */ - c1out << _("The following information may help to resolve the situation:") << std::endl; - c1out << std::endl; - ShowBroken(c1out,Cache,false); + out << _("The following information may help to resolve the situation:") << std::endl; + out << std::endl; + ShowBroken(out, Cache, false); if (_error->PendingError() == true) return false; else return _error->Error(_("Broken packages")); +} +bool CheckNothingBroken(CacheFile &Cache) +{ + return CheckNothingBroken(c1out, Cache); } /*}}}*/ +// WriteApportReport - Write an apport bug report /*{{{*/ +// --------------------------------------------------------------------- +static void WriteApportReport(pkgCacheFile &Cache, std::string &title, std::vector const &errors, int mode, bool distUpgrade) +{ + auto dumpfile = flCombine(_config->FindDir("Dir::Log"), "edsp.log"); + auto crashfile = flCombine(_config->FindDir("Dir::Apport", "/var/crash"), "apt-edsp." + std::to_string(getuid()) + ".crash"); + auto apt = Cache->FindPkg("apt"); + if (access(flNotFile(dumpfile).c_str(), W_OK) != 0 || access(flNotFile(crashfile).c_str(), W_OK) != 0) + return; + OpTextProgress Progress(*_config); + Progress.OverallProgress(0, 100, 50, _("Writing error report")); + FileFd edspDump(dumpfile, + FileFd::Exclusive | FileFd::Create | FileFd::WriteOnly | FileFd::BufferedWrite); + int edspFlags = 0; + if (mode || distUpgrade) + edspFlags |= EDSP::Request::UPGRADE_ALL; + if (mode & APT::Upgrade::FORBID_REMOVE_PACKAGES) + edspFlags |= EDSP::Request::FORBID_REMOVE; + if (mode & APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES) + edspFlags |= EDSP::Request::FORBID_NEW_INSTALL; + + EDSP::WriteRequest(Cache, edspDump, edspFlags, &Progress); + EDSP::WriteScenario(Cache, edspDump, &Progress); + edspDump.Close(); + + Progress.OverallProgress(50, 100, 50, _("Writing error report")); + + std::ofstream crash(crashfile, std::ios::out | std::ios::trunc); + chmod(crashfile.c_str(), 0600); + time_t now = time(NULL); + char ctime_buf[26]; // need at least 26 bytes according to ctime(3) + crash << "ProblemType: AptSolver\n" + << "Architecture: " << _config->Find("APT::Architecture") << "\n" + << "Date: " << ctime_r(&now, ctime_buf) + << "Package: apt " << (apt.end() || apt.CurrentVer().end() ? "" : apt.CurrentVer().VerStr()) << "\n" + << "Title: " << title << "\n" + << "SourcePackage: apt\n"; + + crash << "ErrorMessage:\n"; + for (auto &error : errors) + crash << " " << error << "\n"; + + crash << "AptSolverDump:\n"; + std::ifstream toCopy(edspDump.Name()); + for (std::string line; std::getline(toCopy, line);) + crash << " " << line << "\n"; + crash.close(); +} + // InstallPackages - Actually download and install the packages /*{{{*/ // --------------------------------------------------------------------- /* This displays the informative messages describing what is going to @@ -836,85 +890,168 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vectorFindB("APT::Get::AutoSolving", true) == true) - { - InstallAction.propagateReleaseCandidateSwitching(helper.selectedByRelease, c0out); - InstallAction.doAutoInstall(); - } - - if (_error->PendingError() == true) - { - return false; - } - - /* If we are in the Broken fixing mode we do not attempt to fix the - problems. This is if the user invoked install without -f and gave - packages */ - if (BrokenFix == true && Cache->BrokenCount() != 0) - { - c1out << _("You might want to run 'apt --fix-broken install' to correct these.") << std::endl; - ShowBroken(c1out,Cache,false); - return _error->Error(_("Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).")); - } - - if (Fix != NULL) - { - // Call the scored problem resolver - OpTextProgress Progress(*_config); - bool const distUpgradeMode = strcmp(CmdL.FileList[0], "dist-upgrade") == 0 || strcmp(CmdL.FileList[0], "full-upgrade") == 0; - - if (distUpgradeMode && _config->Find("Binary") == "apt") - _config->CndSet("APT::Get::AutomaticRemove::Kernels", _config->FindB("APT::Get::AutomaticRemove", true)); - - bool resolver_fail = false; - if (distUpgradeMode == true || UpgradeMode != APT::Upgrade::ALLOW_EVERYTHING) - resolver_fail = APT::Upgrade::Upgrade(Cache, UpgradeMode, &Progress); - else - resolver_fail = Fix->Resolve(true, &Progress); - - if (resolver_fail == false && Cache->BrokenCount() == 0) - return false; - } - - if (CheckNothingBroken(Cache) == false) - return false; - } - if (!DoAutomaticRemove(Cache)) - return false; - - // if nothing changed in the cache, but only the automark information - // we write the StateFile here, otherwise it will be written in - // cache.commit() - if (InstallAction.AutoMarkChanged > 0 && - Cache->DelCount() == 0 && Cache->InstCount() == 0 && - Cache->BadCount() == 0 && - _config->FindB("APT::Get::Simulate",false) == false) - Cache->writeStateFile(NULL); - - SortedPackageUniverse Universe(Cache); - for (auto const &Pkg: Universe) - if (Pkg->CurrentVer != 0 && not Cache[Pkg].Upgrade() && not Cache[Pkg].Delete() && - UpgradablePackages.find(Pkg) != UpgradablePackages.end()) - HeldBackPackages.push_back(Pkg); - - return true; + bool const distUpgradeMode = strcmp(CmdL.FileList[0], "dist-upgrade") == 0 || strcmp(CmdL.FileList[0], "full-upgrade") == 0; + + { + unsigned short const order[] = {MOD_REMOVE, MOD_INSTALL, 0}; + + for (unsigned short i = 0; order[i] != 0; ++i) + { + if (order[i] == MOD_INSTALL) + InstallAction = std::for_each(verset[MOD_INSTALL].begin(), verset[MOD_INSTALL].end(), InstallAction); + else if (order[i] == MOD_REMOVE) + RemoveAction = std::for_each(verset[MOD_REMOVE].begin(), verset[MOD_REMOVE].end(), RemoveAction); + } + + { + APT::CacheSetHelper helper; + helper.PackageFrom(APT::CacheSetHelper::PATTERN, &UpgradablePackages, Cache, "?upgradable"); + } + + // Record the state before we call the solver. + pkgDepCache::Transaction transaction(Cache, pkgDepCache::Transaction::Behavior::COMMIT); + auto runTheSolver = [&](std::ostream &out) -> bool + { + if (Fix != NULL && _config->FindB("APT::Get::AutoSolving", true) == true) + { + InstallAction.propagateReleaseCandidateSwitching(helper.selectedByRelease, c0out); + InstallAction.doAutoInstall(); + } + + if (_error->PendingError() == true) + { + return false; + } + + /* If we are in the Broken fixing mode we do not attempt to fix the + problems. This is if the user invoked install without -f and gave + packages */ + if (BrokenFix == true && Cache->BrokenCount() != 0) + { + out << _("You might want to run 'apt --fix-broken install' to correct these.") << std::endl; + ShowBroken(out, Cache, false); + return _error->Error(_("Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).")); + } + + if (Fix != NULL) + { + // Call the scored problem resolver + OpTextProgress Progress(*_config); + + if (distUpgradeMode && _config->Find("Binary") == "apt") + _config->CndSet("APT::Get::AutomaticRemove::Kernels", _config->FindB("APT::Get::AutomaticRemove", true)); + + bool resolver_fail = false; + if (distUpgradeMode == true || UpgradeMode != APT::Upgrade::ALLOW_EVERYTHING) + resolver_fail = APT::Upgrade::Upgrade(Cache, UpgradeMode, &Progress); + else + resolver_fail = Fix->Resolve(true, &Progress); + + if (resolver_fail == false && Cache->BrokenCount() == 0) + return false; + } + + if (CheckNothingBroken(out, Cache) == false) + return false; + + return true; + }; + + std::ostringstream out; + _error->PushToStack(); + auto res = runTheSolver(out); + + // 3.0 solver is a bit more strict and applies the rules to user provided arguments as well, so if you provide + // a removal request, but don't allow removals, it fails; or if you install something conflicting with an installed + // package during an `upgrade` run, that's the same problem. So if an upgrade restriction is set and we have + // arguments specified, don't try solver3. + auto invalidCombinations = UpgradeMode && (not verset[MOD_REMOVE].empty() || not verset[MOD_INSTALL].empty()); + // Fallback to 3.0 if we don't get a result or apport is installed + if (not invalidCombinations && _config->Find("APT::Solver", "internal") == "internal" && (not res || (not Cache->FindPkg("apport").end() && Cache->FindPkg("apport")->CurrentVer))) + { + auto internalBroken = Cache->BrokenCount(); + auto internalDel = Cache->DelCount(); + auto internalInst = Cache->InstCount(); + auto internalUpgrade = Cache->UpgradeCount(); + auto internalKeep = Cache->KeepCount(); + + // Create a nested transaction. When leaving this scope, we are back to the 'internal' result. + pkgDepCache::Transaction solver3(Cache, pkgDepCache::Transaction::Behavior::ROLLBACK); + transaction.temporaryRollback(); // Rollback to before internal solver made changes + + std::vector errors; + _error->PushToStack(); + _config->Set("APT::Solver", "3.0"); + _config->CndSet("APT::Solver::RemoveManual", "true"); // otherwise we are always "worse" + std::ofstream solver3out; // empty out stream... + bool solver3Res = runTheSolver(solver3out); + + std::string str; + while (not _error->empty()) + if (_error->PopMessage(str)) + errors.push_back(str); + _config->Set("APT::Solver", "internal"); + _error->RevertToStack(); + // An error summary for apport. + std::string error; + if (solver3Res && Cache->BrokenCount()) + { + error = "Internal error: The 3.0 solver produced a broken cache"; + } + else if (not solver3Res || Cache->BrokenCount() || not errors.empty()) + { + if (res && not internalBroken) + error = "Failure: The 3.0 solver did not find a result"; + } + else if (not res) + { + // Commit the changes from the 3.0 solver. This in turn commits the 3.0 transaction into the internal + // transaction which sounds a bit awkward, but that's the way it goes: commit() is just delete the + // transaction stage so it can't rollback. + _error->Discard(); + _error->Audit(_("Result calculated by the 3.0 solver.")); + solver3.commit(); + res = true; + } + else if (std::make_tuple(Cache->DelCount(), -Cache->UpgradeCount(), Cache->KeepCount(), Cache->InstCount()) > std::make_tuple(internalDel, -internalUpgrade, internalKeep, internalInst)) + { + error = "Failure: The 3.0 solver produced a worse result"; + } + + if (res && not error.empty() && not Cache->FindPkg("apport").end() && Cache->FindPkg("apport")->CurrentVer) + { + pkgDepCache::Transaction dumpTransaction(Cache, pkgDepCache::Transaction::Behavior::ROLLBACK); + transaction.temporaryRollback(); // Rollback to before internal solver made changes so we can dump the OG request + WriteApportReport(Cache, error, errors, UpgradeMode, distUpgradeMode); + } + } + // If the 3.0 solver could not recover either, abort. + _error->MergeWithStack(); + if (not res) + { + c1out << out.str() << std::flush; + return false; + } + } + if (!DoAutomaticRemove(Cache)) + return false; + + // if nothing changed in the cache, but only the automark information + // we write the StateFile here, otherwise it will be written in + // cache.commit() + if (InstallAction.AutoMarkChanged > 0 && + Cache->DelCount() == 0 && Cache->InstCount() == 0 && + Cache->BadCount() == 0 && + _config->FindB("APT::Get::Simulate", false) == false) + Cache->writeStateFile(NULL); + + SortedPackageUniverse Universe(Cache); + for (auto const &Pkg : Universe) + if (Pkg->CurrentVer != 0 && not Cache[Pkg].Upgrade() && not Cache[Pkg].Delete() && + UpgradablePackages.find(Pkg) != UpgradablePackages.end()) + HeldBackPackages.push_back(Pkg); + + return true; } /*}}}*/ bool AddVolatileSourceFile(pkgSourceList *const SL, PseudoPkg &&pkg, std::vector &VolatileCmdL)/*{{{*/ diff --git a/doc/examples/configure-index b/doc/examples/configure-index index 8476d733a..d6d76930c 100644 --- a/doc/examples/configure-index +++ b/doc/examples/configure-index @@ -464,6 +464,7 @@ Dir "" Boot ""; Usr ""; + Apport ""; }; // Things that effect the APT dselect method diff --git a/test/integration/test-bug-745046-candidate-propagation-fails b/test/integration/test-bug-745046-candidate-propagation-fails index 8475a4ea1..f4fc15e0e 100755 --- a/test/integration/test-bug-745046-candidate-propagation-fails +++ b/test/integration/test-bug-745046-candidate-propagation-fails @@ -17,6 +17,7 @@ setupaptarchive testfailureequal "Reading package lists... Building dependency tree... Selected version '2' (experimental [amd64]) for 'gedit' +Selected version '2' (experimental [amd64]) for 'gedit' Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstable distribution that some required packages have not yet been created diff --git a/test/integration/test-release-candidate-switching b/test/integration/test-release-candidate-switching index be5b3f997..52d3e5a12 100755 --- a/test/integration/test-release-candidate-switching +++ b/test/integration/test-release-candidate-switching @@ -424,6 +424,7 @@ E: Trivial Only specified but this is not a trivial operation." aptget install a testfailureequal "Reading package lists... Building dependency tree... Selected version '1.0' (experimental [all]) for 'uninstallablepkg' +Selected version '1.0' (experimental [all]) for 'uninstallablepkg' Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstable distribution that some required packages have not yet been created diff --git a/test/integration/test-solver3-evaluation b/test/integration/test-solver3-evaluation new file mode 100755 index 000000000..d3521e9c6 --- /dev/null +++ b/test/integration/test-solver3-evaluation @@ -0,0 +1,220 @@ +#!/bin/sh +set -e + +TESTDIR="$(readlink -f "$(dirname "$0")")" +. "$TESTDIR/framework" +setupenvironment +configarchitecture 'amd64' + +insertinstalledpackage 'apport' 'all' '3' + +insertpackage 'unstable' 'x' 'all' '3' 'Depends: a | b' +insertpackage 'unstable' 'y' 'all' '3' 'Depends: b | a' +insertpackage 'unstable' 'a' 'all' '3' 'Depends: c' +insertpackage 'unstable' 'b' 'all' '3' +insertpackage 'unstable' 'c' 'all' '3' + +setupaptarchive + +mkdir rootdir/var/log/apt + +testsuccess aptget install y x --solver internal -s -o Dir::Apport=var/crash + +testsuccess sed -i "s/^Date:.*/Date: Sun Mar 9 00:08:41 2025/" rootdir/var/crash/apt-edsp.$(id -u).crash + +testsuccessequal "ProblemType: AptSolver +Architecture: amd64 +Date: Sun Mar 9 00:08:41 2025 +Package: apt +Title: Failure: The 3.0 solver produced a worse result +SourcePackage: apt +ErrorMessage: +AptSolverDump: + Request: EDSP 0.5 + Architecture: amd64 + Architectures: amd64 + Machine-ID: 912e43bd1c1d4ba481f9f8ccab25f9ee + Install: x:amd64 y:amd64 + Solver: internal + + Package: a + Architecture: all + Version: 3 + APT-ID: 2 + Source: a + Source-Version: 3 + Priority: optional + Section: other + Size: 42 + APT-Release: + a=unstable,n=sid,c=main,b=all + APT-Pin: 500 + APT-Candidate: yes + Depends: c + + Package: b + Architecture: all + Version: 3 + APT-ID: 3 + Source: b + Source-Version: 3 + Priority: optional + Section: other + Size: 42 + APT-Release: + a=unstable,n=sid,c=main,b=all + APT-Pin: 500 + APT-Candidate: yes + + Package: c + Architecture: all + Version: 3 + APT-ID: 4 + Source: c + Source-Version: 3 + Priority: optional + Section: other + Size: 42 + APT-Release: + a=unstable,n=sid,c=main,b=all + APT-Pin: 500 + APT-Candidate: yes + + Package: x + Architecture: all + Version: 3 + APT-ID: 0 + Source: x + Source-Version: 3 + Priority: optional + Section: other + Size: 42 + APT-Release: + a=unstable,n=sid,c=main,b=all + APT-Pin: 500 + APT-Candidate: yes + Depends: a | b + + Package: y + Architecture: all + Version: 3 + APT-ID: 1 + Source: y + Source-Version: 3 + Priority: optional + Section: other + Size: 42 + APT-Release: + a=unstable,n=sid,c=main,b=all + APT-Pin: 500 + APT-Candidate: yes + Depends: b | a + + Package: apport + Architecture: all + Version: 3 + APT-ID: 5 + Source: apport + Source-Version: 3 + Priority: optional + Section: other + Installed: yes + APT-Pin: 100 + APT-Candidate: yes + " cat rootdir/var/crash/apt-edsp.$(id -u).crash + + +testsuccessequal "Request: EDSP 0.5 +Architecture: amd64 +Architectures: amd64 +Machine-ID: 912e43bd1c1d4ba481f9f8ccab25f9ee +Install: x:amd64 y:amd64 +Solver: internal + +Package: a +Architecture: all +Version: 3 +APT-ID: 2 +Source: a +Source-Version: 3 +Priority: optional +Section: other +Size: 42 +APT-Release: + a=unstable,n=sid,c=main,b=all +APT-Pin: 500 +APT-Candidate: yes +Depends: c + +Package: b +Architecture: all +Version: 3 +APT-ID: 3 +Source: b +Source-Version: 3 +Priority: optional +Section: other +Size: 42 +APT-Release: + a=unstable,n=sid,c=main,b=all +APT-Pin: 500 +APT-Candidate: yes + +Package: c +Architecture: all +Version: 3 +APT-ID: 4 +Source: c +Source-Version: 3 +Priority: optional +Section: other +Size: 42 +APT-Release: + a=unstable,n=sid,c=main,b=all +APT-Pin: 500 +APT-Candidate: yes + +Package: x +Architecture: all +Version: 3 +APT-ID: 0 +Source: x +Source-Version: 3 +Priority: optional +Section: other +Size: 42 +APT-Release: + a=unstable,n=sid,c=main,b=all +APT-Pin: 500 +APT-Candidate: yes +Depends: a | b + +Package: y +Architecture: all +Version: 3 +APT-ID: 1 +Source: y +Source-Version: 3 +Priority: optional +Section: other +Size: 42 +APT-Release: + a=unstable,n=sid,c=main,b=all +APT-Pin: 500 +APT-Candidate: yes +Depends: b | a + +Package: apport +Architecture: all +Version: 3 +APT-ID: 5 +Source: apport +Source-Version: 3 +Priority: optional +Section: other +Installed: yes +APT-Pin: 100 +APT-Candidate: yes +" cat rootdir/var/log/apt/edsp.log + + -- cgit v1.2.3-70-g09d2 From 6c112f9860c1730237029f4e23d40bf658d73704 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sun, 9 Mar 2025 13:55:23 +0100 Subject: Print --solver 3.0 explanation if both internal and it failed This can provide useful additional context. To avoid updating the whole test suite for it, introduce a new option `quiet::NoSolver3Explanation` and set it by default. All the other tests that assert output will already have matching tests for --solver 3.0 with the correct messages asserted, it makes no sense to duplicate them. --- apt-private/private-install.cc | 3 +++ doc/examples/configure-index | 1 + test/integration/framework | 2 ++ test/integration/test-solver3-evaluation | 21 ++++++++++++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/apt-private/private-install.cc b/apt-private/private-install.cc index cb9f93e9e..29e23a57e 100644 --- a/apt-private/private-install.cc +++ b/apt-private/private-install.cc @@ -1024,6 +1024,9 @@ bool DoCacheManipulationFromCommandLine(CommandLine &CmdL, std::vectorFindB("quiet::NoSolver3Explanation", false)) + _error->Error("The following information from --solver 3.0 may provide additional context:\n%s", APT::String::Join(errors, "\n").c_str()); } // If the 3.0 solver could not recover either, abort. _error->MergeWithStack(); diff --git a/doc/examples/configure-index b/doc/examples/configure-index index d6d76930c..c36f922b6 100644 --- a/doc/examples/configure-index +++ b/doc/examples/configure-index @@ -33,6 +33,7 @@ quiet "" { NoUpdate ""; // never update progress information - included in -q=1 NoProgress ""; // disables the 0% → 100% progress on cache generation and stuff NoStatistic ""; // no "42 kB downloaded" stats in update + NoSolver3Explanation ""; // do not print solver3 explanations when used with internal solver ReleaseInfoChange "" // don't even print the notices if the info change is allowed { Origin ""; diff --git a/test/integration/framework b/test/integration/framework index d2570b6b2..3f0290569 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -589,6 +589,8 @@ EOF echo 'DPkg::Path "";' >> aptconfig.conf echo 'Dir::Bin::ischroot "/bin/false";' >> aptconfig.conf + echo 'quiet::NoSolver3Explanation "true";' > rootdir/etc/apt/apt.conf.d/disable-solver3-context + # most tests just need one signed Release file, not both export APT_DONT_SIGN='Release.gpg' diff --git a/test/integration/test-solver3-evaluation b/test/integration/test-solver3-evaluation index d3521e9c6..af12698b5 100755 --- a/test/integration/test-solver3-evaluation +++ b/test/integration/test-solver3-evaluation @@ -16,6 +16,7 @@ insertpackage 'unstable' 'c' 'all' '3' setupaptarchive +rm rootdir/etc/apt/apt.conf.d/disable-solver3-context mkdir rootdir/var/log/apt testsuccess aptget install y x --solver internal -s -o Dir::Apport=var/crash @@ -217,4 +218,22 @@ APT-Pin: 100 APT-Candidate: yes " cat rootdir/var/log/apt/edsp.log - +testfailureequal "Reading package lists... +Building dependency tree... +Package 'c' is not installed, so not removed +Solving dependencies... +Some packages could not be installed. This may mean that you have +requested an impossible situation or if you are using the unstable +distribution that some required packages have not yet been created +or been moved out of Incoming. +The following information may help to resolve the situation: + +The following packages have unmet dependencies: + a : Depends: c but it is not going to be installed +E: Unable to correct problems, you have held broken packages. +E: The following information from --solver 3.0 may provide additional context: + Unable to satisfy dependencies. Reached two conflicting decisions: + 1. a:amd64=3 is selected for install + 2. a:amd64 Depends c + but none of the choices are installable: + - c:amd64 is not selected for install" apt install -s a c- --solver internal -- cgit v1.2.3-70-g09d2