apt: apt-pkg/solver3.h Source File

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