apt: apt-pkg/solver3.h Source File

apt 3.3.2 commandline package manager

solver3.h
1/*
2 * solver3.h - The APT 3.0 solver
3 *
4 * Copyright (c) 2023 Julian Andres Klode
5 * Copyright (c) 2023 Canonical Ltd
6 *
7 * SPDX-License-Identifier: GPL-2.0+
8 */
9
10#include <cassert>
11#include <memory>
12#include <optional>
13#include <queue>
14#include <type_traits>
15#include <vector>
16
17#include <apt-pkg/configuration.h>
18#include <apt-pkg/depcache.h>
19#include <apt-pkg/edsp.h>
20#include <apt-pkg/pkgcache.h>
21#include <apt-pkg/policy.h>
22
23 template <typename T> struct always_false : std::false_type {};
24
25namespace APT::Solver
26{
27
34template <typename K, typename V, bool fast = false>
35 class ContiguousCacheMap
36{
37 V *data_; // Avoid std::unique_ptr() as it may check that it's non-null.
38
39 public:
40 ContiguousCacheMap(pkgCache &cache)
41 {
42 static_assert(std::is_constructible_v<V>);
43 if constexpr (fast)
44 {
45 static_assert(std::is_trivially_constructible_v<V>);
46 static_assert(std::is_trivially_destructible_v<V>);
47 }
48
49 size_t size;
50 if constexpr (std::is_same_v<K, pkgCache::Version>)
51 size = cache.Head().VersionCount;
52 else if constexpr (std::is_same_v<K, pkgCache::Package>)
53 size = cache.Head().PackageCount;
54 else
55 static_assert(always_false<K>::value, "Cannot construct map for key type");
56
57 data_ = new V[size]{};
58 }
59 constexpr V &operator[](const K *key) noexcept { return data_[key->ID]; }
60 constexpr const V &operator[](const K *key) const noexcept { return data_[key->ID]; }
61 ~ContiguousCacheMap() { delete[] data_; }
62
63 // Delete copy constructors for memory safety (rule of 3)
64 ContiguousCacheMap(const ContiguousCacheMap &) = delete;
65 ContiguousCacheMap &operator=(const ContiguousCacheMap &) = delete;
66};
67
71template <typename K, typename V>
72using FastContiguousCacheMap = ContiguousCacheMap<K, V, true>;
73
74struct Lit;
75
76// \brief Groups of works, these are ordered.
77//
78// Later items will be skipped if they are optional, or we will when backtracking,
79// try a different choice for them.
80enum class Group : uint8_t
81{
82 HoldOrDelete,
83
84 // Satisfying dependencies on entirely new packages first is a good idea because
85 // it may contain replacement packages like libfoo1t64 whereas we later will see
86 // Depends: libfoo1 where libfoo1t64 Provides libfoo1 and we'd have to choose.
87 SatisfyNew,
88 Satisfy,
89 // On a similar note as for SatisfyNew, if the dependency contains obsolete packages
90 // try it last.
91 SatisfyObsolete,
92
93 // Select a version of a package chosen for install.
94 SelectVersion,
95
96 // My intuition tells me that we should try to schedule upgrades first, then
97 // any non-obsolete installed packages, and only finally obsolete ones, such
98 // that newer packages guide resolution of dependencies for older ones, they
99 // may have more stringent dependencies, like a (>> 2) whereas an obsolete
100 // package may have a (>> 1), for example.
101 UpgradeManual,
102 InstallManual,
103 ObsoleteManual,
104
105 // Satisfy optional dependencies that were previously satisfied but won't otherwise be installed
106 SatisfySuggests,
107
108 // Automatically installed packages must come last in the group, this allows
109 // us to see if they were installed as a dependency of a manually installed package,
110 // allowing a simple implementation of an autoremoval code.
111 UpgradeAuto,
112 KeepAuto,
113 ObsoleteAuto,
114};
115
116// \brief This essentially describes the install state in RFC2119 terms.
117enum class LiftedBool : uint8_t
118{
119 // \brief We have not made a choice about the package yet
120 Undefined,
121 // \brief We need to install this package
122 True,
123 // \brief We cannot install this package (need conflicts with it)
124 False,
125};
126
135 struct Var
136{
137 uint32_t value;
138
139 explicit constexpr Var(uint32_t value = 0) noexcept : value{value} {}
140 explicit Var(pkgCache::PkgIterator const &Pkg) noexcept : value(uint32_t(Pkg.MapPointer()) << 1) {}
141 explicit Var(pkgCache::VerIterator const &Ver) noexcept : value(uint32_t(Ver.MapPointer()) << 1 | 1) {}
142
143 constexpr bool isVersion() const noexcept { return value & 1; }
144 constexpr uint32_t mapPtr() const noexcept { return value >> 1; }
145
146 // \brief Return the package, if any, otherwise 0.
147 constexpr map_pointer<pkgCache::Package> Pkg() const noexcept
148 {
149 return isVersion() ? 0 : map_pointer<pkgCache::Package>{mapPtr()};
150 }
151 // \brief Return the version, if any, otherwise 0.
152 constexpr map_pointer<pkgCache::Version> Ver() const noexcept
153 {
154 return isVersion() ? map_pointer<pkgCache::Version>{mapPtr()} : 0;
155 }
156 // \brief Return the package iterator if storing a package, or an empty one
157 constexpr pkgCache::PkgIterator Pkg(pkgCache &cache) const noexcept
158 {
159 return isVersion() ? pkgCache::PkgIterator() : pkgCache::PkgIterator(cache, cache.PkgP + Pkg());
160 }
161 // \brief Return the version iterator if storing a package, or an empty end.
162 constexpr pkgCache::VerIterator Ver(pkgCache &cache) const noexcept
163 {
164 return isVersion() ? pkgCache::VerIterator(cache, cache.VerP + Ver()) : pkgCache::VerIterator();
165 }
166 // \brief Return a package, cast from version if needed
167 constexpr pkgCache::PkgIterator CastPkg(pkgCache &cache) const noexcept
168 {
169 return isVersion() ? Ver(cache).ParentPkg() : Pkg(cache);
170 }
171 // \brief Check if there is no reason.
172 constexpr bool empty() const noexcept { return value == 0; }
173 constexpr bool operator!=(Var const other) const noexcept { return value != other.value; }
174 constexpr bool operator==(Var const other) const noexcept { return value == other.value; }
175
177 constexpr Lit operator~() const noexcept;
178
179 std::string toString(pkgCache &cache) const
180 {
181 if (auto P = Pkg(cache); not P.end())
182 return P.FullName();
183 if (auto V = Ver(cache); not V.end())
184 return V.ParentPkg().FullName() + "=" + V.VerStr();
185 return "(root)";
186 }
187};
188
194 struct Lit
195{
196 private:
197 friend struct std::hash<Lit>;
198 // Private constructor from a number, to be used with operator~
199 explicit constexpr Lit(int32_t value) noexcept : value{value} {}
200 int32_t value;
201
202 public:
203 constexpr Lit() noexcept : value{0} {}
204 // SAFETY: value must be 31 bit, one bit is needed for the sign.
205 constexpr Lit(Var var) noexcept : value{static_cast<int32_t>(var.value)} {}
206
207 // Accessors
208 constexpr Var var() const noexcept { return Var(std::abs(value)); }
209 constexpr bool sign() const noexcept { return value < 0; }
210 constexpr Lit operator~() const noexcept { return Lit(-value); }
211
212 // Properties
213 constexpr bool empty() const noexcept { return value == 0; }
214 constexpr bool operator!=(Lit const other) const noexcept { return value != other.value; }
215 constexpr bool operator==(Lit const other) const noexcept { return value == other.value; }
216
217 std::string toString(pkgCache &cache) const { return (sign() ? "not " : "") + var().toString(cache); }
218};
219
226 struct Clause
227{
228 // \brief Underyling dependency
229 pkgCache::Dependency *dep = nullptr;
230 // \brief Var for the work
231 Var reason;
232 // \brief The group we are in
233 Group group;
234 // \brief Possible solutions to this task, ordered in order of preference.
235 std::vector<Var> solutions{};
236 // \brief An optional clause does not need to be satisfied
237 bool optional;
238
239 // \brief A negative clause negates the solutions, that is X->A|B you get X->!(A|B), aka X->!A&!B
240 bool negative;
241
242 // \brief An optional clause may be eager
243 bool eager;
244
245 // Clauses merged with this clause
246 std::forward_list<Clause> merged;
247
248 inline Clause(Var reason, Group group, bool optional = false, bool negative = false) : reason(reason), group(group), optional(optional), negative(negative), eager(not optional) {}
249
250 std::string toString(pkgCache &cache, bool pretty = false, bool showMerged = true) const;
251};
252
253 constexpr Lit Solver::Var::operator~() const noexcept
254{
255 return ~Lit(*this);
256}
257
258constexpr LiftedBool operator~(LiftedBool value) noexcept
259{
260 switch (value)
261 {
262 case LiftedBool::Undefined:
263 return LiftedBool::Undefined;
264 case LiftedBool::True:
265 return LiftedBool::False;
266 case LiftedBool::False:
267 return LiftedBool::True;
268 }
269 abort();
270}
271
272/*
273 * \brief APT 3.0 solver
274 *
275 * This is a simple solver focused on understandability and sensible results, it
276 * will not generally find all solutions to the problem but will try to find the best
277 * ones.
278 *
279 * It is a brute force solver with heuristics, conflicts learning, and 2**32 levels
280 * of backtracking.
281 */
282 class Solver
283{
284 protected:
285 struct State;
286 struct Work;
287 struct Trail;
288
289 // \brief Type to record decision level at. This may very well be a 16-bit
290 // unsigned integer, then change Solver::State::LiftedBool to be a
291 // uint16_t class enum as well to get a more compact space.
292 using level_type = unsigned int;
293
294 // Documentation
295 template <typename T>
296 using heap = std::vector<T>;
297
298 static_assert(sizeof(level_type) >= sizeof(map_id_t));
299
300 // Cache is needed to construct Iterators from Version objects we see
301 pkgCache &cache;
302 // Root state
303 std::unique_ptr<State> rootState;
304 // States for packages
305 ContiguousCacheMap<pkgCache::Package, State> pkgStates;
306 // States for versions
307 ContiguousCacheMap<pkgCache::Version, State> verStates;
308
309 // \brief Helper function for safe access to package state.
310 constexpr State &operator[](const pkgCache::Package *P) noexcept
311 {
312 return pkgStates[P];
313 }
314 constexpr const State &operator[](const pkgCache::Package *P) const noexcept
315 {
316 return pkgStates[P];
317 }
318
319 // \brief Helper function for safe access to version state.
320 constexpr State &operator[](const pkgCache::Version *V) noexcept
321 {
322 return verStates[V];
323 }
324 constexpr const State &operator[](const pkgCache::Version *V) const noexcept
325 {
326 return verStates[V];
327 }
328 // \brief Helper function for safe access to either state.
329 constexpr State &operator[](Var r) noexcept;
330 constexpr const State &operator[](Var r) const noexcept;
331
332 constexpr std::vector<const Clause *> &watches(Lit lit) noexcept;
333
334 // \brief Heap of the remaining work.
335 //
336 // In contrast to MiniSAT which picks undecided literals and decides them,
337 // we keep track of unsolved active clauses in a priority queue. This allows
338 // us to for example, solve Depends before Recommends (see Group).
339 heap<Work> work;
340
345 std::vector<Trail> trail;
346
348 std::vector<level_type> trailLim{};
349
350 // \brief Propagation queue
351 std::queue<Var> propQ;
352
353 // \brief The time we called Solve()
354 time_t startTime{};
355
357 std::string version{_config->Find("APT::Solver", "3.0")};
358 // \brief Debug level
359 int debug{_config->FindI("Debug::APT::Solver")};
360 // \brief If set, we use strict pinning.
361 int Timeout{_config->FindI("APT::Solver::Timeout", 10)};
362
363 // \brief Discover a variable, translating the underlying dependencies to the SAT presentation
364 //
365 // This does a breadth-first search of the entire dependency tree of var,
366 // utilizing the discoverQ above.
367 virtual void Discover(Var var) = 0;
368 // \brief Propagate all pending propagations
369 [[nodiscard]] bool Propagate();
370 // \brief Propagate all pending propagations
371 [[nodiscard]] bool Propagate(const Clause *clause, Lit lit);
372
373 // \brief Return the current level (.size() with casting)
374 level_type decisionLevel()
375 {
376 return static_cast<level_type>(trailLim.size());
377 }
378 constexpr Var bestReason(Clause const *clause, Var var) const noexcept;
379 constexpr LiftedBool value(Lit lit) const noexcept;
380
381 public:
382 // \brief Revert to the previous decision level.
383 [[nodiscard]] bool Pop();
384 // \brief Undo a single assignment / trail work item
385 void UndoOne();
386 // \brief Add work to our work queue.
387 [[nodiscard]] bool AddWork(Work &&work);
388
389 // \brief Basic solver initializer. This cannot fail.
390 Solver(pkgCache &Cache);
391 virtual ~Solver();
392
393 // Assume a literal
394 [[nodiscard]] bool Assume(Lit lit, const Clause *reason = nullptr);
395 // Enqueue a fact
396 [[nodiscard]] bool Enqueue(Lit lit, const Clause *reason = nullptr);
397
398 // \brief Solve the dependencies
399 [[nodiscard]] bool Solve();
400
401 // Print dependency chain
402 virtual std::string WhyStr(Var reason) const;
403
415 virtual std::string LongWhyStr(Var var, bool assignment, const Clause *rclause, std::string prefix, std::unordered_set<Var> &seen) const;
416};
417
418/*
419 * \brief APT 3.0 solver
420 *
421 * This is a simple solver focused on understandability and sensible results, it
422 * will not generally find all solutions to the problem but will try to find the best
423 * ones.
424 *
425 * It is a brute force solver with heuristics, conflicts learning, and 2**32 levels
426 * of backtracking.
427 */
428 class DependencySolver final : public Solver
429{
430 friend class CompareProviders3;
431
432 // Policy is needed for determining candidate version.
433 pkgDepCache::Policy &policy;
434 // Request flags determine the behavior of the options below, make sure it comes first.
435 EDSP::Request::Flags requestFlags;
436
437 // Configuration options for the dependency solver
438 bool KeepAuto{version == "3.0" || not _config->FindB("APT::Get::AutomaticRemove")};
439 bool IsUpgrade{_config->FindB("APT::Solver::Upgrade", requestFlags &EDSP::Request::UPGRADE_ALL)};
440 bool AllowRemove{_config->FindB("APT::Solver::Remove", not(requestFlags & EDSP::Request::FORBID_REMOVE))};
441 bool AllowRemoveManual{AllowRemove && _config->FindB("APT::Solver::RemoveManual", true)};
442 bool AllowInstall{_config->FindB("APT::Solver::Install", not(requestFlags & EDSP::Request::FORBID_NEW_INSTALL))};
443 bool StrictPinning{_config->FindB("APT::Solver::Strict-Pinning", true)};
444 bool FixPolicyBroken{_config->FindB("APT::Get::Fix-Policy-Broken")};
445 bool DeferVersionSelection{_config->FindB("APT::Solver::Defer-Version-Selection", true)};
446 bool KeepRecommends{_config->FindB("APT::AutoRemove::RecommendsImportant", true)};
447 bool KeepSuggests{_config->FindB("APT::AutoRemove::SuggestsImportant", true)};
448 bool UpgradeBySourcePackage{_config->FindB("APT::Get::Upgrade-By-Source-Package", true)};
449
450 // Helper functions for detecting obsolete packages
451 mutable FastContiguousCacheMap<pkgCache::Package, LiftedBool> pkgObsolete;
452 bool Obsolete(pkgCache::PkgIterator pkg, bool AllowManual = false) const;
453 bool ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) const;
454
455 // GetPriority() with caching
456 mutable FastContiguousCacheMap<pkgCache::Version, short> priorities;
457 short GetPriority(pkgCache::VerIterator ver) const
458 {
459 if (priorities[ver] == 0)
460 priorities[ver] = policy.GetPriority(ver);
461 return priorities[ver];
462 }
463
464 // GetCandidateVer() with caching
465 mutable FastContiguousCacheMap<pkgCache::Package, pkgCache::Version *> candidates;
466 pkgCache::VerIterator GetCandidateVer(pkgCache::PkgIterator pkg) const
467 {
468 if (candidates[pkg] == 0)
469 candidates[pkg] = policy.GetCandidateVer(pkg);
470 return pkgCache::VerIterator(cache, candidates[pkg]);
471 }
472
473 // \brief Discover variables
474 std::queue<Var> discoverQ;
476 void Discover(Var var) override;
478 const Clause *RegisterClause(Clause &&clause);
480 void RegisterCommonDependencies(pkgCache::PkgIterator Pkg);
481
483 [[nodiscard]] Clause TranslateOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason);
484
485 public:
486 // \brief Basic solver initializer. This cannot fail.
487 DependencySolver(pkgCache &Cache, pkgDepCache::Policy &Policy, EDSP::Request::Flags requestFlags);
488 ~DependencySolver() override;
489
491 [[nodiscard]] bool FromDepCache(pkgDepCache &depcache);
493 [[nodiscard]] bool ToDepCache(pkgDepCache &depcache) const;
495 APT_PUBLIC static std::string InternalCliWhy(pkgDepCache &depcache, pkgCache::PkgIterator Pkg, bool assignment);
496};
497}; // namespace APT::Solver
508 struct APT::Solver::Solver::Work
509{
510 const Clause *clause;
511
512 // \brief The level at which the item has been added
513 level_type level;
514
516 size_t size{0};
517
518 constexpr bool operator<(APT::Solver::Solver::Work const &b) const noexcept;
519 std::string toString(pkgCache &cache) const;
520 constexpr Work(const Clause *clause, level_type level) noexcept : clause(clause), level(level) {}
521};
522
529 struct APT::Solver::Solver::State
530{
531 // \brief The reason for causing this state (invalid for Undefined).
532 //
533 // Rejects may have been caused by a later state. Consider we select
534 // between x1 and x2 in level = N. If we now find dependencies of x1
535 // leading to a conflict with a package in K < N, we will record all
536 // of them as REJECT in level = K.
537 //
538 // You can follow the reason chain upwards as long as the level
539 // doesn't increase to unwind.
540 //
541 // Vars < 0 are package ID, reasons > 0 are version IDs.
542 const Clause *reason{};
543
544 const char *reasonStr{};
545
546 // \brief The level at which the value has been assigned
547 level_type level{0};
548
549 LiftedBool assignment{LiftedBool::Undefined};
550
551 // \brief Flags.
552 struct
553 {
554 bool discovered{};
555 bool manual{};
556 } flags;
557
558 static_assert(sizeof(flags) <= sizeof(int));
559
560 // \brief Clauses owned by this package/version
561 std::vector<std::unique_ptr<Clause>> clauses;
562 // \brief Watches watching a clause by sign
563 std::vector<const Clause *> watches[2];
564};
565
578 struct APT::Solver::Solver::Trail
579{
581 Var assigned;
583 std::optional<Work> work;
584};
585
586constexpr APT::Solver::Solver::State &APT::Solver::Solver::operator[](APT::Solver::Var r) noexcept
587{
588 if (auto P = r.Pkg())
589 return (*this)[cache.PkgP + P];
590 if (auto V = r.Ver())
591 return (*this)[cache.VerP + V];
592 return *rootState.get();
593}
594
595constexpr const APT::Solver::Solver::State &APT::Solver::Solver::operator[](APT::Solver::Var r) const noexcept
596{
597 return const_cast<Solver &>(*this)[r];
598}
599
600// Custom specialization of std::hash can be injected in namespace std.
601template <>
602 struct std::hash<APT::Solver::Var>
603{
604 std::hash<decltype(APT::Solver::Var::value)> hash_value;
605 std::size_t operator()(const APT::Solver::Var &v) const noexcept { return hash_value(v.value); }
606};
607
608// Custom specialization of std::hash can be injected in namespace std.
609template <>
610 struct std::hash<APT::Solver::Lit>
611{
612 std::hash<decltype(APT::Solver::Lit::value)> hash_value;
613 std::size_t operator()(const APT::Solver::Lit &v) const noexcept { return hash_value(v.value); }
614};
615
616constexpr std::vector<const APT::Solver::Clause *> &APT::Solver::Solver::watches(Lit lit) noexcept
617{
618 return (*this)[lit.var()].watches[lit.sign()];
619}
APT::Solver::ContiguousCacheMap
A simple mapping from objects in the cache to user-defined types.
Definition solver3.h:36
APT::Solver::DependencySolver::InternalCliWhy
static APT_PUBLIC std::string InternalCliWhy(pkgDepCache &depcache, pkgCache::PkgIterator Pkg, bool assignment)
Temporary internal API with external linkage for the apt why and apt why-not commands.
Definition solver3.cc:1383
APT::Solver::DependencySolver::FromDepCache
bool FromDepCache(pkgDepCache &depcache)
Apply the selections from the dep cache to the solver.
Definition solver3.cc:1186
APT::Solver::DependencySolver::DependencySolver
DependencySolver(pkgCache &Cache, pkgDepCache::Policy &Policy, EDSP::Request::Flags requestFlags)
Definition solver3.cc:762
APT::Solver::DependencySolver::ToDepCache
bool ToDepCache(pkgDepCache &depcache) const
Apply the solver result to the depCache.
Definition solver3.cc:1317
APT::Solver::Solver
Definition solver3.h:283
APT::Solver::Solver::LongWhyStr
virtual std::string LongWhyStr(Var var, bool assignment, const Clause *rclause, std::string prefix, std::unordered_set< Var > &seen) const
Print a long reason string.
Definition solver3.cc:172
APT::Solver::Solver::UndoOne
void UndoOne()
Definition solver3.cc:445
APT::Solver::Solver::version
std::string version
Various configuration options.
Definition solver3.h:357
APT::Solver::Solver::Pop
bool Pop()
Definition solver3.cc:476
APT::Solver::Solver::trail
std::vector< Trail > trail
Trail of assignments done, and clauses solved.
Definition solver3.h:345
APT::Solver::Solver::Enqueue
bool Enqueue(Lit lit, const Clause *reason=nullptr)
Definition solver3.cc:340
APT::Solver::Solver::trailLim
std::vector< level_type > trailLim
Separator indices for different decision levels in trail.
Definition solver3.h:348
map_pointer
Definition pkgcache.h:98
pkgCache::DepIterator
Definition cacheiterators.h:337
pkgCache::PkgIterator
Definition cacheiterators.h:147
pkgCache::VerIterator
Definition cacheiterators.h:218
pkgCache
Definition pkgcache.h:126
pkgDepCache::Policy
Definition depcache.h:250
pkgDepCache
Definition depcache.h:63
edsp.h
EDSP::Request::Flags
Flags
Definition edsp.h:31
EDSP::Request::FORBID_REMOVE
@ FORBID_REMOVE
Definition edsp.h:35
EDSP::Request::FORBID_NEW_INSTALL
@ FORBID_NEW_INSTALL
Definition edsp.h:34
EDSP::Request::UPGRADE_ALL
@ UPGRADE_ALL
Definition edsp.h:33
pkgcache.h
pkgCache - Structure definitions for the cache file
APT::Solver::Clause
A single clause.
Definition solver3.h:227
APT::Solver::Lit
A literal is a variable with a sign.
Definition solver3.h:195
APT::Solver::Solver::State
The solver state.
Definition solver3.h:530
APT::Solver::Solver::Trail
A trail item.
Definition solver3.h:579
APT::Solver::Solver::Trail::assigned
Var assigned
A variable that got assigned True or False. May be reset to Undefined on backtracking.
Definition solver3.h:581
APT::Solver::Solver::Trail::work
std::optional< Work > work
A work item (a clause) that was solved. Needs to be put back on the work heap on backtracking.
Definition solver3.h:583
APT::Solver::Solver::Work
A single work item.
Definition solver3.h:509
APT::Solver::Solver::Work::size
size_t size
Number of valid choices at insertion time.
Definition solver3.h:516
APT::Solver::Var
Tagged union holding either a package, version, or nothing; representing the reason for installing so...
Definition solver3.h:136
APT::Solver::Var::operator~
constexpr Lit operator~() const noexcept
Negate.
Definition solver3.h:253
always_false
Definition solver3.h:23
pkgCache::Dependency
Definition pkgcache.h:784
pkgCache::Package
contains information for a single unique package
Definition pkgcache.h:460
pkgCache::Version
information for a single version of a package
Definition pkgcache.h:652
pkgCache::Version::VerStr
map_stringitem_t VerStr
complete version string
Definition pkgcache.h:656
pkgCache::Version::ParentPkg
map_pointer< Package > ParentPkg
links to the owning package
Definition pkgcache.h:696