Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] dep_zapdeps: adjust || preference for slot upgrades (bug 706278)
Date: Mon, 27 Jan 2020 02:22:43
Message-Id: 20200127021716.238917-1-zmedico@gentoo.org
1 Prefer choices that include a slot upgrade when appropriate, like for
2 the || ( llvm:10 ... llvm:7 ) case reported in bug 706278. In order
3 to avoid pulling in inappropriate slot upgrades, like those which
4 should only be pulled in with --update and --deep, add a want_update
5 flag to each choice which is True for choices that pull in a new slot
6 for which an update is desirable.
7
8 Mark the test case for bug 480736 as todo, since the "undesirable"
9 slot upgrade which triggers a blocker conflict in this test case is
10 practically indistinguishable from a desirable slot upgrade. This
11 particular blocker conflict is no longer relevant, since current
12 versions of media-libs/libpostproc are no longer compatible with
13 any available media-video/ffmpeg slot. In order to solve this test
14 case, some fancy backtracking (like for bug 382421) will be required.
15
16 Bug: https://bugs.gentoo.org/706278
17 Bug: https://bugs.gentoo.org/480736
18 Signed-off-by: Zac Medico <zmedico@g.o>
19 ---
20 lib/_emerge/depgraph.py | 17 +++-
21 lib/portage/dep/dep_check.py | 79 ++++++++++---------
22 lib/portage/tests/resolver/test_or_choices.py | 9 +++
23 .../resolver/test_or_upgrade_installed.py | 4 +-
24 4 files changed, 69 insertions(+), 40 deletions(-)
25
26 diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
27 index bf8882774..7daeb08cf 100644
28 --- a/lib/_emerge/depgraph.py
29 +++ b/lib/_emerge/depgraph.py
30 @@ -95,6 +95,14 @@ if sys.hexversion >= 0x3000000:
31 else:
32 _unicode = unicode
33
34 +# Exposes a depgraph interface to dep_check.
35 +_dep_check_graph_interface = collections.namedtuple('_dep_check_graph_interface',(
36 + # Indicates a removal action, like depclean or prune.
37 + 'removal_action',
38 + # Checks if update is desirable for a given package.
39 + 'want_update_pkg',
40 +))
41 +
42 class _scheduler_graph_config(object):
43 def __init__(self, trees, pkg_cache, graph, mergelist):
44 self.trees = trees
45 @@ -510,6 +518,10 @@ class _dynamic_depgraph_config(object):
46 soname_deps=depgraph._frozen_config.soname_deps_enabled)
47 # Track missed updates caused by solved conflicts.
48 self._conflict_missed_update = collections.defaultdict(dict)
49 + dep_check_iface = _dep_check_graph_interface(
50 + removal_action="remove" in myparams,
51 + want_update_pkg=depgraph._want_update_pkg,
52 + )
53
54 for myroot in depgraph._frozen_config.trees:
55 self.sets[myroot] = _depgraph_sets()
56 @@ -530,8 +542,9 @@ class _dynamic_depgraph_config(object):
57 self._graph_trees[myroot]["vartree"] = graph_tree
58 self._graph_trees[myroot]["graph_db"] = graph_tree.dbapi
59 self._graph_trees[myroot]["graph"] = self.digraph
60 - self._graph_trees[myroot]["want_update_pkg"] = depgraph._want_update_pkg
61 + self._graph_trees[myroot]["graph_interface"] = dep_check_iface
62 self._graph_trees[myroot]["downgrade_probe"] = depgraph._downgrade_probe
63 +
64 def filtered_tree():
65 pass
66 filtered_tree.dbapi = _dep_check_composite_db(depgraph, myroot)
67 @@ -558,7 +571,7 @@ class _dynamic_depgraph_config(object):
68 self._filtered_trees[myroot]["graph"] = self.digraph
69 self._filtered_trees[myroot]["vartree"] = \
70 depgraph._frozen_config.trees[myroot]["vartree"]
71 - self._filtered_trees[myroot]["want_update_pkg"] = depgraph._want_update_pkg
72 + self._filtered_trees[myroot]["graph_interface"] = dep_check_iface
73 self._filtered_trees[myroot]["downgrade_probe"] = depgraph._downgrade_probe
74
75 dbs = []
76 diff --git a/lib/portage/dep/dep_check.py b/lib/portage/dep/dep_check.py
77 index 321d961dd..a7ae2cfa4 100644
78 --- a/lib/portage/dep/dep_check.py
79 +++ b/lib/portage/dep/dep_check.py
80 @@ -296,7 +296,7 @@ def dep_eval(deplist):
81
82 class _dep_choice(SlotObject):
83 __slots__ = ('atoms', 'slot_map', 'cp_map', 'all_available',
84 - 'all_installed_slots', 'new_slot_count')
85 + 'all_installed_slots', 'new_slot_count', 'want_update', 'all_in_graph')
86
87 def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
88 minimize_slots=False):
89 @@ -331,9 +331,9 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
90 # c) contains masked installed packages
91 # d) is the first item
92
93 - preferred_installed = []
94 preferred_in_graph = []
95 - preferred_any_slot = []
96 + preferred_installed = preferred_in_graph
97 + preferred_any_slot = preferred_in_graph
98 preferred_non_installed = []
99 unsat_use_in_graph = []
100 unsat_use_installed = []
101 @@ -347,8 +347,6 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
102 # for correct ordering in cases like || ( foo[a] foo[b] ).
103 choice_bins = (
104 preferred_in_graph,
105 - preferred_installed,
106 - preferred_any_slot,
107 preferred_non_installed,
108 unsat_use_in_graph,
109 unsat_use_installed,
110 @@ -365,7 +363,7 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
111 graph_db = trees[myroot].get("graph_db")
112 graph = trees[myroot].get("graph")
113 pkg_use_enabled = trees[myroot].get("pkg_use_enabled")
114 - want_update_pkg = trees[myroot].get("want_update_pkg")
115 + graph_interface = trees[myroot].get("graph_interface")
116 downgrade_probe = trees[myroot].get("downgrade_probe")
117 circular_dependency = trees[myroot].get("circular_dependency")
118 vardb = None
119 @@ -506,14 +504,24 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
120 if current_higher or (all_match_current and not all_match_previous):
121 cp_map[avail_pkg.cp] = avail_pkg
122
123 - new_slot_count = (len(slot_map) if graph_db is None else
124 - sum(not graph_db.match_pkgs(slot_atom) for slot_atom in slot_map
125 - if not slot_atom.cp.startswith("virtual/")))
126 + want_update = False
127 + if graph_interface is None or graph_interface.removal_action:
128 + new_slot_count = len(slot_map)
129 + else:
130 + new_slot_count = 0
131 + for slot_atom, avail_pkg in slot_map.items():
132 + if graph_interface.want_update_pkg(parent, avail_pkg):
133 + want_update = True
134 + if (not slot_atom.cp.startswith("virtual/")
135 + and not graph_db.match_pkgs(slot_atom)):
136 + new_slot_count += 1
137
138 this_choice = _dep_choice(atoms=atoms, slot_map=slot_map,
139 cp_map=cp_map, all_available=all_available,
140 all_installed_slots=False,
141 - new_slot_count=new_slot_count)
142 + new_slot_count=new_slot_count,
143 + all_in_graph=False,
144 + want_update=want_update)
145 if all_available:
146 # The "all installed" criterion is not version or slot specific.
147 # If any version of a package is already in the graph then we
148 @@ -567,6 +575,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
149 graph_db.match_pkgs(atom)):
150 all_in_graph = False
151 break
152 + this_choice.all_in_graph = all_in_graph
153 +
154 circular_atom = None
155 if not (parent is None or priority is None) and \
156 (parent.onlydeps or
157 @@ -607,27 +617,8 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
158 elif all_installed:
159 if all_installed_slots:
160 preferred_installed.append(this_choice)
161 - elif parent is None or want_update_pkg is None:
162 - preferred_any_slot.append(this_choice)
163 else:
164 - # When appropriate, prefer a slot that is not
165 - # installed yet for bug #478188.
166 - want_update = True
167 - for slot_atom, avail_pkg in slot_map.items():
168 - if avail_pkg in graph:
169 - continue
170 - # New-style virtuals have zero cost to install.
171 - if slot_atom.startswith("virtual/") or \
172 - vardb.match(slot_atom):
173 - continue
174 - if not want_update_pkg(parent, avail_pkg):
175 - want_update = False
176 - break
177 -
178 - if want_update:
179 - preferred_installed.append(this_choice)
180 - else:
181 - preferred_any_slot.append(this_choice)
182 + preferred_any_slot.append(this_choice)
183 else:
184 preferred_non_installed.append(this_choice)
185 else:
186 @@ -676,10 +667,6 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
187 if len(choices) < 2:
188 continue
189
190 - sort_keys = []
191 - # Prefer choices with all_installed_slots for bug #480736.
192 - sort_keys.append(lambda x: not x.all_installed_slots)
193 -
194 if minimize_slots:
195 # Prefer choices having fewer new slots. When used with DNF form,
196 # this can eliminate unecessary packages that depclean would
197 @@ -694,15 +681,35 @@ def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None,
198 # contribute to outcomes that appear to be random. Meanwhile,
199 # the order specified in the ebuild is without variance, so it
200 # does not have this problem.
201 - sort_keys.append(lambda x: x.new_slot_count)
202 + choices.sort(key=operator.attrgetter('new_slot_count'))
203
204 - choices.sort(key=lambda x: tuple(f(x) for f in sort_keys))
205 for choice_1 in choices[1:]:
206 cps = set(choice_1.cp_map)
207 for choice_2 in choices:
208 if choice_1 is choice_2:
209 # choice_1 will not be promoted, so move on
210 break
211 + if (
212 + # For removal actions, prefer choices where all packages
213 + # have been pulled into the graph.
214 + (graph_interface and graph_interface.removal_action and
215 + choice_1.all_in_graph and not choice_2.all_in_graph)
216 +
217 + # Prefer choices where all_installed_slots is True, except
218 + # in cases where we want to upgrade to a new slot as in
219 + # bug 706278. Don't compare new_slot_count here since that
220 + # would aggressively override the preference order defined
221 + # in the ebuild, breaking the test case for bug 645002.
222 + or (choice_1.all_installed_slots and
223 + not choice_2.all_installed_slots and
224 + not choice_2.want_update)
225 + ):
226 + # promote choice_1 in front of choice_2
227 + choices.remove(choice_1)
228 + index_2 = choices.index(choice_2)
229 + choices.insert(index_2, choice_1)
230 + break
231 +
232 intersecting_cps = cps.intersection(choice_2.cp_map)
233 if not intersecting_cps:
234 continue
235 diff --git a/lib/portage/tests/resolver/test_or_choices.py b/lib/portage/tests/resolver/test_or_choices.py
236 index c0316bfb3..a50ad0151 100644
237 --- a/lib/portage/tests/resolver/test_or_choices.py
238 +++ b/lib/portage/tests/resolver/test_or_choices.py
239 @@ -288,6 +288,15 @@ class OrChoicesTestCase(TestCase):
240 class OrChoicesLibpostprocTestCase(TestCase):
241
242 def testOrChoicesLibpostproc(self):
243 + # This test case is expected to fail after the fix for bug 706278,
244 + # since the "undesirable" slot upgrade which triggers a blocker conflict
245 + # in this test case is practically indistinguishable from a desirable
246 + # slot upgrade. This particular blocker conflict is no longer relevant,
247 + # since current versions of media-libs/libpostproc are no longer
248 + # compatible with any available media-video/ffmpeg slot. In order to
249 + # solve this test case, some fancy backtracking (like for bug 382421)
250 + # will be required.
251 + self.todo = True
252
253 ebuilds = {
254 "media-video/ffmpeg-0.10" : {
255 diff --git a/lib/portage/tests/resolver/test_or_upgrade_installed.py b/lib/portage/tests/resolver/test_or_upgrade_installed.py
256 index c3efebf55..2400adf7d 100644
257 --- a/lib/portage/tests/resolver/test_or_upgrade_installed.py
258 +++ b/lib/portage/tests/resolver/test_or_upgrade_installed.py
259 @@ -213,8 +213,8 @@ class OrUpgradeInstalledTestCase(TestCase):
260 ['@world'],
261 options={'--update': True, '--deep': True},
262 success=True,
263 - mergelist=[],
264 - #mergelist=['sys-devel/llvm-9'],
265 + #mergelist=[],
266 + mergelist=['sys-devel/llvm-9', 'media-libs/mesa-19.2.8'],
267 ),
268 )
269
270 --
271 2.24.1