Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: man/, bin/, pym/portage/util/_async/, ...
Date: Wed, 03 Oct 2012 09:37:38
Message-Id: 1349257010.15a799b52155a47568f4b049ff8487a2718b270c.zmedico@gentoo
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