1 |
Author: zmedico |
2 |
Date: 2008-04-25 01:53:44 +0000 (Fri, 25 Apr 2008) |
3 |
New Revision: 9965 |
4 |
|
5 |
Modified: |
6 |
main/trunk/doc/dependency_resolution/task_scheduling.docbook |
7 |
main/trunk/pym/_emerge/__init__.py |
8 |
Log: |
9 |
Bug #172812 - If any Uninstall tasks need to be executed in order |
10 |
to avoid a conflict, complete the graph with any dependencies that |
11 |
may have been initially neglected (to ensure that unsafe Uninstall |
12 |
tasks are properly identified and blocked from execution). |
13 |
|
14 |
|
15 |
Modified: main/trunk/doc/dependency_resolution/task_scheduling.docbook |
16 |
=================================================================== |
17 |
--- main/trunk/doc/dependency_resolution/task_scheduling.docbook 2008-04-24 20:43:16 UTC (rev 9964) |
18 |
+++ main/trunk/doc/dependency_resolution/task_scheduling.docbook 2008-04-25 01:53:44 UTC (rev 9965) |
19 |
@@ -29,13 +29,14 @@ |
20 |
Installed packages that have been pulled into the current dependency |
21 |
graph will not be uninstalled. Due to |
22 |
<link linkend='dependency-resolution-package-modeling-dependency-neglection'> |
23 |
- dependency neglection</link>, other checks may be necessary in order |
24 |
+ dependency neglection</link> and special properties of packages |
25 |
+ in the "system" set, other checks may be necessary in order |
26 |
to protect inappropriate packages from being uninstalled. |
27 |
</listitem> |
28 |
<listitem> |
29 |
An installed package that is matched by a dependency atom from the |
30 |
"system" set will not be uninstalled in advance since it might not |
31 |
- be safe. Such a package will be uninstalled through replacement. |
32 |
+ be safe. Such a package will be only uninstalled through replacement. |
33 |
</listitem> |
34 |
<listitem> |
35 |
An installed package that is matched by a dependency atom from the |
36 |
|
37 |
Modified: main/trunk/pym/_emerge/__init__.py |
38 |
=================================================================== |
39 |
--- main/trunk/pym/_emerge/__init__.py 2008-04-24 20:43:16 UTC (rev 9964) |
40 |
+++ main/trunk/pym/_emerge/__init__.py 2008-04-25 01:53:44 UTC (rev 9965) |
41 |
@@ -1711,7 +1711,7 @@ |
42 |
# Slot collision nodes are not allowed to block other packages since |
43 |
# blocker validation is only able to account for one package per slot. |
44 |
self._slot_collision_nodes = set() |
45 |
- self._altlist_cache = {} |
46 |
+ self._serialized_tasks_cache = None |
47 |
self._pprovided_args = [] |
48 |
self._missing_args = [] |
49 |
self._masked_installed = [] |
50 |
@@ -1858,7 +1858,6 @@ |
51 |
nodeps = "--nodeps" in self.myopts |
52 |
empty = "empty" in self.myparams |
53 |
deep = "deep" in self.myparams |
54 |
- complete = "complete" in self.myparams |
55 |
update = "--update" in self.myopts and dep.depth <= 1 |
56 |
if dep.blocker: |
57 |
if not buildpkgonly and \ |
58 |
@@ -1902,8 +1901,7 @@ |
59 |
# should have been masked. |
60 |
raise |
61 |
if not myarg: |
62 |
- if complete: |
63 |
- self._ignored_deps.append(dep) |
64 |
+ self._ignored_deps.append(dep) |
65 |
return 1 |
66 |
|
67 |
if not self._add_pkg(dep_pkg, dep.parent, |
68 |
@@ -2050,8 +2048,6 @@ |
69 |
return 1 |
70 |
elif pkg.installed and \ |
71 |
"deep" not in self.myparams: |
72 |
- if "complete" not in self.myparams: |
73 |
- return 1 |
74 |
dep_stack = self._ignored_deps |
75 |
|
76 |
self.spinner.update() |
77 |
@@ -2567,7 +2563,7 @@ |
78 |
|
79 |
if not self.validate_blockers(): |
80 |
return False, myfavorites |
81 |
- |
82 |
+ |
83 |
# We're true here unless we are missing binaries. |
84 |
return (not missing,myfavorites) |
85 |
|
86 |
@@ -3233,9 +3229,17 @@ |
87 |
blocker, set()).add(parent) |
88 |
if not self.blocker_parents[blocker]: |
89 |
del self.blocker_parents[blocker] |
90 |
- # Validate blockers that depend on merge order. |
91 |
- if not self.blocker_digraph.empty(): |
92 |
+ |
93 |
+ # This checks whether or not it's possible to resolve blocker |
94 |
+ # conflicts that depend on installation order or require |
95 |
+ # uninstallation of a currently installed package. Note that |
96 |
+ # this can lead to the current method being called recursively |
97 |
+ # if changes to the dependency graph are required. |
98 |
+ try: |
99 |
self.altlist() |
100 |
+ except self._unknown_internal_error: |
101 |
+ return False |
102 |
+ |
103 |
if self._slot_collision_info: |
104 |
# The user is only notified of a slot collision if there are no |
105 |
# unresolvable blocks. |
106 |
@@ -3267,13 +3271,19 @@ |
107 |
mygraph.order.sort(cmp_merge_preference) |
108 |
|
109 |
def altlist(self, reversed=False): |
110 |
- if reversed in self._altlist_cache: |
111 |
- return self._altlist_cache[reversed][:] |
112 |
+ |
113 |
+ while self._serialized_tasks_cache is None: |
114 |
+ try: |
115 |
+ self._serialized_tasks_cache = self._serialize_tasks() |
116 |
+ except self._serialize_tasks_retry: |
117 |
+ pass |
118 |
+ |
119 |
+ retlist = self._serialized_tasks_cache[:] |
120 |
if reversed: |
121 |
- retlist = self.altlist() |
122 |
retlist.reverse() |
123 |
- self._altlist_cache[reversed] = retlist[:] |
124 |
- return retlist |
125 |
+ return retlist |
126 |
+ |
127 |
+ def _serialize_tasks(self): |
128 |
mygraph=self.digraph.copy() |
129 |
# Prune "nomerge" root nodes if nothing depends on them, since |
130 |
# otherwise they slow down merge order calculation. Don't remove |
131 |
@@ -3311,7 +3321,11 @@ |
132 |
# correspond to blocker conflicts that could not be |
133 |
# resolved. |
134 |
ignored_uninstall_tasks = set() |
135 |
- blocker_deps = None |
136 |
+ have_uninstall_task = False |
137 |
+ complete = "complete" in self.myparams |
138 |
+ myblocker_parents = self.blocker_parents.copy() |
139 |
+ for k, v in myblocker_parents.iteritems(): |
140 |
+ myblocker_parents[k] = v.copy() |
141 |
asap_nodes = [] |
142 |
portage_node = None |
143 |
def get_nodes(**kwargs): |
144 |
@@ -3501,43 +3515,54 @@ |
145 |
inst_pkg = self._pkg_cache[ |
146 |
("installed", task.root, task.cpv, "nomerge")] |
147 |
|
148 |
- # For packages in the system set, don't take |
149 |
- # any chances. If the conflict can't be resolved |
150 |
- # by a normal upgrade operation then require |
151 |
- # user intervention. |
152 |
- skip = False |
153 |
- try: |
154 |
- for atom in root_config.sets[ |
155 |
- "system"].iterAtomsForPackage(task): |
156 |
- skip = True |
157 |
- break |
158 |
- except portage.exception.InvalidDependString: |
159 |
- skip = True |
160 |
- if skip: |
161 |
+ if self.digraph.contains(inst_pkg): |
162 |
continue |
163 |
|
164 |
- # For packages in the world set, go ahead an uninstall |
165 |
- # when necessary, as long as the atom will be satisfied |
166 |
- # in the final state. |
167 |
- graph_db = self.mydbapi[task.root] |
168 |
- try: |
169 |
- for atom in root_config.sets[ |
170 |
- "world"].iterAtomsForPackage(task): |
171 |
- satisfied = False |
172 |
- for cpv in graph_db.match(atom): |
173 |
- if cpv == inst_pkg.cpv and \ |
174 |
- inst_pkg in graph_db: |
175 |
- continue |
176 |
- satisfied = True |
177 |
- break |
178 |
- if not satisfied: |
179 |
+ if "/" == task.root: |
180 |
+ # For packages in the system set, don't take |
181 |
+ # any chances. If the conflict can't be resolved |
182 |
+ # by a normal replacement operation then abort. |
183 |
+ skip = False |
184 |
+ try: |
185 |
+ for atom in root_config.sets[ |
186 |
+ "system"].iterAtomsForPackage(task): |
187 |
skip = True |
188 |
break |
189 |
- except portage.exception.InvalidDependString: |
190 |
- skip = True |
191 |
- if skip: |
192 |
- continue |
193 |
+ except portage.exception.InvalidDependString: |
194 |
+ skip = True |
195 |
+ if skip: |
196 |
+ continue |
197 |
|
198 |
+ # Note that the world check isn't always |
199 |
+ # necessary since self._complete_graph() will |
200 |
+ # add all packages from the system and world sets to the |
201 |
+ # graph. This just allows unresolved conflicts to be |
202 |
+ # detected as early as possible, which makes it possible |
203 |
+ # to avoid calling self._complete_graph() when it is |
204 |
+ # unnecessary due to blockers triggering an abortion. |
205 |
+ if not complete: |
206 |
+ # For packages in the world set, go ahead an uninstall |
207 |
+ # when necessary, as long as the atom will be satisfied |
208 |
+ # in the final state. |
209 |
+ graph_db = self.mydbapi[task.root] |
210 |
+ try: |
211 |
+ for atom in root_config.sets[ |
212 |
+ "world"].iterAtomsForPackage(task): |
213 |
+ satisfied = False |
214 |
+ for cpv in graph_db.match(atom): |
215 |
+ if cpv == inst_pkg.cpv and \ |
216 |
+ inst_pkg in graph_db: |
217 |
+ continue |
218 |
+ satisfied = True |
219 |
+ break |
220 |
+ if not satisfied: |
221 |
+ skip = True |
222 |
+ break |
223 |
+ except portage.exception.InvalidDependString: |
224 |
+ skip = True |
225 |
+ if skip: |
226 |
+ continue |
227 |
+ |
228 |
# Check the deps of parent nodes to ensure that |
229 |
# the chosen task produces a leaf node. Maybe |
230 |
# this can be optimized some more to make the |
231 |
@@ -3629,6 +3654,7 @@ |
232 |
# and uninstallation tasks. |
233 |
uninst_task = None |
234 |
if isinstance(node, Uninstall): |
235 |
+ have_uninstall_task = True |
236 |
uninst_task = node |
237 |
else: |
238 |
vardb = self.trees[node.root]["vartree"].dbapi |
239 |
@@ -3654,24 +3680,32 @@ |
240 |
unresolved = \ |
241 |
self._unresolved_blocker_parents.get(blocker) |
242 |
if unresolved: |
243 |
- self.blocker_parents[blocker] = unresolved |
244 |
+ myblocker_parents[blocker] = unresolved |
245 |
else: |
246 |
- del self.blocker_parents[blocker] |
247 |
+ del myblocker_parents[blocker] |
248 |
|
249 |
if node[-1] != "nomerge": |
250 |
retlist.append(list(node)) |
251 |
mygraph.remove(node) |
252 |
|
253 |
- if not reversed: |
254 |
- """Blocker validation does not work with reverse mode, |
255 |
- so self.altlist() should first be called with reverse disabled |
256 |
- so that blockers are properly validated.""" |
257 |
- self.blocker_digraph = myblockers |
258 |
+ for blocker in myblocker_parents: |
259 |
+ retlist.append(list(blocker)) |
260 |
|
261 |
- """ Add any unresolved blocks so that they can be displayed.""" |
262 |
- for blocker in self.blocker_parents: |
263 |
- retlist.append(list(blocker)) |
264 |
- self._altlist_cache[reversed] = retlist[:] |
265 |
+ # If any Uninstall tasks need to be executed in order |
266 |
+ # to avoid a conflict, complete the graph with any |
267 |
+ # dependencies that may have been initially |
268 |
+ # neglected (to ensure that unsafe Uninstall tasks |
269 |
+ # are properly identified and blocked from execution). |
270 |
+ if have_uninstall_task and \ |
271 |
+ not complete and \ |
272 |
+ not myblocker_parents: |
273 |
+ self.myparams.add("complete") |
274 |
+ if not self._complete_graph(): |
275 |
+ raise self._unknown_internal_error("") |
276 |
+ if not self.validate_blockers(): |
277 |
+ raise self._unknown_internal_error("") |
278 |
+ raise self._serialize_tasks_retry("") |
279 |
+ |
280 |
return retlist |
281 |
|
282 |
def display(self, mylist, favorites=[], verbosity=None): |
283 |
@@ -4562,6 +4596,22 @@ |
284 |
fakedb[myroot].cpv_inject(pkg) |
285 |
self.spinner.update() |
286 |
|
287 |
+ class _unknown_internal_error(portage.exception.PortageException): |
288 |
+ """ |
289 |
+ Used by the depgraph internally to terminate graph creation. |
290 |
+ The specific reason for the failure should have been dumped |
291 |
+ to stderr, unfortunately, the exact reason for the failure |
292 |
+ may not be known. |
293 |
+ """ |
294 |
+ |
295 |
+ class _serialize_tasks_retry(portage.exception.PortageException): |
296 |
+ """ |
297 |
+ This is raised by the _serialize_tasks() method when it needs to |
298 |
+ be called again for some reason. The only case that it's currently |
299 |
+ used for is when neglected dependencies need to be added to the |
300 |
+ graph in order to avoid making a potentially unsafe decision. |
301 |
+ """ |
302 |
+ |
303 |
class _dep_check_composite_db(portage.dbapi): |
304 |
""" |
305 |
A dbapi-like interface that is optimized for use in dep_check() calls. |
306 |
|
307 |
-- |
308 |
gentoo-commits@l.g.o mailing list |