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] _serialize_tasks: limit scope of dropped circular dependencies
Date: Thu, 26 Dec 2019 12:57:51
Message-Id: 20191226125518.3499-1-zmedico@gentoo.org
1 Ensure that all members of a buildtime dependency cycle are merged
2 as a group, such that packages which depend on one or more members
3 of the group will only be merged *after* the entire group has been
4 merged.
5
6 This extends runtime cycle handling to also handle buildtime cycles
7 in cases where the buildtime dependencies happen to be satisfied by
8 installed packages. In situations when this is necessary, it is
9 desirable to rely on the old installed instances of these packages
10 as little as possible, since they might have been broken by the
11 upgrade of a package that is a member of the dependency cycle.
12 Upgrading members of the cycle as a group effectively minimizes
13 reliance on the old installed package instances, avoiding some cases
14 of bug 199856. For example, it should avoid bug 703676, where
15 libspectre reportedly failed to build against an old installed
16 instance of ghostscript-gpl.
17
18 Bug: https://bugs.gentoo.org/199856
19 Bug: https://bugs.gentoo.org/690436
20 Bug: https://bugs.gentoo.org/703676
21 ---
22 lib/_emerge/depgraph.py | 91 +++++++++++--------
23 .../tests/resolver/test_merge_order.py | 25 ++++-
24 2 files changed, 75 insertions(+), 41 deletions(-)
25
26 diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
27 index ed7aeccad..58255681c 100644
28 --- a/lib/_emerge/depgraph.py
29 +++ b/lib/_emerge/depgraph.py
30 @@ -7636,21 +7636,6 @@ class depgraph(object):
31 break
32 removed_nodes.clear()
33 self._merge_order_bias(mygraph)
34 - def cmp_circular_bias(n1, n2):
35 - """
36 - RDEPEND is stronger than PDEPEND and this function
37 - measures such a strength bias within a circular
38 - dependency relationship.
39 - """
40 - n1_n2_medium = n2 in mygraph.child_nodes(n1,
41 - ignore_priority=priority_range.ignore_medium_soft)
42 - n2_n1_medium = n1 in mygraph.child_nodes(n2,
43 - ignore_priority=priority_range.ignore_medium_soft)
44 - if n1_n2_medium == n2_n1_medium:
45 - return 0
46 - elif n1_n2_medium:
47 - return 1
48 - return -1
49 myblocker_uninstalls = self._dynamic_config._blocker_uninstalls.copy()
50 retlist=[]
51 # Contains uninstall tasks that have been scheduled to
52 @@ -7811,7 +7796,8 @@ class depgraph(object):
53 self._spinner_update()
54 selected_nodes = None
55 ignore_priority = None
56 - if drop_satisfied or (prefer_asap and asap_nodes):
57 + cycle_digraph = None
58 + if prefer_asap and asap_nodes:
59 priority_range = DepPrioritySatisfiedRange
60 else:
61 priority_range = DepPriorityNormalRange
62 @@ -7893,11 +7879,12 @@ class depgraph(object):
63 break
64
65 if not selected_nodes:
66 - nodes = get_nodes(ignore_priority=priority_range.ignore_medium)
67 - if nodes:
68 - mergeable_nodes = set(nodes)
69 +
70 + def find_smallest_cycle(mergeable_nodes, priority_ranges):
71 if prefer_asap and asap_nodes:
72 nodes = asap_nodes
73 + else:
74 + nodes = mergeable_nodes
75 # When gathering the nodes belonging to a runtime cycle,
76 # we want to minimize the number of nodes gathered, since
77 # this tends to produce a more optimal merge order.
78 @@ -7908,21 +7895,44 @@ class depgraph(object):
79 # that depend on them. Therefore, we search for the
80 # smallest cycle in order to try and identify and prefer
81 # these smaller independent cycles.
82 - ignore_priority = priority_range.ignore_medium_soft
83 smallest_cycle = None
84 + ignore_priority = None
85 for node in nodes:
86 if not mygraph.parent_nodes(node):
87 continue
88 - selected_nodes = set()
89 - if gather_deps(ignore_priority,
90 - mergeable_nodes, selected_nodes, node):
91 - if smallest_cycle is None or \
92 - len(selected_nodes) < len(smallest_cycle):
93 - smallest_cycle = selected_nodes
94 + for local_priority_range in priority_ranges:
95 + selected_nodes = set()
96 + if gather_deps(local_priority_range.ignore_medium_soft,
97 + mergeable_nodes, selected_nodes, node):
98 + if smallest_cycle is None or \
99 + len(selected_nodes) < len(smallest_cycle):
100 + smallest_cycle = selected_nodes
101 + ignore_priority = local_priority_range.ignore_medium_soft
102 + break
103 +
104 + return smallest_cycle, ignore_priority
105
106 - selected_nodes = smallest_cycle
107 + priority_ranges = []
108 + if priority_range is not DepPriorityNormalRange:
109 + priority_ranges.append(DepPriorityNormalRange)
110 + priority_ranges.append(priority_range)
111 + if drop_satisfied and priority_range is not DepPrioritySatisfiedRange:
112 + priority_ranges.append(DepPrioritySatisfiedRange)
113
114 - if selected_nodes is not None:
115 + for local_priority_range in priority_ranges:
116 + mergeable_nodes = set(get_nodes(ignore_priority=local_priority_range.ignore_medium))
117 + if mergeable_nodes:
118 + selected_nodes, ignore_priority = find_smallest_cycle(mergeable_nodes, priority_ranges)
119 + if selected_nodes:
120 + break
121 +
122 + if not selected_nodes:
123 + if prefer_asap and asap_nodes:
124 + # We failed to find any asap nodes to merge, so ignore
125 + # them for the next iteration.
126 + prefer_asap = False
127 + continue
128 + else:
129 cycle_digraph = mygraph.copy()
130 cycle_digraph.difference_update([x for x in
131 cycle_digraph if x not in selected_nodes])
132 @@ -7949,12 +7959,6 @@ class depgraph(object):
133 writemsg("runtime cycle leaf: %s\n\n" %
134 (selected_nodes[0],), noiselevel=-1)
135
136 - if prefer_asap and asap_nodes and not selected_nodes:
137 - # We failed to find any asap nodes to merge, so ignore
138 - # them for the next iteration.
139 - prefer_asap = False
140 - continue
141 -
142 if selected_nodes and ignore_priority is not None:
143 # Try to merge ignored medium_soft deps as soon as possible
144 # if they're not satisfied by installed packages.
145 @@ -7976,10 +7980,21 @@ class depgraph(object):
146 # Merge PDEPEND asap for bug #180045.
147 asap_nodes.append(child)
148
149 - if selected_nodes and len(selected_nodes) > 1:
150 - if not isinstance(selected_nodes, list):
151 - selected_nodes = list(selected_nodes)
152 - selected_nodes.sort(key=cmp_sort_key(cmp_circular_bias))
153 + if selected_nodes and len(selected_nodes) > 1 and cycle_digraph is not None:
154 + # Sort nodes to account for direct circular relationships. Relevant
155 + # priorities here are: runtime < buildtime < buildtime slot operator
156 + ignore_priorities = [lambda priority: not priority.buildtime, lambda priority: not priority.buildtime_slot_op]
157 + selected_nodes = []
158 + while cycle_digraph:
159 + for ignore_priority in ignore_priorities:
160 + leaves = cycle_digraph.leaf_nodes(ignore_priority=ignore_priority)
161 + if leaves:
162 + cycle_digraph.difference_update(leaves)
163 + selected_nodes.extend(leaves)
164 + break
165 + else:
166 + selected_nodes.extend(cycle_digraph)
167 + break
168
169 if not selected_nodes and myblocker_uninstalls:
170 # An Uninstall task needs to be executed in order to
171 diff --git a/lib/portage/tests/resolver/test_merge_order.py b/lib/portage/tests/resolver/test_merge_order.py
172 index 74e826661..11752d71e 100644
173 --- a/lib/portage/tests/resolver/test_merge_order.py
174 +++ b/lib/portage/tests/resolver/test_merge_order.py
175 @@ -81,6 +81,13 @@ class MergeOrderTestCase(TestCase):
176 "DEPEND": "app-misc/circ-satisfied-a",
177 "RDEPEND": "app-misc/circ-satisfied-a",
178 },
179 + "app-misc/circ-direct-a-1": {
180 + "RDEPEND": "app-misc/circ-direct-b",
181 + },
182 + "app-misc/circ-direct-b-1": {
183 + "RDEPEND": "app-misc/circ-direct-a",
184 + "DEPEND": "app-misc/circ-direct-a",
185 + },
186 "app-misc/circ-smallest-a-1": {
187 "RDEPEND": "app-misc/circ-smallest-b",
188 },
189 @@ -220,6 +227,13 @@ class MergeOrderTestCase(TestCase):
190 }
191
192 installed = {
193 + "app-misc/circ-direct-a-1": {
194 + "RDEPEND": "app-misc/circ-direct-b",
195 + },
196 + "app-misc/circ-direct-b-1": {
197 + "RDEPEND": "app-misc/circ-direct-a",
198 + "DEPEND": "app-misc/circ-direct-a",
199 + },
200 "app-misc/circ-buildtime-a-0": {},
201 "app-misc/circ-satisfied-a-0": {
202 "RDEPEND": "app-misc/circ-satisfied-b",
203 @@ -295,6 +309,12 @@ class MergeOrderTestCase(TestCase):
204 }
205
206 test_cases = (
207 + ResolverPlaygroundTestCase(
208 + ["app-misc/circ-direct-a", "app-misc/circ-direct-b"],
209 + success = True,
210 + all_permutations = True,
211 + mergelist = ["app-misc/circ-direct-a-1", "app-misc/circ-direct-b-1"],
212 + ),
213 ResolverPlaygroundTestCase(
214 ["app-misc/some-app-a"],
215 success = True,
216 @@ -321,9 +341,8 @@ class MergeOrderTestCase(TestCase):
217 ambiguous_merge_order = True,
218 # The following merge order assertion reflects optimal order for
219 # a circular relationship which is DEPEND in one direction and
220 - # RDEPEND in the other. The assertion currently fails, and the
221 - # patch for bug 690436 will fix it.
222 - #merge_order_assertions = (("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),),
223 + # RDEPEND in the other.
224 + merge_order_assertions = (("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),),
225 mergelist = [("app-misc/circ-buildtime-b-1", "app-misc/circ-buildtime-c-1", "app-misc/circ-buildtime-a-1"), "app-misc/some-app-c-1"]),
226 # Test optimal merge order for a circular dep that is
227 # RDEPEND in one direction and PDEPEND in the other.
228 --
229 2.21.0