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 |