apt: apt-pkg/solver3.h Source File

apt 3.1.4 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
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 V &operator[](const K *key) { return data_[key->ID]; }
60 const V &operator[](const K *key) const { return data_[key->ID]; }
61 ~ContiguousCacheMap() { delete[] data_; }
62};
63
67template <typename K, typename V>
68using FastContiguousCacheMap = ContiguousCacheMap<K, V, true>;
69
70/*
71 * \brief APT 3.0 solver
72 *
73 * This is a simple solver focused on understandability and sensible results, it
74 * will not generally find all solutions to the problem but will try to find the best
75 * ones.
76 *
77 * It is a brute force solver with heuristics, conflicts learning, and 2**32 levels
78 * of backtracking.
79 */
80 class Solver
81{
82 enum class Decision : uint16_t;
83 enum class Hint : uint16_t;
84 struct Var;
85 struct CompareProviders3;
86 struct State;
87 struct Clause;
88 struct Work;
89 struct Solved;
90 friend struct std::hash<APT::Solver::Var>;
91
92 // \brief Groups of works, these are ordered.
93 //
94 // Later items will be skipped if they are optional, or we will when backtracking,
95 // try a different choice for them.
96 enum class Group : uint8_t
97 {
98 HoldOrDelete,
99
100 // Satisfying dependencies on entirely new packages first is a good idea because
101 // it may contain replacement packages like libfoo1t64 whereas we later will see
102 // Depends: libfoo1 where libfoo1t64 Provides libfoo1 and we'd have to choose.
103 SatisfyNew,
104 Satisfy,
105 // On a similar note as for SatisfyNew, if the dependency contains obsolete packages
106 // try it last.
107 SatisfyObsolete,
108
109 // Select a version of a package chosen for install.
110 SelectVersion,
111
112 // My intuition tells me that we should try to schedule upgrades first, then
113 // any non-obsolete installed packages, and only finally obsolete ones, such
114 // that newer packages guide resolution of dependencies for older ones, they
115 // may have more stringent dependencies, like a (>> 2) whereas an obsolete
116 // package may have a (>> 1), for example.
117 UpgradeManual,
118 InstallManual,
119 ObsoleteManual,
120
121 // Automatically installed packages must come last in the group, this allows
122 // us to see if they were installed as a dependency of a manually installed package,
123 // allowing a simple implementation of an autoremoval code.
124 UpgradeAuto,
125 KeepAuto,
126 ObsoleteAuto,
127
128 // Satisfy optional dependencies that were previously satisfied but won't otherwise be installed
129 SatisfySuggests,
130 };
131
132 // \brief Type to record depth at. This may very well be a 16-bit
133 // unsigned integer, then change Solver::State::Decision to be a
134 // uint16_t class enum as well to get a more compact space.
135 using depth_type = unsigned int;
136
137 // Documentation
138 template <typename T>
139 using heap = std::vector<T>;
140
141 static_assert(sizeof(depth_type) >= sizeof(map_id_t));
142
143 // Cache is needed to construct Iterators from Version objects we see
144 pkgCache &cache;
145 // Policy is needed for determining candidate version.
146 pkgDepCache::Policy &policy;
147 // Root state
148 std::unique_ptr<State> rootState;
149 // States for packages
150 ContiguousCacheMap<pkgCache::Package, State> pkgStates;
151 // States for versions
152 ContiguousCacheMap<pkgCache::Version, State> verStates;
153
154 // \brief Helper function for safe access to package state.
155 inline State &operator[](const pkgCache::Package *P)
156 {
157 return pkgStates[P];
158 }
159 inline const State &operator[](const pkgCache::Package *P) const
160 {
161 return pkgStates[P];
162 }
163
164 // \brief Helper function for safe access to version state.
165 inline State &operator[](const pkgCache::Version *V)
166 {
167 return verStates[V];
168 }
169 inline const State &operator[](const pkgCache::Version *V) const
170 {
171 return verStates[V];
172 }
173 // \brief Helper function for safe access to either state.
174 inline State &operator[](Var r);
175 inline const State &operator[](Var r) const;
176
177 mutable FastContiguousCacheMap<pkgCache::Package, char> pkgObsolete;
178 // \brief Check if package is obsolete.
179 // \param AllowManual controls whether manual packages can be obsolete
180 bool Obsolete(pkgCache::PkgIterator pkg, bool AllowManual=false) const;
181 bool ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) const;
182
183 mutable FastContiguousCacheMap<pkgCache::Version, short> priorities;
184 short GetPriority(pkgCache::VerIterator ver) const
185 {
186 if (priorities[ver] == 0)
187 priorities[ver] = policy.GetPriority(ver);
188 return priorities[ver];
189 }
190
191 mutable ContiguousCacheMap<pkgCache::Package, pkgCache::VerIterator> candidates;
192 pkgCache::VerIterator GetCandidateVer(pkgCache::PkgIterator pkg) const
193 {
194 if (candidates[pkg].end())
195 candidates[pkg] = policy.GetCandidateVer(pkg);
196 return candidates[pkg];
197 }
198
199 // \brief Heap of the remaining work.
200 //
201 // We are using an std::vector with std::make_heap(), std::push_heap(),
202 // and std::pop_heap() rather than a priority_queue because we need to
203 // be able to iterate over the queued work and see if a choice would
204 // invalidate any work.
205 heap<Work> work;
206
207 // \brief Backlog of solved work.
208 //
209 // Solved work may become invalidated when backtracking, so store it
210 // here to revisit it later. This is similar to what MiniSAT calls the
211 // trail; one distinction is that we have both literals and our work
212 // queue to be concerned about
213 std::vector<Solved> solved;
214
215 // \brief Propagation queue
216 std::queue<Var> propQ;
217 // \brief Discover variables
218 std::queue<Var> discoverQ;
219
220 // \brief Current decision level.
221 //
222 // This is an index into the solved vector.
223 std::vector<depth_type> choices{};
224
225 // \brief The time we called Solve()
226 time_t startTime{};
227
228 EDSP::Request::Flags requestFlags;
230 std::string version{_config->Find("APT::Solver", "3.0")};
231 // \brief Debug level
232 int debug{_config->FindI("Debug::APT::Solver")};
233 // \brief If set, we try to keep automatically installed packages installed.
234 bool KeepAuto{version == "3.0" || not _config->FindB("APT::Get::AutomaticRemove")};
235 // \brief Determines if we are in upgrade mode.
236 bool IsUpgrade{_config->FindB("APT::Solver::Upgrade", requestFlags &EDSP::Request::UPGRADE_ALL)};
237 // \brief If set, removals are allowed.
238 bool AllowRemove{_config->FindB("APT::Solver::Remove", not(requestFlags & EDSP::Request::FORBID_REMOVE))};
239 // \brief If set, removal of manual packages is allowed.
240 bool AllowRemoveManual{AllowRemove && _config->FindB("APT::Solver::RemoveManual", false)};
241 // \brief If set, installs are allowed.
242 bool AllowInstall{_config->FindB("APT::Solver::Install", not(requestFlags & EDSP::Request::FORBID_NEW_INSTALL))};
243 // \brief If set, we use strict pinning.
244 bool StrictPinning{_config->FindB("APT::Solver::Strict-Pinning", true)};
245 // \brief If set, we install missing recommends and pick new best packages.
246 bool FixPolicyBroken{_config->FindB("APT::Get::Fix-Policy-Broken")};
247 // \brief If set, we use strict pinning.
248 bool DeferVersionSelection{_config->FindB("APT::Solver::Defer-Version-Selection", true)};
249 // \brief If set, we use strict pinning.
250 int Timeout{_config->FindI("APT::Solver::Timeout", 10)};
251
252 // \brief Keep recommends installed
253 bool KeepRecommends{_config->FindB("APT::AutoRemove::RecommendsImportant", true)};
254 // \brief Keep suggests installed
255 bool KeepSuggests{_config->FindB("APT::AutoRemove::SuggestsImportant", true)};
256
257 // \brief Discover a variable, translating the underlying dependencies to the SAT presentation
258 //
259 // This does a breadth-first search of the entire dependency tree of var,
260 // utilizing the discoverQ above.
261 void Discover(Var var);
262 // \brief Link a clause into the watchers
263 const Clause *RegisterClause(Clause &&clause);
264 // \brief Enqueue dependencies shared by all versions of the package.
265 void RegisterCommonDependencies(pkgCache::PkgIterator Pkg);
266
267 // \brief Translate an or group into a clause object
268 [[nodiscard]] Clause TranslateOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason);
269 // \brief Propagate all pending propagations
270 [[nodiscard]] bool Propagate();
271
272 // \brief Return the current depth (choices.size() with casting)
273 depth_type depth()
274 {
275 return static_cast<depth_type>(choices.size());
276 }
277 inline Var bestReason(Clause const *clause, Var var) const;
278
279 public:
280 // \brief Create a new decision level.
281 void Push(Var var, Work work);
282 // \brief Revert to the previous decision level.
283 [[nodiscard]] bool Pop();
284 // \brief Undo a single assignment / solved work item
285 void UndoOne();
286 // \brief Add work to our work queue.
287 [[nodiscard]] bool AddWork(Work &&work);
288
289 // \brief Basic solver initializer. This cannot fail.
290 Solver(pkgCache &Cache, pkgDepCache::Policy &Policy, EDSP::Request::Flags requestFlags);
291 ~Solver();
292
293 // Assume that the variable is decided as specified.
294 [[nodiscard]] bool Assume(Var var, bool decision, const Clause *reason = nullptr);
295 // Enqueue a decision fact
296 [[nodiscard]] bool Enqueue(Var var, bool decision, const Clause *reason = nullptr);
297
298 // \brief Apply the selections from the dep cache to the solver
299 [[nodiscard]] bool FromDepCache(pkgDepCache &depcache);
300 // \brief Apply the solver result to the depCache
301 [[nodiscard]] bool ToDepCache(pkgDepCache &depcache) const;
302
303 // \brief Solve the dependencies
304 [[nodiscard]] bool Solve();
305
306 // Print dependency chain
307 std::string WhyStr(Var reason) const;
308
320 std::string LongWhyStr(Var var, bool decision, const Clause *rclause, std::string prefix, std::unordered_set<Var> &seen) const;
321
322 // \brief Temporary internal API with external linkage for the `apt why` and `apt why-not` commands.
323 APT_PUBLIC static std::string InternalCliWhy(pkgDepCache &depcache, pkgCache::PkgIterator Pkg, bool decision);
324};
325
326}; // namespace APT
327
336 struct APT::Solver::Var
337{
338 uint32_t value;
339
340 explicit constexpr Var(uint32_t value = 0) : value{value} {}
341 explicit Var(pkgCache::PkgIterator const &Pkg) : value(uint32_t(Pkg.MapPointer()) << 1) {}
342 explicit Var(pkgCache::VerIterator const &Ver) : value(uint32_t(Ver.MapPointer()) << 1 | 1) {}
343
344 inline constexpr bool isVersion() const { return value & 1; }
345 inline constexpr uint32_t mapPtr() const { return value >> 1; }
346
347 // \brief Return the package, if any, otherwise 0.
348 map_pointer<pkgCache::Package> Pkg() const
349 {
350 return isVersion() ? 0 : map_pointer<pkgCache::Package>{mapPtr()};
351 }
352 // \brief Return the version, if any, otherwise 0.
353 map_pointer<pkgCache::Version> Ver() const
354 {
355 return isVersion() ? map_pointer<pkgCache::Version>{mapPtr()} : 0;
356 }
357 // \brief Return the package iterator if storing a package, or an empty one
358 pkgCache::PkgIterator Pkg(pkgCache &cache) const
359 {
360 return isVersion() ? pkgCache::PkgIterator() : pkgCache::PkgIterator(cache, cache.PkgP + Pkg());
361 }
362 // \brief Return the version iterator if storing a package, or an empty end.
363 pkgCache::VerIterator Ver(pkgCache &cache) const
364 {
365 return isVersion() ? pkgCache::VerIterator(cache, cache.VerP + Ver()) : pkgCache::VerIterator();
366 }
367 // \brief Return a package, cast from version if needed
368 pkgCache::PkgIterator CastPkg(pkgCache &cache) const
369 {
370 return isVersion() ? Ver(cache).ParentPkg() : Pkg(cache);
371 }
372 // \brief Check if there is no reason.
373 constexpr bool empty() const { return value == 0; }
374 constexpr bool operator!=(Var const other) const { return value != other.value; }
375 constexpr bool operator==(Var const other) const { return value == other.value; }
376
377 std::string toString(pkgCache &cache) const
378 {
379 if (auto P = Pkg(cache); not P.end())
380 return P.FullName();
381 if (auto V = Ver(cache); not V.end())
382 return V.ParentPkg().FullName() + "=" + V.VerStr();
383 return "(root)";
384 }
385};
386
393 struct APT::Solver::Clause
394{
395 // \brief Underyling dependency
396 pkgCache::Dependency *dep = nullptr;
397 // \brief Var for the work
398 Var reason;
399 // \brief The group we are in
400 Group group;
401 // \brief Possible solutions to this task, ordered in order of preference.
402 std::vector<Var> solutions{};
403 // \brief An optional clause does not need to be satisfied
404 bool optional;
405
406 // \brief A negative clause negates the solutions, that is X->A|B you get X->!(A|B), aka X->!A&!B
407 bool negative;
408
409 // Clauses merged with this clause
410 std::forward_list<Clause> merged;
411
412 inline Clause(Var reason, Group group, bool optional = false, bool negative = false) : reason(reason), group(group), optional(optional), negative(negative) {}
413
414 std::string toString(pkgCache &cache, bool pretty = false, bool showMerged = true) const;
415};
416
427 struct APT::Solver::Work
428{
429 const Clause *clause;
430
431 // \brief The depth at which the item has been added
432 depth_type depth;
433
434 // Number of valid choices
435 size_t size{0};
436
437 // \brief This item should be removed from the queue.
438 bool erased{false};
439
440 bool operator<(APT::Solver::Work const &b) const;
441 std::string toString(pkgCache &cache) const;
442 inline Work(const Clause *clause, depth_type depth) : clause(clause), depth(depth) {}
443};
444
445// \brief This essentially describes the install state in RFC2119 terms.
446enum class APT::Solver::Decision : uint16_t
447{
448 // \brief We have not made a choice about the package yet
449 NONE,
450 // \brief We need to install this package
451 MUST,
452 // \brief We cannot install this package (need conflicts with it)
453 MUSTNOT,
454};
455
462 struct APT::Solver::State
463{
464 // \brief The reason for causing this state (invalid for NONE).
465 //
466 // Rejects may have been caused by a later state. Consider we select
467 // between x1 and x2 in depth = N. If we now find dependencies of x1
468 // leading to a conflict with a package in K < N, we will record all
469 // of them as REJECT in depth = K.
470 //
471 // You can follow the reason chain upwards as long as the depth
472 // doesn't increase to unwind.
473 //
474 // Vars < 0 are package ID, reasons > 0 are version IDs.
475 const Clause *reason{};
476
477 // \brief The depth at which the decision has been taken
478 depth_type depth{0};
479
480 // \brief This essentially describes the install state in RFC2119 terms.
481 Decision decision{Decision::NONE};
482
483 // \brief Flags.
484 struct
485 {
486 bool discovered{};
487 bool manual{};
488 } flags;
489
490 static_assert(sizeof(flags) <= sizeof(int));
491
492 // \brief Clauses owned by this package/version
493 std::vector<std::unique_ptr<Clause>> clauses;
494 // \brief Reverse clauses, that is dependencies (or conflicts) from other packages on this one
495 std::vector<const Clause *> rclauses;
496};
497
504 struct APT::Solver::Solved
505{
506 // \brief A variable that has been assigned. We store this as a reason (FIXME: Rename Var to Var)
507 Var assigned;
508 // \brief A work item that has been solved. This needs to be put back on the queue.
509 std::optional<Work> work;
510};
511
512inline APT::Solver::State &APT::Solver::operator[](Var r)
513{
514 if (auto P = r.Pkg())
515 return (*this)[cache.PkgP + P];
516 if (auto V = r.Ver())
517 return (*this)[cache.VerP + V];
518 return *rootState.get();
519}
520
521inline const APT::Solver::State &APT::Solver::operator[](Var r) const
522{
523 return const_cast<Solver &>(*this)[r];
524}
525
526// Custom specialization of std::hash can be injected in namespace std.
527template <>
528 struct std::hash<APT::Solver::Var>
529{
530 std::hash<decltype(APT::Solver::Var::value)> hash_value;
531 std::size_t operator()(const APT::Solver::Var &v) const noexcept { return hash_value(v.value); }
532};
APT::ContiguousCacheMap
A simple mapping from objects in the cache to user-defined types.
Definition solver3.h:36
APT::Solver
Definition solver3.h:81
APT::Solver::UndoOne
void UndoOne()
Definition solver3.cc:991
APT::Solver::Solver
Solver(pkgCache &Cache, pkgDepCache::Policy &Policy, EDSP::Request::Flags requestFlags)
Definition solver3.cc:199
APT::Solver::Pop
bool Pop()
Definition solver3.cc:1022
APT::Solver::LongWhyStr
std::string LongWhyStr(Var var, bool decision, const Clause *rclause, std::string prefix, std::unordered_set< Var > &seen) const
Print a long reason string.
Definition solver3.cc:331
map_pointer
Definition pkgcache.h:98
pkgCache::DepIterator
Definition cacheiterators.h:327
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:394
APT::Solver::CompareProviders3
Definition solver3.cc:43
APT::Solver::Solved
A solved item.
Definition solver3.h:505
APT::Solver::State
The solver state.
Definition solver3.h:463
APT::Solver::Var
Tagged union holding either a package, version, or nothing; representing the reason for installing so...
Definition solver3.h:337
APT::Solver::Work
A single work item.
Definition solver3.h:428
always_false
Definition solver3.h:23
pkgCache::Dependency
Definition pkgcache.h:783
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