OLD | NEW |
1 #!/usr/bin/python2.6 | 1 #!/usr/bin/python2.6 |
2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Program to run emerge in parallel, for significant speedup. | 6 """Program to run emerge in parallel, for significant speedup. |
7 | 7 |
8 Usage: | 8 Usage: |
9 ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps] | 9 ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps] |
10 [emerge args] package" | 10 [emerge args] package" |
(...skipping 22 matching lines...) Expand all Loading... |
33 * Some ebuild packages have incorrectly specified deps, and running | 33 * Some ebuild packages have incorrectly specified deps, and running |
34 them in parallel is more likely to bring out these failures. | 34 them in parallel is more likely to bring out these failures. |
35 * Some ebuilds (especially the build part) have complex dependencies | 35 * Some ebuilds (especially the build part) have complex dependencies |
36 that are not captured well by this script (it may be necessary to | 36 that are not captured well by this script (it may be necessary to |
37 install an old package to build, but then install a newer version | 37 install an old package to build, but then install a newer version |
38 of the same package for a runtime dep). | 38 of the same package for a runtime dep). |
39 """ | 39 """ |
40 | 40 |
41 import codecs | 41 import codecs |
42 import copy | 42 import copy |
| 43 import errno |
43 import multiprocessing | 44 import multiprocessing |
44 import os | 45 import os |
45 import Queue | 46 import Queue |
46 import shlex | 47 import shlex |
47 import signal | 48 import signal |
48 import sys | 49 import sys |
49 import tempfile | 50 import tempfile |
50 import time | 51 import time |
51 import traceback | 52 import traceback |
52 import urllib2 | 53 import urllib2 |
(...skipping 1248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1301 """ | 1302 """ |
1302 | 1303 |
1303 SetupWorkerSignals() | 1304 SetupWorkerSignals() |
1304 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb | 1305 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb |
1305 opts, spinner = emerge.opts, emerge.spinner | 1306 opts, spinner = emerge.opts, emerge.spinner |
1306 opts["--nodeps"] = True | 1307 opts["--nodeps"] = True |
1307 while True: | 1308 while True: |
1308 # Wait for a new item to show up on the queue. This is a blocking wait, | 1309 # Wait for a new item to show up on the queue. This is a blocking wait, |
1309 # so if there's nothing to do, we just sit here. | 1310 # so if there's nothing to do, we just sit here. |
1310 target = task_queue.get() | 1311 target = task_queue.get() |
| 1312 if not target: |
| 1313 # If target is None, this means that the main thread wants us to quit. |
| 1314 # The other workers need to exit too, so we'll push the message back on |
| 1315 # to the queue so they'll get it too. |
| 1316 task_queue.put(target) |
| 1317 return |
1311 db_pkg = package_db[target] | 1318 db_pkg = package_db[target] |
1312 db_pkg.root_config = emerge.root_config | 1319 db_pkg.root_config = emerge.root_config |
1313 install_list = [db_pkg] | 1320 install_list = [db_pkg] |
1314 pkgname = db_pkg.pf | 1321 pkgname = db_pkg.pf |
1315 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False) | 1322 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False) |
1316 start_timestamp = time.time() | 1323 start_timestamp = time.time() |
1317 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp) | 1324 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp) |
1318 job_queue.put(job) | 1325 job_queue.put(job) |
1319 if "--pretend" in opts: | 1326 if "--pretend" in opts: |
1320 retcode = 0 | 1327 retcode = 0 |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1405 print "=== Complete: %s ===" % info | 1412 print "=== Complete: %s ===" % info |
1406 else: | 1413 else: |
1407 print "=== Still running: %s ===" % info | 1414 print "=== Still running: %s ===" % info |
1408 | 1415 |
1409 if self.unlink: | 1416 if self.unlink: |
1410 os.unlink(job.filename) | 1417 os.unlink(job.filename) |
1411 | 1418 |
1412 | 1419 |
1413 def PrintWorker(queue): | 1420 def PrintWorker(queue): |
1414 """A worker that prints stuff to the screen as requested.""" | 1421 """A worker that prints stuff to the screen as requested.""" |
1415 SetupWorkerSignals() | 1422 |
| 1423 def ExitHandler(signum, frame): |
| 1424 # Switch to default signal handlers so that we'll die after two signals. |
| 1425 signal.signal(signal.SIGINT, signal.SIG_DFL) |
| 1426 signal.signal(signal.SIGTERM, signal.SIG_DFL) |
| 1427 |
| 1428 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will |
| 1429 # handle it and tell us when we need to exit. |
| 1430 signal.signal(signal.SIGINT, ExitHandler) |
| 1431 signal.signal(signal.SIGTERM, ExitHandler) |
| 1432 |
| 1433 # seek_locations is a map indicating the position we are at in each file. |
| 1434 # It starts off empty, but is set by the various Print jobs as we go along |
| 1435 # to indicate where we left off in each file. |
1416 seek_locations = {} | 1436 seek_locations = {} |
1417 while True: | 1437 while True: |
1418 job = queue.get() | 1438 try: |
1419 if job: | 1439 job = queue.get() |
1420 job.Print(seek_locations) | 1440 if job: |
1421 else: | 1441 job.Print(seek_locations) |
1422 break | 1442 else: |
| 1443 break |
| 1444 except IOError as ex: |
| 1445 if ex.errno == errno.EINTR: |
| 1446 # Looks like we received a signal. Keep printing. |
| 1447 continue |
| 1448 raise |
1423 | 1449 |
1424 | 1450 |
1425 class EmergeQueue(object): | 1451 class EmergeQueue(object): |
1426 """Class to schedule emerge jobs according to a dependency graph.""" | 1452 """Class to schedule emerge jobs according to a dependency graph.""" |
1427 | 1453 |
1428 def __init__(self, deps_map, emerge, package_db, show_output): | 1454 def __init__(self, deps_map, emerge, package_db, show_output): |
1429 # Store the dependency graph. | 1455 # Store the dependency graph. |
1430 self._deps_map = deps_map | 1456 self._deps_map = deps_map |
1431 # Initialize the running queue to empty | 1457 # Initialize the running queue to empty |
1432 self._jobs = {} | 1458 self._jobs = {} |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1483 signal.signal(signal.SIGTERM, signal.SIG_DFL) | 1509 signal.signal(signal.SIGTERM, signal.SIG_DFL) |
1484 | 1510 |
1485 # Print our current job status | 1511 # Print our current job status |
1486 for target, job in self._jobs.iteritems(): | 1512 for target, job in self._jobs.iteritems(): |
1487 if job: | 1513 if job: |
1488 self._print_queue.put(JobPrinter(job, unlink=True)) | 1514 self._print_queue.put(JobPrinter(job, unlink=True)) |
1489 | 1515 |
1490 # Notify the user that we are exiting | 1516 # Notify the user that we are exiting |
1491 self._Print("Exiting on signal %s" % signum) | 1517 self._Print("Exiting on signal %s" % signum) |
1492 | 1518 |
1493 # Exit when print worker is done. | 1519 # Kill child threads, then exit. |
1494 self._print_queue.put(None) | 1520 self._Exit() |
1495 self._print_worker.join() | |
1496 sys.exit(1) | 1521 sys.exit(1) |
1497 | 1522 |
1498 # Print out job status when we are killed | 1523 # Print out job status when we are killed |
1499 signal.signal(signal.SIGINT, ExitHandler) | 1524 signal.signal(signal.SIGINT, ExitHandler) |
1500 signal.signal(signal.SIGTERM, ExitHandler) | 1525 signal.signal(signal.SIGTERM, ExitHandler) |
1501 | 1526 |
1502 def _Schedule(self, target): | 1527 def _Schedule(self, target): |
1503 # We maintain a tree of all deps, if this doesn't need | 1528 # We maintain a tree of all deps, if this doesn't need |
1504 # to be installed just free up it's children and continue. | 1529 # to be installed just free up it's children and continue. |
1505 # It is possible to reinstall deps of deps, without reinstalling | 1530 # It is possible to reinstall deps of deps, without reinstalling |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1551 if not self._deps_map[dep]["needs"]: | 1576 if not self._deps_map[dep]["needs"]: |
1552 self._Schedule(dep) | 1577 self._Schedule(dep) |
1553 self._deps_map.pop(target) | 1578 self._deps_map.pop(target) |
1554 | 1579 |
1555 def _Retry(self): | 1580 def _Retry(self): |
1556 if self._retry_queue: | 1581 if self._retry_queue: |
1557 target = self._retry_queue.pop(0) | 1582 target = self._retry_queue.pop(0) |
1558 self._Schedule(target) | 1583 self._Schedule(target) |
1559 self._Print("Retrying emerge of %s." % target) | 1584 self._Print("Retrying emerge of %s." % target) |
1560 | 1585 |
| 1586 def _Exit(self): |
| 1587 # Tell emerge workers to exit. They all exit when 'None' is pushed |
| 1588 # to the queue. |
| 1589 self._emerge_queue.put(None) |
| 1590 self._pool.close() |
| 1591 self._pool.join() |
| 1592 |
| 1593 # Now that our workers are finished, we can kill the print queue. |
| 1594 self._print_queue.put(None) |
| 1595 self._print_worker.join() |
| 1596 |
1561 def Run(self): | 1597 def Run(self): |
1562 """Run through the scheduled ebuilds. | 1598 """Run through the scheduled ebuilds. |
1563 | 1599 |
1564 Keep running so long as we have uninstalled packages in the | 1600 Keep running so long as we have uninstalled packages in the |
1565 dependency graph to merge. | 1601 dependency graph to merge. |
1566 """ | 1602 """ |
1567 while self._deps_map: | 1603 while self._deps_map: |
1568 # Check here that we are actually waiting for something. | 1604 # Check here that we are actually waiting for something. |
1569 if (self._emerge_queue.empty() and | 1605 if (self._emerge_queue.empty() and |
1570 self._job_queue.empty() and | 1606 self._job_queue.empty() and |
1571 not self._jobs and | 1607 not self._jobs and |
1572 self._deps_map): | 1608 self._deps_map): |
1573 # If we have failed on a package, retry it now. | 1609 # If we have failed on a package, retry it now. |
1574 if self._retry_queue: | 1610 if self._retry_queue: |
1575 self._Retry() | 1611 self._Retry() |
1576 else: | 1612 else: |
1577 # Tell the print worker we're done, and wait for it to exit. | 1613 # Tell child threads to exit. |
1578 self._print_queue.put(None) | 1614 self._Exit() |
1579 self._print_worker.join() | |
1580 | 1615 |
1581 # The dependency map is helpful for debugging failures. | 1616 # The dependency map is helpful for debugging failures. |
1582 PrintDepsMap(self._deps_map) | 1617 PrintDepsMap(self._deps_map) |
1583 | 1618 |
1584 # Tell the user why we're exiting. | 1619 # Tell the user why we're exiting. |
1585 if self._failed: | 1620 if self._failed: |
1586 print "Packages failed: %s" % " ,".join(self._failed) | 1621 print "Packages failed: %s" % " ,".join(self._failed) |
1587 else: | 1622 else: |
1588 print "Deadlock! Circular dependencies!" | 1623 print "Deadlock! Circular dependencies!" |
1589 sys.exit(1) | 1624 sys.exit(1) |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1630 # one retrying package actively running at a time. | 1665 # one retrying package actively running at a time. |
1631 self._Retry() | 1666 self._Retry() |
1632 | 1667 |
1633 self._Print("Completed %s" % details) | 1668 self._Print("Completed %s" % details) |
1634 # Mark as completed and unblock waiting ebuilds. | 1669 # Mark as completed and unblock waiting ebuilds. |
1635 self._Finish(target) | 1670 self._Finish(target) |
1636 | 1671 |
1637 # Print an update. | 1672 # Print an update. |
1638 self._Status() | 1673 self._Status() |
1639 | 1674 |
1640 # Tell the print worker we're done, and wait for it to exit. | 1675 # Tell child threads to exit. |
1641 self._print_queue.put(None) | 1676 self._Print("Merge complete") |
1642 self._print_worker.join() | 1677 self._Exit() |
1643 | 1678 |
1644 | 1679 |
1645 def main(): | 1680 def main(): |
1646 | 1681 |
1647 deps = DepGraphGenerator() | 1682 deps = DepGraphGenerator() |
1648 deps.Initialize(sys.argv[1:]) | 1683 deps.Initialize(sys.argv[1:]) |
1649 emerge = deps.emerge | 1684 emerge = deps.emerge |
1650 | 1685 |
1651 if emerge.action is not None: | 1686 if emerge.action is not None: |
1652 sys.argv = deps.ParseParallelEmergeArgs(sys.argv) | 1687 sys.argv = deps.ParseParallelEmergeArgs(sys.argv) |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1730 # need to upgrade the rest of the packages. So we'll go ahead and do that. | 1765 # need to upgrade the rest of the packages. So we'll go ahead and do that. |
1731 if portage_upgrade: | 1766 if portage_upgrade: |
1732 args = sys.argv[1:] + ["--nomerge=sys-apps/portage"] | 1767 args = sys.argv[1:] + ["--nomerge=sys-apps/portage"] |
1733 os.execvp(os.path.realpath(sys.argv[0]), args) | 1768 os.execvp(os.path.realpath(sys.argv[0]), args) |
1734 | 1769 |
1735 print "Done" | 1770 print "Done" |
1736 sys.exit(0) | 1771 sys.exit(0) |
1737 | 1772 |
1738 if __name__ == "__main__": | 1773 if __name__ == "__main__": |
1739 main() | 1774 main() |
OLD | NEW |