1 |
commit: 15a799b52155a47568f4b049ff8487a2718b270c |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Wed Oct 3 09:31:41 2012 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Wed Oct 3 09:36:50 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=15a799b5 |
7 |
|
8 |
egencache: add --update-manifests, bug #436918 |
9 |
|
10 |
Update manifest files, and sign them if signing is enabled. This |
11 |
supports parallelization if enabled via the --jobs option. The |
12 |
--thin-manifests and --sign-manifests options may be used to manually |
13 |
override layout.conf settings. There's also a new --strict-manifests |
14 |
option that may be used to manually override the "strict" FEATURES |
15 |
setting, a --gpg-key option to override PORTAGE_GPG_KEY, and a |
16 |
--gpg-dir option to override PORTAGE_GPG_DIR. |
17 |
|
18 |
--- |
19 |
bin/egencache | 160 +++++++++++++++++++- |
20 |
man/egencache.1 | 21 +++ |
21 |
pym/_emerge/PollScheduler.py | 6 +- |
22 |
pym/portage/manifest.py | 10 +- |
23 |
.../ebuild/_parallel_manifest/ManifestProcess.py | 43 ++++++ |
24 |
.../ebuild/_parallel_manifest/ManifestScheduler.py | 79 ++++++++++ |
25 |
.../ebuild/_parallel_manifest/ManifestTask.py | 75 +++++++++ |
26 |
.../package/ebuild/_parallel_manifest/__init__.py | 2 + |
27 |
pym/portage/util/_async/AsyncScheduler.py | 88 +++++++++++ |
28 |
pym/portage/util/_async/ForkProcess.py | 48 ++++++ |
29 |
pym/portage/util/_async/__init__.py | 2 + |
30 |
11 files changed, 527 insertions(+), 7 deletions(-) |
31 |
|
32 |
diff --git a/bin/egencache b/bin/egencache |
33 |
index ec62a8c..a72fff7 100755 |
34 |
--- a/bin/egencache |
35 |
+++ b/bin/egencache |
36 |
@@ -4,6 +4,7 @@ |
37 |
|
38 |
from __future__ import print_function |
39 |
|
40 |
+import platform |
41 |
import signal |
42 |
import sys |
43 |
# This block ensures that ^C interrupts are handled quietly. |
44 |
@@ -20,6 +21,17 @@ try: |
45 |
except KeyboardInterrupt: |
46 |
sys.exit(128 + signal.SIGINT) |
47 |
|
48 |
+def debug_signal(signum, frame): |
49 |
+ import pdb |
50 |
+ pdb.set_trace() |
51 |
+ |
52 |
+if platform.python_implementation() == 'Jython': |
53 |
+ debug_signum = signal.SIGUSR2 # bug #424259 |
54 |
+else: |
55 |
+ debug_signum = signal.SIGUSR1 |
56 |
+ |
57 |
+signal.signal(debug_signum, debug_signal) |
58 |
+ |
59 |
import io |
60 |
import logging |
61 |
import optparse |
62 |
@@ -36,7 +48,9 @@ from portage import os, _encodings, _unicode_encode, _unicode_decode |
63 |
from _emerge.MetadataRegen import MetadataRegen |
64 |
from portage.cache.cache_errors import CacheError, StatCollision |
65 |
from portage.manifest import guessManifestFileType |
66 |
+from portage.package.ebuild._parallel_manifest.ManifestScheduler import ManifestScheduler |
67 |
from portage.util import cmp_sort_key, writemsg_level |
68 |
+from portage.util._eventloop.global_event_loop import global_event_loop |
69 |
from portage import cpv_getkey |
70 |
from portage.dep import Atom, isjustname |
71 |
from portage.versions import pkgsplit, vercmp |
72 |
@@ -72,6 +86,9 @@ def parse_args(args): |
73 |
actions.add_option("--update-changelogs", |
74 |
action="store_true", |
75 |
help="update the ChangeLog files from SCM logs") |
76 |
+ actions.add_option("--update-manifests", |
77 |
+ action="store_true", |
78 |
+ help="update manifests") |
79 |
parser.add_option_group(actions) |
80 |
|
81 |
common = optparse.OptionGroup(parser, 'Common options') |
82 |
@@ -81,12 +98,33 @@ def parse_args(args): |
83 |
common.add_option("--config-root", |
84 |
help="location of portage config files", |
85 |
dest="portage_configroot") |
86 |
+ common.add_option("--gpg-dir", |
87 |
+ help="override the PORTAGE_GPG_DIR variable", |
88 |
+ dest="gpg_dir") |
89 |
+ common.add_option("--gpg-key", |
90 |
+ help="override the PORTAGE_GPG_KEY variable", |
91 |
+ dest="gpg_key") |
92 |
common.add_option("--portdir", |
93 |
help="override the portage tree location", |
94 |
dest="portdir") |
95 |
common.add_option("--portdir-overlay", |
96 |
help="override the PORTDIR_OVERLAY variable (requires that --repo is also specified)", |
97 |
dest="portdir_overlay") |
98 |
+ common.add_option("--sign-manifests", |
99 |
+ type="choice", |
100 |
+ choices=('y', 'n'), |
101 |
+ metavar="<y|n>", |
102 |
+ help="manually override layout.conf sign-manifests setting") |
103 |
+ common.add_option("--strict-manifests", |
104 |
+ type="choice", |
105 |
+ choices=('y', 'n'), |
106 |
+ metavar="<y|n>", |
107 |
+ help="manually override \"strict\" FEATURES setting") |
108 |
+ common.add_option("--thin-manifests", |
109 |
+ type="choice", |
110 |
+ choices=('y', 'n'), |
111 |
+ metavar="<y|n>", |
112 |
+ help="manually override layout.conf thin-manifests setting") |
113 |
common.add_option("--tolerant", |
114 |
action="store_true", |
115 |
help="exit successfully if only minor errors occurred") |
116 |
@@ -865,8 +903,8 @@ def egencache_main(args): |
117 |
settings = portage.config(config_root=config_root, |
118 |
local_config=False, env=env) |
119 |
|
120 |
- if not options.update and not options.update_use_local_desc \ |
121 |
- and not options.update_changelogs: |
122 |
+ if not (options.update or options.update_use_local_desc or |
123 |
+ options.update_changelogs or options.update_manifests): |
124 |
parser.error('No action specified') |
125 |
return 1 |
126 |
|
127 |
@@ -883,10 +921,17 @@ def egencache_main(args): |
128 |
parser.error("PORTDIR is undefined") |
129 |
return 1 |
130 |
|
131 |
+ repo_config = settings.repositories.get_repo_for_location(repo_path) |
132 |
+ |
133 |
+ if options.strict_manifests is not None: |
134 |
+ if options.strict_manifests == "y": |
135 |
+ settings.features.add("strict") |
136 |
+ else: |
137 |
+ settings.features.add("discard") |
138 |
+ |
139 |
if options.update and 'metadata-transfer' not in settings.features: |
140 |
# Forcibly enable metadata-transfer if portdbapi has a pregenerated |
141 |
# cache that does not support eclass validation. |
142 |
- repo_config = settings.repositories.get_repo_for_location(repo_path) |
143 |
cache = repo_config.get_pregenerated_cache( |
144 |
portage.dbapi.dbapi._known_keys, readonly=True) |
145 |
if cache is not None and not cache.complete_eclass_entries: |
146 |
@@ -915,6 +960,69 @@ def egencache_main(args): |
147 |
level=logging.ERROR, noiselevel=-1) |
148 |
return 1 |
149 |
|
150 |
+ if options.sign_manifests is not None: |
151 |
+ repo_config.sign_manifest = options.sign_manifests == 'y' |
152 |
+ |
153 |
+ if options.thin_manifests is not None: |
154 |
+ repo_config.thin_manifest = options.thin_manifests == 'y' |
155 |
+ |
156 |
+ gpg_cmd = None |
157 |
+ gpg_vars = None |
158 |
+ |
159 |
+ if options.update_manifests: |
160 |
+ if repo_config.sign_manifest: |
161 |
+ |
162 |
+ sign_problem = False |
163 |
+ gpg_dir = None |
164 |
+ gpg_cmd = settings.get("PORTAGE_GPG_SIGNING_COMMAND") |
165 |
+ if gpg_cmd is None: |
166 |
+ writemsg_level("egencache: error: " |
167 |
+ "PORTAGE_GPG_SIGNING_COMMAND is unset! " |
168 |
+ "Is make.globals missing?\n", |
169 |
+ level=logging.ERROR, noiselevel=-1) |
170 |
+ sign_problem = True |
171 |
+ elif "${PORTAGE_GPG_KEY}" in gpg_cmd and \ |
172 |
+ options.gpg_key is None and \ |
173 |
+ "PORTAGE_GPG_KEY" not in settings: |
174 |
+ writemsg_level("egencache: error: " |
175 |
+ "PORTAGE_GPG_KEY is unset!\n", |
176 |
+ level=logging.ERROR, noiselevel=-1) |
177 |
+ sign_problem = True |
178 |
+ elif "${PORTAGE_GPG_DIR}" in gpg_cmd: |
179 |
+ if options.gpg_dir is not None: |
180 |
+ gpg_dir = options.gpg_dir |
181 |
+ elif "PORTAGE_GPG_DIR" not in settings: |
182 |
+ gpg_dir = os.path.expanduser("~/.gnupg") |
183 |
+ else: |
184 |
+ gpg_dir = os.path.expanduser(settings["PORTAGE_GPG_DIR"]) |
185 |
+ if not os.access(gpg_dir, os.X_OK): |
186 |
+ writemsg_level(("egencache: error: " |
187 |
+ "Unable to access directory: " |
188 |
+ "PORTAGE_GPG_DIR='%s'\n") % gpg_dir, |
189 |
+ level=logging.ERROR, noiselevel=-1) |
190 |
+ sign_problem = True |
191 |
+ |
192 |
+ if sign_problem: |
193 |
+ writemsg_level("egencache: You may disable manifest " |
194 |
+ "signatures with --sign-manifests=n or by setting " |
195 |
+ "\"sign-manifests = false\" in metadata/layout.conf\n", |
196 |
+ level=logging.ERROR, noiselevel=-1) |
197 |
+ return 1 |
198 |
+ |
199 |
+ gpg_vars = {} |
200 |
+ if gpg_dir is not None: |
201 |
+ gpg_vars["PORTAGE_GPG_DIR"] = gpg_dir |
202 |
+ gpg_var_names = [] |
203 |
+ if options.gpg_key is None: |
204 |
+ gpg_var_names.append("PORTAGE_GPG_KEY") |
205 |
+ else: |
206 |
+ gpg_vars["PORTAGE_GPG_KEY"] = options.gpg_key |
207 |
+ |
208 |
+ for k in gpg_var_names: |
209 |
+ v = settings.get(k) |
210 |
+ if v is not None: |
211 |
+ gpg_vars[k] = v |
212 |
+ |
213 |
ret = [os.EX_OK] |
214 |
|
215 |
if options.update: |
216 |
@@ -932,6 +1040,52 @@ def egencache_main(args): |
217 |
else: |
218 |
ret.append(gen_cache.returncode) |
219 |
|
220 |
+ if options.update_manifests: |
221 |
+ |
222 |
+ cp_iter = None |
223 |
+ if atoms: |
224 |
+ cp_iter = iter(atoms) |
225 |
+ |
226 |
+ event_loop = global_event_loop() |
227 |
+ scheduler = ManifestScheduler(portdb, cp_iter=cp_iter, |
228 |
+ gpg_cmd=gpg_cmd, gpg_vars=gpg_vars, |
229 |
+ max_jobs=options.jobs, |
230 |
+ max_load=options.load_average, |
231 |
+ event_loop=event_loop) |
232 |
+ |
233 |
+ received_signal = [] |
234 |
+ |
235 |
+ def sighandler(signum, frame): |
236 |
+ signal.signal(signal.SIGINT, signal.SIG_IGN) |
237 |
+ signal.signal(signal.SIGTERM, signal.SIG_IGN) |
238 |
+ received_signal.append(128 + signum) |
239 |
+ scheduler.terminate() |
240 |
+ |
241 |
+ earlier_sigint_handler = signal.signal(signal.SIGINT, sighandler) |
242 |
+ earlier_sigterm_handler = signal.signal(signal.SIGTERM, sighandler) |
243 |
+ |
244 |
+ try: |
245 |
+ scheduler.start() |
246 |
+ scheduler.wait() |
247 |
+ finally: |
248 |
+ # Restore previous handlers |
249 |
+ if earlier_sigint_handler is not None: |
250 |
+ signal.signal(signal.SIGINT, earlier_sigint_handler) |
251 |
+ else: |
252 |
+ signal.signal(signal.SIGINT, signal.SIG_DFL) |
253 |
+ if earlier_sigterm_handler is not None: |
254 |
+ signal.signal(signal.SIGTERM, earlier_sigterm_handler) |
255 |
+ else: |
256 |
+ signal.signal(signal.SIGTERM, signal.SIG_DFL) |
257 |
+ |
258 |
+ if received_signal: |
259 |
+ sys.exit(received_signal[0]) |
260 |
+ |
261 |
+ if options.tolerant: |
262 |
+ ret.append(os.EX_OK) |
263 |
+ else: |
264 |
+ ret.append(scheduler.returncode) |
265 |
+ |
266 |
if options.update_use_local_desc: |
267 |
gen_desc = GenUseLocalDesc(portdb, |
268 |
output=options.uld_output, |
269 |
|
270 |
diff --git a/man/egencache.1 b/man/egencache.1 |
271 |
index 9094595..bc5db67 100644 |
272 |
--- a/man/egencache.1 |
273 |
+++ b/man/egencache.1 |
274 |
@@ -20,6 +20,12 @@ Update the ChangeLog files from SCM logs (supported only in git repos). |
275 |
.TP |
276 |
.BR "\-\-update\-use\-local\-desc" |
277 |
Update the \fIprofiles/use.local.desc\fR file from metadata.xml. |
278 |
+.TP |
279 |
+.BR "\-\-update\-manifests" |
280 |
+Update manifest files, and sign them if signing is enabled. This supports |
281 |
+parallelization if enabled via the \-\-jobs option. The \-\-thin\-manifests |
282 |
+and \-\-sign\-manifests options may be used to manually override layout.conf |
283 |
+settings. |
284 |
.SH OPTIONS |
285 |
.TP |
286 |
.BR "\-\-cache\-dir=CACHE_DIR" |
287 |
@@ -34,6 +40,12 @@ Location of portage config files. |
288 |
.br |
289 |
Defaults to /. |
290 |
.TP |
291 |
+.BR "\-\-gpg\-dir" |
292 |
+Override the PORTAGE_GPG_DIR variable. |
293 |
+.TP |
294 |
+.BR "\-\-gpg\-key" |
295 |
+Override the PORTAGE_GPG_KEY variable. |
296 |
+.TP |
297 |
.BR "\-\-ignore-default-opts" |
298 |
Causes \fIEGENCACHE_DEFAULT_OPTS\fR to be ignored. |
299 |
.TP |
300 |
@@ -72,6 +84,15 @@ This option should only be needed for distribution via something like |
301 |
more thorough mechanism which allows it to detect changed inode numbers |
302 |
(described in \fIracy-git.txt\fR in the git technical docs). |
303 |
.TP |
304 |
+.BR "\-\-sign\-manifests< y | n >" |
305 |
+Manually override layout.conf sign-manifests setting. |
306 |
+.TP |
307 |
+.BR "\-\-strict\-manifests< y | n >" |
308 |
+Manually override "strict" FEATURES setting. |
309 |
+.TP |
310 |
+.BR "\-\-thin\-manifests< y | n >" |
311 |
+Manually override layout.conf thin-manifests setting. |
312 |
+.TP |
313 |
.BR "\-\-tolerant" |
314 |
Exit successfully if only minor errors occurred, such as skipped cache |
315 |
updates due to ebuilds that either fail to source or are not sourced |
316 |
|
317 |
diff --git a/pym/_emerge/PollScheduler.py b/pym/_emerge/PollScheduler.py |
318 |
index 808fa6e..bcf80ab 100644 |
319 |
--- a/pym/_emerge/PollScheduler.py |
320 |
+++ b/pym/_emerge/PollScheduler.py |
321 |
@@ -30,7 +30,7 @@ class PollScheduler(object): |
322 |
"output", "register", "run", |
323 |
"source_remove", "timeout_add", "unregister") |
324 |
|
325 |
- def __init__(self, main=False): |
326 |
+ def __init__(self, main=False, event_loop=None): |
327 |
""" |
328 |
@param main: If True then use global_event_loop(), otherwise use |
329 |
a local EventLoop instance (default is False, for safe use in |
330 |
@@ -44,7 +44,9 @@ class PollScheduler(object): |
331 |
self._jobs = 0 |
332 |
self._scheduling = False |
333 |
self._background = False |
334 |
- if main: |
335 |
+ if event_loop is not None: |
336 |
+ self._event_loop = event_loop |
337 |
+ elif main: |
338 |
self._event_loop = global_event_loop() |
339 |
else: |
340 |
self._event_loop = EventLoop(main=False) |
341 |
|
342 |
diff --git a/pym/portage/manifest.py b/pym/portage/manifest.py |
343 |
index b81b580..9a85c8f 100644 |
344 |
--- a/pym/portage/manifest.py |
345 |
+++ b/pym/portage/manifest.py |
346 |
@@ -266,9 +266,12 @@ class Manifest(object): |
347 |
(MANIFEST2_REQUIRED_HASH, t, f)) |
348 |
|
349 |
def write(self, sign=False, force=False): |
350 |
- """ Write Manifest instance to disk, optionally signing it """ |
351 |
+ """ Write Manifest instance to disk, optionally signing it. Returns |
352 |
+ True if the Manifest is actually written, and False if the write |
353 |
+ is skipped due to existing Manifest being identical.""" |
354 |
+ rval = False |
355 |
if not self.allow_create: |
356 |
- return |
357 |
+ return rval |
358 |
self.checkIntegrity() |
359 |
try: |
360 |
myentries = list(self._createManifestEntries()) |
361 |
@@ -301,6 +304,7 @@ class Manifest(object): |
362 |
# non-empty for all currently known use cases. |
363 |
write_atomic(self.getFullname(), "".join("%s\n" % |
364 |
_unicode(myentry) for myentry in myentries)) |
365 |
+ rval = True |
366 |
else: |
367 |
# With thin manifest, there's no need to have |
368 |
# a Manifest file if there are no DIST entries. |
369 |
@@ -309,6 +313,7 @@ class Manifest(object): |
370 |
except OSError as e: |
371 |
if e.errno != errno.ENOENT: |
372 |
raise |
373 |
+ rval = True |
374 |
|
375 |
if sign: |
376 |
self.sign() |
377 |
@@ -316,6 +321,7 @@ class Manifest(object): |
378 |
if e.errno == errno.EACCES: |
379 |
raise PermissionDenied(str(e)) |
380 |
raise |
381 |
+ return rval |
382 |
|
383 |
def sign(self): |
384 |
""" Sign the Manifest """ |
385 |
|
386 |
diff --git a/pym/portage/package/ebuild/_parallel_manifest/ManifestProcess.py b/pym/portage/package/ebuild/_parallel_manifest/ManifestProcess.py |
387 |
new file mode 100644 |
388 |
index 0000000..44e2576 |
389 |
--- /dev/null |
390 |
+++ b/pym/portage/package/ebuild/_parallel_manifest/ManifestProcess.py |
391 |
@@ -0,0 +1,43 @@ |
392 |
+# Copyright 2012 Gentoo Foundation |
393 |
+# Distributed under the terms of the GNU General Public License v2 |
394 |
+ |
395 |
+import portage |
396 |
+from portage import os |
397 |
+from portage.exception import (FileNotFound, |
398 |
+ PermissionDenied, PortagePackageException) |
399 |
+from portage.localization import _ |
400 |
+from portage.util._async.ForkProcess import ForkProcess |
401 |
+ |
402 |
+class ManifestProcess(ForkProcess): |
403 |
+ |
404 |
+ __slots__ = ("cp", "distdir", "fetchlist_dict", "repo_config") |
405 |
+ |
406 |
+ MODIFIED = 16 |
407 |
+ |
408 |
+ def _run(self): |
409 |
+ mf = self.repo_config.load_manifest( |
410 |
+ os.path.join(self.repo_config.location, self.cp), |
411 |
+ self.distdir, fetchlist_dict=self.fetchlist_dict) |
412 |
+ |
413 |
+ try: |
414 |
+ mf.create(assumeDistHashesAlways=True) |
415 |
+ except FileNotFound as e: |
416 |
+ portage.writemsg(_("!!! File %s doesn't exist, can't update " |
417 |
+ "Manifest\n") % e, noiselevel=-1) |
418 |
+ return 1 |
419 |
+ |
420 |
+ except PortagePackageException as e: |
421 |
+ portage.writemsg(("!!! %s\n") % (e,), noiselevel=-1) |
422 |
+ return 1 |
423 |
+ |
424 |
+ try: |
425 |
+ modified = mf.write(sign=False) |
426 |
+ except PermissionDenied as e: |
427 |
+ portage.writemsg("!!! %s: %s\n" % (_("Permission Denied"), e,), |
428 |
+ noiselevel=-1) |
429 |
+ return 1 |
430 |
+ else: |
431 |
+ if modified: |
432 |
+ return self.MODIFIED |
433 |
+ else: |
434 |
+ return os.EX_OK |
435 |
|
436 |
diff --git a/pym/portage/package/ebuild/_parallel_manifest/ManifestScheduler.py b/pym/portage/package/ebuild/_parallel_manifest/ManifestScheduler.py |
437 |
new file mode 100644 |
438 |
index 0000000..b480e77 |
439 |
--- /dev/null |
440 |
+++ b/pym/portage/package/ebuild/_parallel_manifest/ManifestScheduler.py |
441 |
@@ -0,0 +1,79 @@ |
442 |
+# Copyright 2012 Gentoo Foundation |
443 |
+# Distributed under the terms of the GNU General Public License v2 |
444 |
+ |
445 |
+import portage |
446 |
+from portage import os |
447 |
+from portage.dep import _repo_separator |
448 |
+from portage.localization import _ |
449 |
+from portage.util._async.AsyncScheduler import AsyncScheduler |
450 |
+from .ManifestTask import ManifestTask |
451 |
+ |
452 |
+class ManifestScheduler(AsyncScheduler): |
453 |
+ |
454 |
+ def __init__(self, portdb, cp_iter=None, |
455 |
+ gpg_cmd=None, gpg_vars=None, **kwargs): |
456 |
+ |
457 |
+ AsyncScheduler.__init__(self, **kwargs) |
458 |
+ |
459 |
+ self._portdb = portdb |
460 |
+ |
461 |
+ if cp_iter is None: |
462 |
+ cp_iter = self._iter_every_cp() |
463 |
+ self._cp_iter = cp_iter |
464 |
+ self._gpg_cmd = gpg_cmd |
465 |
+ self._gpg_vars = gpg_vars |
466 |
+ self._task_iter = self._iter_tasks() |
467 |
+ |
468 |
+ def _next_task(self): |
469 |
+ return next(self._task_iter) |
470 |
+ |
471 |
+ def _iter_every_cp(self): |
472 |
+ every_cp = self._portdb.cp_all() |
473 |
+ every_cp.sort(reverse=True) |
474 |
+ try: |
475 |
+ while not self._terminated_tasks: |
476 |
+ yield every_cp.pop() |
477 |
+ except IndexError: |
478 |
+ pass |
479 |
+ |
480 |
+ def _iter_tasks(self): |
481 |
+ portdb = self._portdb |
482 |
+ distdir = portdb.settings["DISTDIR"] |
483 |
+ disabled_repos = set() |
484 |
+ |
485 |
+ for cp in self._cp_iter: |
486 |
+ if self._terminated_tasks: |
487 |
+ break |
488 |
+ # We iterate over portdb.porttrees, since it's common to |
489 |
+ # tweak this attribute in order to adjust repo selection. |
490 |
+ for mytree in portdb.porttrees: |
491 |
+ repo_config = portdb.repositories.get_repo_for_location(mytree) |
492 |
+ if not repo_config.create_manifest: |
493 |
+ if repo_config.name not in disabled_repos: |
494 |
+ disabled_repos.add(repo_config.name) |
495 |
+ portage.writemsg( |
496 |
+ _(">>> Skipping creating Manifest for %s%s%s; " |
497 |
+ "repository is configured to not use them\n") % |
498 |
+ (cp, _repo_separator, repo_config.name), |
499 |
+ noiselevel=-1) |
500 |
+ continue |
501 |
+ cpv_list = portdb.cp_list(cp, mytree=[repo_config.location]) |
502 |
+ if not cpv_list: |
503 |
+ continue |
504 |
+ fetchlist_dict = {} |
505 |
+ for cpv in cpv_list: |
506 |
+ fetchlist_dict[cpv] = \ |
507 |
+ list(portdb.getFetchMap(cpv, mytree=mytree)) |
508 |
+ |
509 |
+ yield ManifestTask(cp=cp, distdir=distdir, |
510 |
+ fetchlist_dict=fetchlist_dict, repo_config=repo_config, |
511 |
+ gpg_cmd=self._gpg_cmd, gpg_vars=self._gpg_vars) |
512 |
+ |
513 |
+ def _task_exit(self, task): |
514 |
+ AsyncScheduler._task_exit(self, task) |
515 |
+ if task.returncode != os.EX_OK: |
516 |
+ if not self._terminated_tasks: |
517 |
+ portage.writemsg( |
518 |
+ "Error processing %s%s%s, continuing...\n" % |
519 |
+ (task.cp, _repo_separator, task.repo_config.name), |
520 |
+ noiselevel=-1) |
521 |
|
522 |
diff --git a/pym/portage/package/ebuild/_parallel_manifest/ManifestTask.py b/pym/portage/package/ebuild/_parallel_manifest/ManifestTask.py |
523 |
new file mode 100644 |
524 |
index 0000000..53b85b2 |
525 |
--- /dev/null |
526 |
+++ b/pym/portage/package/ebuild/_parallel_manifest/ManifestTask.py |
527 |
@@ -0,0 +1,75 @@ |
528 |
+# Copyright 2012 Gentoo Foundation |
529 |
+# Distributed under the terms of the GNU General Public License v2 |
530 |
+ |
531 |
+from portage import os |
532 |
+from portage.util import shlex_split, varexpand, writemsg |
533 |
+from _emerge.CompositeTask import CompositeTask |
534 |
+from _emerge.SpawnProcess import SpawnProcess |
535 |
+from .ManifestProcess import ManifestProcess |
536 |
+ |
537 |
+class ManifestTask(CompositeTask): |
538 |
+ |
539 |
+ __slots__ = ("cp", "distdir", "fetchlist_dict", "gpg_cmd", |
540 |
+ "gpg_vars", "repo_config", "_manifest_path") |
541 |
+ |
542 |
+ def _start(self): |
543 |
+ self._manifest_path = os.path.join(self.repo_config.location, |
544 |
+ self.cp, "Manifest") |
545 |
+ manifest_proc = ManifestProcess(cp=self.cp, distdir=self.distdir, |
546 |
+ fetchlist_dict=self.fetchlist_dict, repo_config=self.repo_config, |
547 |
+ scheduler=self.scheduler) |
548 |
+ self._start_task(manifest_proc, self._manifest_proc_exit) |
549 |
+ |
550 |
+ def _manifest_proc_exit(self, manifest_proc): |
551 |
+ self._assert_current(manifest_proc) |
552 |
+ if manifest_proc.returncode not in (os.EX_OK, manifest_proc.MODIFIED): |
553 |
+ self.returncode = manifest_proc.returncode |
554 |
+ self._current_task = None |
555 |
+ self.wait() |
556 |
+ return |
557 |
+ |
558 |
+ modified = manifest_proc.returncode == manifest_proc.MODIFIED |
559 |
+ |
560 |
+ if self.gpg_cmd is None or not modified or \ |
561 |
+ not os.path.exists(self._manifest_path): |
562 |
+ self.returncode = os.EX_OK |
563 |
+ self._current_task = None |
564 |
+ self.wait() |
565 |
+ return |
566 |
+ |
567 |
+ self._start_gpg_proc() |
568 |
+ |
569 |
+ def _start_gpg_proc(self): |
570 |
+ gpg_vars = self.gpg_vars |
571 |
+ if gpg_vars is None: |
572 |
+ gpg_vars = {} |
573 |
+ else: |
574 |
+ gpg_vars = gpg_vars.copy() |
575 |
+ gpg_vars["FILE"] = self._manifest_path |
576 |
+ gpg_cmd = varexpand(self.gpg_cmd, mydict=gpg_vars) |
577 |
+ gpg_cmd = shlex_split(gpg_cmd) |
578 |
+ gpg_proc = SpawnProcess( |
579 |
+ args=gpg_cmd, env=os.environ, scheduler=self.scheduler) |
580 |
+ self._start_task(gpg_proc, self._gpg_proc_exit) |
581 |
+ |
582 |
+ def _gpg_proc_exit(self, gpg_proc): |
583 |
+ if self._default_exit(gpg_proc) != os.EX_OK: |
584 |
+ self.wait() |
585 |
+ return |
586 |
+ |
587 |
+ rename_args = (self._manifest_path + ".asc", self._manifest_path) |
588 |
+ try: |
589 |
+ os.rename(*rename_args) |
590 |
+ except OSError as e: |
591 |
+ writemsg("!!! rename('%s', '%s'): %s\n" % rename_args + (e,), |
592 |
+ noiselevel=-1) |
593 |
+ try: |
594 |
+ os.unlink(self._manifest_path + ".asc") |
595 |
+ except OSError: |
596 |
+ pass |
597 |
+ self.returncode = 1 |
598 |
+ else: |
599 |
+ self.returncode = os.EX_OK |
600 |
+ |
601 |
+ self._current_task = None |
602 |
+ self.wait() |
603 |
|
604 |
diff --git a/pym/portage/package/ebuild/_parallel_manifest/__init__.py b/pym/portage/package/ebuild/_parallel_manifest/__init__.py |
605 |
new file mode 100644 |
606 |
index 0000000..418ad86 |
607 |
--- /dev/null |
608 |
+++ b/pym/portage/package/ebuild/_parallel_manifest/__init__.py |
609 |
@@ -0,0 +1,2 @@ |
610 |
+# Copyright 2012 Gentoo Foundation |
611 |
+# Distributed under the terms of the GNU General Public License v2 |
612 |
|
613 |
diff --git a/pym/portage/util/_async/AsyncScheduler.py b/pym/portage/util/_async/AsyncScheduler.py |
614 |
new file mode 100644 |
615 |
index 0000000..cae45fd |
616 |
--- /dev/null |
617 |
+++ b/pym/portage/util/_async/AsyncScheduler.py |
618 |
@@ -0,0 +1,88 @@ |
619 |
+# Copyright 2012 Gentoo Foundation |
620 |
+# Distributed under the terms of the GNU General Public License v2 |
621 |
+ |
622 |
+from portage import os |
623 |
+from _emerge.AsynchronousTask import AsynchronousTask |
624 |
+from _emerge.PollScheduler import PollScheduler |
625 |
+ |
626 |
+class AsyncScheduler(AsynchronousTask, PollScheduler): |
627 |
+ |
628 |
+ __slots__ = ('_error_count', '_loadavg_check_id', |
629 |
+ '_max_jobs', '_max_load', |
630 |
+ '_remaining_tasks', '_running_tasks', '_term_check_id') |
631 |
+ |
632 |
+ def __init__(self, max_jobs=None, max_load=None, **kwargs): |
633 |
+ AsynchronousTask.__init__(self) |
634 |
+ PollScheduler.__init__(self, **kwargs) |
635 |
+ |
636 |
+ if max_jobs is None: |
637 |
+ max_jobs = 1 |
638 |
+ self._max_jobs = max_jobs |
639 |
+ self._max_load = max_load |
640 |
+ self._error_count = 0 |
641 |
+ self._running_tasks = set() |
642 |
+ self._remaining_tasks = True |
643 |
+ self._term_check_id = None |
644 |
+ self._loadavg_check_id = None |
645 |
+ |
646 |
+ def _cancel(self): |
647 |
+ self._terminated.set() |
648 |
+ self._terminate_tasks() |
649 |
+ |
650 |
+ def _terminate_tasks(self): |
651 |
+ for task in list(self._running_tasks): |
652 |
+ task.cancel() |
653 |
+ |
654 |
+ def _next_task(self): |
655 |
+ raise NotImplementedError(self) |
656 |
+ |
657 |
+ def _keep_scheduling(self): |
658 |
+ return self._remaining_tasks and not self._terminated_tasks |
659 |
+ |
660 |
+ def _running_job_count(self): |
661 |
+ return len(self._running_tasks) |
662 |
+ |
663 |
+ def _schedule_tasks(self): |
664 |
+ while self._keep_scheduling() and self._can_add_job(): |
665 |
+ try: |
666 |
+ task = self._next_task() |
667 |
+ except StopIteration: |
668 |
+ self._remaining_tasks = False |
669 |
+ else: |
670 |
+ self._running_tasks.add(task) |
671 |
+ task.scheduler = self.sched_iface |
672 |
+ task.addExitListener(self._task_exit) |
673 |
+ task.start() |
674 |
+ |
675 |
+ def _task_exit(self, task): |
676 |
+ self._running_tasks.discard(task) |
677 |
+ if task.returncode != os.EX_OK: |
678 |
+ self._error_count += 1 |
679 |
+ self._schedule() |
680 |
+ |
681 |
+ def _start(self): |
682 |
+ self._term_check_id = self.sched_iface.idle_add(self._termination_check) |
683 |
+ if self._max_load is not None: |
684 |
+ # We have to schedule periodically, in case the load |
685 |
+ # average has changed since the last call. |
686 |
+ self._loadavg_check_id = self.sched_iface.timeout_add( |
687 |
+ self._loadavg_latency, self._schedule) |
688 |
+ self._schedule() |
689 |
+ |
690 |
+ def _wait(self): |
691 |
+ # Loop while there are jobs to be scheduled. |
692 |
+ while self._keep_scheduling(): |
693 |
+ self.sched_iface.iteration() |
694 |
+ |
695 |
+ # Clean shutdown of previously scheduled jobs. In the |
696 |
+ # case of termination, this allows for basic cleanup |
697 |
+ # such as flushing of buffered output to logs. |
698 |
+ while self._is_work_scheduled(): |
699 |
+ self.sched_iface.iteration() |
700 |
+ |
701 |
+ if self._error_count > 0: |
702 |
+ self.returncode = 1 |
703 |
+ else: |
704 |
+ self.returncode = os.EX_OK |
705 |
+ |
706 |
+ return self.returncode |
707 |
|
708 |
diff --git a/pym/portage/util/_async/ForkProcess.py b/pym/portage/util/_async/ForkProcess.py |
709 |
new file mode 100644 |
710 |
index 0000000..607d0ff |
711 |
--- /dev/null |
712 |
+++ b/pym/portage/util/_async/ForkProcess.py |
713 |
@@ -0,0 +1,48 @@ |
714 |
+# Copyright 2012 Gentoo Foundation |
715 |
+# Distributed under the terms of the GNU General Public License v2 |
716 |
+ |
717 |
+import signal |
718 |
+import traceback |
719 |
+ |
720 |
+import portage |
721 |
+from portage import os |
722 |
+from _emerge.SpawnProcess import SpawnProcess |
723 |
+ |
724 |
+class ForkProcess(SpawnProcess): |
725 |
+ |
726 |
+ def _spawn(self, args, fd_pipes=None, **kwargs): |
727 |
+ """ |
728 |
+ Fork a subprocess, apply local settings, and call fetch(). |
729 |
+ """ |
730 |
+ |
731 |
+ pid = os.fork() |
732 |
+ if pid != 0: |
733 |
+ if not isinstance(pid, int): |
734 |
+ raise AssertionError( |
735 |
+ "fork returned non-integer: %s" % (repr(pid),)) |
736 |
+ portage.process.spawned_pids.append(pid) |
737 |
+ return [pid] |
738 |
+ |
739 |
+ portage.locks._close_fds() |
740 |
+ # Disable close_fds since we don't exec (see _setup_pipes docstring). |
741 |
+ portage.process._setup_pipes(fd_pipes, close_fds=False) |
742 |
+ |
743 |
+ # Use default signal handlers in order to avoid problems |
744 |
+ # killing subprocesses as reported in bug #353239. |
745 |
+ signal.signal(signal.SIGINT, signal.SIG_DFL) |
746 |
+ signal.signal(signal.SIGTERM, signal.SIG_DFL) |
747 |
+ |
748 |
+ rval = 1 |
749 |
+ try: |
750 |
+ rval = self._run() |
751 |
+ except SystemExit: |
752 |
+ raise |
753 |
+ except: |
754 |
+ traceback.print_exc() |
755 |
+ finally: |
756 |
+ # Call os._exit() from finally block, in order to suppress any |
757 |
+ # finally blocks from earlier in the call stack. See bug #345289. |
758 |
+ os._exit(rval) |
759 |
+ |
760 |
+ def _run(self): |
761 |
+ raise NotImplementedError(self) |
762 |
|
763 |
diff --git a/pym/portage/util/_async/__init__.py b/pym/portage/util/_async/__init__.py |
764 |
new file mode 100644 |
765 |
index 0000000..418ad86 |
766 |
--- /dev/null |
767 |
+++ b/pym/portage/util/_async/__init__.py |
768 |
@@ -0,0 +1,2 @@ |
769 |
+# Copyright 2012 Gentoo Foundation |
770 |
+# Distributed under the terms of the GNU General Public License v2 |