apt 2.9.16 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 <optional>
11#include <queue>
12#include <vector>
13
14#include <apt-pkg/configuration.h>
15#include <apt-pkg/depcache.h>
16#include <apt-pkg/pkgcache.h>
17#include <apt-pkg/policy.h>
18
19 namespace APT
20{
21
22/*
23 * \brief APT 3.0 solver
24 *
25 * This is a simple solver focused on understandability and sensible results, it
26 * will not generally find all solutions to the problem but will try to find the best
27 * ones.
28 *
29 * It is a brute force solver with heuristics, conflicts learning, and 2**32 levels
30 * of backtracking.
31 */
32 class Solver
33{
34 enum class Decision : uint16_t;
35 enum class Hint : uint16_t;
36 struct Var;
37 struct CompareProviders3;
38 struct State;
39 struct Work;
40 struct Solved;
41
42 // \brief Groups of works, these are ordered.
43 //
44 // Later items will be skipped if they are optional, or we will when backtracking,
45 // try a different choice for them.
46 enum class Group : uint8_t
47 {
48 HoldOrDelete,
49 NewUnsatRecommends,
50
51 // Satisfying dependencies on entirely new packages first is a good idea because
52 // it may contain replacement packages like libfoo1t64 whereas we later will see
53 // Depends: libfoo1 where libfoo1t64 Provides libfoo1 and we'd have to choose.
54 SatisfyNew,
55 Satisfy,
56 // On a similar note as for SatisfyNew, if the dependency contains obsolete packages
57 // try it last.
58 SatisfyObsolete,
59
60 // My intuition tells me that we should try to schedule upgrades first, then
61 // any non-obsolete installed packages, and only finally obsolete ones, such
62 // that newer packages guide resolution of dependencies for older ones, they
63 // may have more stringent dependencies, like a (>> 2) whereas an obsolete
64 // package may have a (>> 1), for example.
65 UpgradeManual,
66 InstallManual,
67 ObsoleteManual,
68
69 // Automatically installed packages must come last in the group, this allows
70 // us to see if they were installed as a dependency of a manually installed package,
71 // allowing a simple implementation of an autoremoval code.
72 UpgradeAuto,
73 KeepAuto,
74 ObsoleteAuto
75 };
76
77 // \brief Type to record depth at. This may very well be a 16-bit
78 // unsigned integer, then change Solver::State::Decision to be a
79 // uint16_t class enum as well to get a more compact space.
80 using depth_type = unsigned int;
81
82 // Documentation
83 template <typename T>
84 using heap = std::vector<T>;
85
86 static_assert(sizeof(depth_type) >= sizeof(map_id_t));
87
88 // Cache is needed to construct Iterators from Version objects we see
89 pkgCache &cache;
90 // Policy is needed for determining candidate version.
91 pkgDepCache::Policy &policy;
92 // States for packages
93 std::vector<State> pkgStates{};
94 // States for versions
95 std::vector<State> verStates{};
96
97 // \brief Helper function for safe access to package state.
98 inline State &operator[](pkgCache::Package *P)
99 {
100 return pkgStates[P->ID];
101 }
102
103 // \brief Helper function for safe access to version state.
104 inline State &operator[](pkgCache::Version *V)
105 {
106 return verStates[V->ID];
107 }
108 // \brief Helper function for safe access to either state.
109 inline State &operator[](Var r);
110
111 mutable std::vector<char> pkgObsolete;
112 bool Obsolete(pkgCache::PkgIterator pkg) const;
113 bool ObsoletedByNewerSourceVersion(pkgCache::VerIterator cand) const;
114
115 // \brief Heap of the remaining work.
116 //
117 // We are using an std::vector with std::make_heap(), std::push_heap(),
118 // and std::pop_heap() rather than a priority_queue because we need to
119 // be able to iterate over the queued work and see if a choice would
120 // invalidate any work.
121 heap<Work> work{};
122 // \brief Whether RescoreWork() actually needs to rescore the work.
123 bool needsRescore{false};
124
125 // \brief Backlog of solved work.
126 //
127 // Solved work may become invalidated when backtracking, so store it
128 // here to revisit it later. This is similar to what MiniSAT calls the
129 // trail; one distinction is that we have both literals and our work
130 // queue to be concerned about
131 std::vector<Solved> solved{};
132
133 // \brief Current decision level.
134 //
135 // This is an index into the solved vector.
136 std::vector<depth_type> choices{};
137
139 // \brief Debug level
140 int debug{_config->FindI("Debug::APT::Solver")};
141 // \brief If set, we try to keep automatically installed packages installed.
142 bool KeepAuto{not _config->FindB("APT::Get::AutomaticRemove")};
143 // \brief If set, removals are allowed.
144 bool AllowRemove{_config->FindB("APT::Solver::Remove", true)};
145 // \brief If set, installs are allowed.
146 bool AllowInstall{_config->FindB("APT::Solver::Install", true)};
147 // \brief If set, we use strict pinning.
148 bool StrictPinning{_config->FindB("APT::Solver::Strict-Pinning", true)};
149
150 // \brief Enqueue dependencies shared by all versions of the package.
151 bool EnqueueCommonDependencies(pkgCache::PkgIterator Pkg);
152 // \brief Reject reverse dependencies. Must call std::make_heap() after.
153 bool RejectReverseDependencies(pkgCache::VerIterator Ver);
154 // \brief Enqueue a single or group
155 bool EnqueueOrGroup(pkgCache::DepIterator start, pkgCache::DepIterator end, Var reason);
156 // \brief Propagate a "true" value of a variable
157 bool PropagateInstall(Var var);
158 // \brief Propagate a rejection of a variable
159 bool PropagateReject(Var var);
160 // \brief Check if a version is allowed by policy.
161 bool IsAllowedVersion(pkgCache::Version *V);
162
163 // \brief Return the current depth (choices.size() with casting)
164 depth_type depth()
165 {
166 return static_cast<depth_type>(choices.size());
167 }
168
169 public:
170 // \brief Create a new decision level.
171 void Push(Work work);
172 // \brief Revert to the previous decision level.
173 bool Pop();
174 // \brief Undo a single assignment / solved work item
175 void UndoOne();
176 // \brief Add work to our work queue.
177 void AddWork(Work &&work);
178 // \brief Rescore the work after a reject or a pop
179 void RescoreWorkIfNeeded();
180
181 // \brief Basic solver initializer. This cannot fail.
182 Solver(pkgCache &Cache, pkgDepCache::Policy &Policy);
183
184 // Assume that the variable is decided as specified.
185 bool Assume(Var var, bool decision, Var reason);
186 // Enqueue a decision fact
187 bool Enqueue(Var var, bool decision, Var reason);
188
189 // \brief Mark the package for install. This is annoying as it incurs a decision
190 bool Install(pkgCache::PkgIterator Pkg, Var reason, Group group);
191
192 // \brief Apply the selections from the dep cache to the solver
193 bool FromDepCache(pkgDepCache &depcache);
194 // \brief Apply the solver result to the depCache
195 bool ToDepCache(pkgDepCache &depcache);
196
197 // \brief Solve the dependencies
198 bool Solve();
199
200 // Print dependency chain
201 std::string WhyStr(Var reason);
202};
203
204}; // namespace APT
205
214 struct APT::Solver::Var
215{
216 uint32_t IsVersion : 1;
217 uint32_t MapPtr : 31;
218
219 Var() : IsVersion(0), MapPtr(0) {}
220 explicit Var(pkgCache::PkgIterator const &Pkg) : IsVersion(0), MapPtr(Pkg.MapPointer()) {}
221 explicit Var(pkgCache::VerIterator const &Ver) : IsVersion(1), MapPtr(Ver.MapPointer()) {}
222
223 // \brief Return the package, if any, otherwise 0.
224 map_pointer<pkgCache::Package> Pkg() const
225 {
226 return IsVersion ? 0 : map_pointer<pkgCache::Package>{(uint32_t)MapPtr};
227 }
228 // \brief Return the version, if any, otherwise 0.
229 map_pointer<pkgCache::Version> Ver() const
230 {
231 return IsVersion ? map_pointer<pkgCache::Version>{(uint32_t)MapPtr} : 0;
232 }
233 // \brief Return the package iterator if storing a package, or an empty one
234 pkgCache::PkgIterator Pkg(pkgCache &cache) const
235 {
236 return IsVersion ? pkgCache::PkgIterator() : pkgCache::PkgIterator(cache, cache.PkgP + Pkg());
237 }
238 // \brief Return the version iterator if storing a package, or an empty end.
239 pkgCache::VerIterator Ver(pkgCache &cache) const
240 {
241 return IsVersion ? pkgCache::VerIterator(cache, cache.VerP + Ver()) : pkgCache::VerIterator();
242 }
243 // \brief Check if there is no reason.
244 bool empty() const
245 {
246 return IsVersion == 0 && MapPtr == 0;
247 }
248
249 std::string toString(pkgCache &cache) const
250 {
251 if (auto P = Pkg(cache); not P.end())
252 return P.FullName();
253 if (auto V = Ver(cache); not V.end())
254 return V.ParentPkg().FullName() + "=" + V.VerStr();
255 return "(root)";
256 }
257};
258
269 struct APT::Solver::Work
270{
271 // \brief Var for the work
272 Var reason;
273 // \brief The depth at which the item has been added
274 depth_type depth;
275 // \brief The group we are in
276 Group group;
277 // \brief Possible solutions to this task, ordered in order of preference.
278 std::vector<pkgCache::Version *> solutions{};
279
280 // This is a union because we only need to store the choice we made when adding
281 // to the choice vector, and we don't need the size of valid choices in there.
282 union
283 {
284 // The choice we took
285 pkgCache::Version *choice;
286 // Number of valid choices
287 size_t size;
288 };
289
290 // \brief Whether this is an optional work item, they will be processed last
291 bool optional;
292 // \brief Whether this is an ugprade
293 bool upgrade;
294 // \brief This item should be removed from the queue.
295 bool erased;
296
297 bool operator<(APT::Solver::Work const &b) const;
298 // \brief Dump the work item to std::cerr
299 void Dump(pkgCache &cache);
300
301 inline Work(Var reason, depth_type depth, Group group, bool optional = false, bool upgrade = false) : reason(reason), depth(depth), group(group), size(0), optional(optional), upgrade(upgrade), erased(false) {}
302};
303
304// \brief This essentially describes the install state in RFC2119 terms.
305 enum class APT::Solver::Decision : uint16_t
306{
307 // \brief We have not made a choice about the package yet
308 NONE,
309 // \brief We need to install this package
310 MUST,
311 // \brief We cannot install this package (need conflicts with it)
312 MUSTNOT,
313};
314
315// \brief Hints for the solver about the item.
316 enum class APT::Solver::Hint : uint16_t
317{
318 // \brief We have not made a choice about the package yet
319 NONE,
320 // \brief This package was listed as a Recommends of a must package,
321 SHOULD,
322 // \brief This package was listed as a Suggests of a must-not package
323 MAY,
324};
325
332 struct APT::Solver::State
333{
334 // \brief The reason for causing this state (invalid for NONE).
335 //
336 // Rejects may have been caused by a later state. Consider we select
337 // between x1 and x2 in depth = N. If we now find dependencies of x1
338 // leading to a conflict with a package in K < N, we will record all
339 // of them as REJECT in depth = K.
340 //
341 // You can follow the reason chain upwards as long as the depth
342 // doesn't increase to unwind.
343 //
344 // Vars < 0 are package ID, reasons > 0 are version IDs.
345 Var reason{};
346
347 // \brief The depth at which the decision has been taken
348 depth_type depth{0};
349
350 // \brief This essentially describes the install state in RFC2119 terms.
351 Decision decision{Decision::NONE};
352
353 // \brief Any hint.
354 Hint hint{Hint::NONE};
355};
356
363 struct APT::Solver::Solved
364{
365 // \brief A variable that has been assigned. We store this as a reason (FIXME: Rename Var to Var)
366 Var assigned;
367 // \brief A work item that has been solved. This needs to be put back on the queue.
368 std::optional<Work> work;
369};
370
371 inline APT::Solver::State &APT::Solver::operator[](Var r)
372{
373 if (auto P = r.Pkg())
374 return (*this)[cache.PkgP + P];
375 if (auto V = r.Ver())
376 return (*this)[cache.VerP + V];
377
378 abort();
379}
APT::Solver
Definition solver3.h:33
APT::Solver::UndoOne
void UndoOne()
Definition solver3.cc:718
APT::Solver::Pop
bool Pop()
Definition solver3.cc:757
APT::Solver::Solver
Solver(pkgCache &Cache, pkgDepCache::Policy &Policy)
Definition solver3.cc:165
map_pointer
Definition pkgcache.h:98
pkgCache::DepIterator
Definition cacheiterators.h:299
pkgCache::PkgIterator
Definition cacheiterators.h:147
pkgCache::VerIterator
Definition cacheiterators.h:193
pkgCache
Definition pkgcache.h:126
pkgDepCache::Policy
Definition depcache.h:249
pkgDepCache
Definition depcache.h:62
pkgcache.h
pkgCache - Structure definitions for the cache file
APT::Solver::CompareProviders3
Definition solver3.cc:30
APT::Solver::Solved
A solved item.
Definition solver3.h:364
APT::Solver::State
The solver state.
Definition solver3.h:333
APT::Solver::Var
Tagged union holding either a package, version, or nothing; representing the reason for installing so...
Definition solver3.h:215
APT::Solver::Work
A single work item.
Definition solver3.h:270
pkgCache::Package
contains information for a single unique package
Definition pkgcache.h:450
pkgCache::Package::ID
map_id_t ID
unique sequel ID
Definition pkgcache.h:494
pkgCache::Version
information for a single version of a package
Definition pkgcache.h:625
pkgCache::Version::ID
map_id_t ID
unique sequel ID
Definition pkgcache.h:686