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 628 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 final_pkgs.add(str(match.cpv)) | 639 final_pkgs.add(str(match.cpv)) |
640 | 640 |
641 def FindCycles(): | 641 def FindCycles(): |
642 """Find cycles in the dependency tree. | 642 """Find cycles in the dependency tree. |
643 | 643 |
644 Returns: | 644 Returns: |
645 Dict of packages involved in cyclic dependencies, mapping each package | 645 Dict of packages involved in cyclic dependencies, mapping each package |
646 to a list of the cycles the package is involved in. | 646 to a list of the cycles the package is involved in. |
647 """ | 647 """ |
648 | 648 |
649 def FindCyclesAtNode(pkg, cycles, unresolved, resolved): | 649 def FindCyclesAtNode(pkg, cycles, unresolved): |
650 """Find cycles in cyclic dependencies starting at specified package. | 650 """Find cycles in cyclic dependencies starting at specified package. |
651 | 651 |
652 Args: | 652 Args: |
653 pkg: Package identifier. | 653 pkg: Package identifier. |
654 cycles: Set of cycles so far. | 654 cycles: Set of cycles so far. |
655 unresolved: Nodes that have been visited but are not fully processed. | 655 unresolved: Nodes that have been visited but are not fully processed. |
656 resolved: Nodes that have been visited and are fully processed. | |
657 Returns: | |
658 Whether a cycle was found. | |
659 """ | 656 """ |
660 if pkg in resolved: | |
661 return | |
662 unresolved.append(pkg) | 657 unresolved.append(pkg) |
| 658 mycycles = cycles.get(pkg) |
| 659 if mycycles: |
| 660 mycycles = mycycles.get("pkgs") |
663 for dep in deps_map[pkg]["needs"]: | 661 for dep in deps_map[pkg]["needs"]: |
664 if dep in unresolved: | 662 if mycycles and dep in mycycles: |
| 663 continue |
| 664 elif dep in unresolved: |
665 idx = unresolved.index(dep) | 665 idx = unresolved.index(dep) |
666 mycycle = unresolved[idx:] + [dep] | 666 mycycle = unresolved[idx:] + [dep] |
667 for cycle_pkg in mycycle: | 667 for cycle_pkg in mycycle: |
668 info = cycles.setdefault(cycle_pkg, {}) | 668 info = cycles.setdefault(cycle_pkg, {}) |
669 info.setdefault("pkgs", set()).update(mycycle) | 669 info.setdefault("pkgs", set()).update(mycycle) |
670 info.setdefault("cycles", []).append(mycycle) | 670 info.setdefault("cycles", []).append(mycycle) |
671 else: | 671 else: |
672 FindCyclesAtNode(dep, cycles, unresolved, resolved) | 672 FindCyclesAtNode(dep, cycles, unresolved) |
673 unresolved.pop() | 673 unresolved.pop() |
674 resolved.add(pkg) | |
675 | 674 |
676 cycles, unresolved, resolved = {}, [], set() | 675 cycles, unresolved = {}, [] |
677 for pkg in deps_map: | 676 for pkg in deps_map: |
678 FindCyclesAtNode(pkg, cycles, unresolved, resolved) | 677 FindCyclesAtNode(pkg, cycles, unresolved) |
679 return cycles | 678 return cycles |
680 | 679 |
681 def RemoveInstalledPackages(): | 680 def RemoveInstalledPackages(): |
682 """Remove installed packages, propagating dependencies.""" | 681 """Remove installed packages, propagating dependencies.""" |
683 | 682 |
684 # If we're in non-selective mode, the packages specified on the command | 683 # If we're in non-selective mode, the packages specified on the command |
685 # line are generally mandatory. | 684 # line are generally mandatory. |
686 # | 685 # |
687 # There are a few exceptions to this rule: | 686 # There are a few exceptions to this rule: |
688 # 1. If the package isn't getting installed because it's in | 687 # 1. If the package isn't getting installed because it's in |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
746 | 745 |
747 Because we don't treat any dependencies as "soft" unless they're killed | 746 Because we don't treat any dependencies as "soft" unless they're killed |
748 by a cycle, we pay attention to a larger number of dependencies when | 747 by a cycle, we pay attention to a larger number of dependencies when |
749 merging. This hurts performance a bit, but helps reliability. | 748 merging. This hurts performance a bit, but helps reliability. |
750 | 749 |
751 Args: | 750 Args: |
752 cycles: Dict of packages involved in cyclic dependencies, mapping each | 751 cycles: Dict of packages involved in cyclic dependencies, mapping each |
753 package to a list of the cycles the package is involved in. Produced | 752 package to a list of the cycles the package is involved in. Produced |
754 by FindCycles(). | 753 by FindCycles(). |
755 """ | 754 """ |
756 for basedep in set(cycles).intersection(deps_map): | 755 for basedep, cycle_info in cycles.iteritems(): |
757 this_pkg = deps_map[basedep] | 756 for mycycle in cycle_info["cycles"]: |
758 for dep in this_pkg["provides"].intersection(cycles[basedep]["pkgs"]): | 757 info = [] |
759 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]: | 758 broken = False |
760 for mycycle in cycles[basedep]["cycles"]: | 759 for i in range(len(mycycle) - 1): |
761 if dep in mycycle: | 760 pkg1, pkg2 = mycycle[i], mycycle[i+1] |
762 print "Breaking %s -> %s in cycle:" % (dep, basedep) | 761 needs = deps_map[pkg1]["needs"] |
763 for i in range(len(mycycle) - 1): | 762 depinfo = needs.get(pkg2, "deleted") |
764 needs = deps_map[mycycle[i]]["needs"] | 763 bad = False |
765 deptype = needs.get(mycycle[i+1], "deleted") | 764 if (deps_info[pkg1]["idx"] >= deps_info[pkg2]["idx"] and |
766 print " %s -> %s (%s)" % (mycycle[i], mycycle[i+1], deptype) | 765 depinfo != "deleted"): |
767 del deps_map[dep]["needs"][basedep] | 766 depinfo = depinfo + ", deleting" |
768 this_pkg["provides"].remove(dep) | 767 broken = True |
769 break | 768 del deps_map[pkg1]["needs"][pkg2] |
| 769 deps_map[pkg2]["provides"].remove(pkg1) |
| 770 info.append(" %s -> %s (%s)" % (pkg1, pkg2, depinfo)) |
| 771 if broken: |
| 772 print "Breaking cycle:" |
| 773 print "\n".join(info) |
770 | 774 |
771 def AddSecretDeps(): | 775 def AddSecretDeps(): |
772 """Find these tagged packages and add extra dependencies. | 776 """Find these tagged packages and add extra dependencies. |
773 | 777 |
774 For debugging dependency problems. | 778 For debugging dependency problems. |
775 """ | 779 """ |
776 for bad in secret_deps: | 780 for bad in secret_deps: |
777 needed = secret_deps[bad] | 781 needed = secret_deps[bad] |
778 bad_pkg = None | 782 bad_pkg = None |
779 needed_pkg = None | 783 needed_pkg = None |
(...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1062 | 1066 |
1063 deps_map = copy.deepcopy(deps_map) | 1067 deps_map = copy.deepcopy(deps_map) |
1064 install_plan = [] | 1068 install_plan = [] |
1065 plan = set() | 1069 plan = set() |
1066 for target, info in deps_map.iteritems(): | 1070 for target, info in deps_map.iteritems(): |
1067 if not info["needs"] and target not in plan: | 1071 if not info["needs"] and target not in plan: |
1068 for item in InstallPlanAtNode(target, deps_map): | 1072 for item in InstallPlanAtNode(target, deps_map): |
1069 plan.add(item) | 1073 plan.add(item) |
1070 install_plan.append(self.package_db[item]) | 1074 install_plan.append(self.package_db[item]) |
1071 | 1075 |
| 1076 for pkg in plan: |
| 1077 del deps_map[pkg] |
| 1078 |
| 1079 if deps_map: |
| 1080 print "Cyclic dependencies:", " ".join(deps_map) |
| 1081 PrintDepsMap(deps_map) |
| 1082 sys.exit(1) |
| 1083 |
1072 self.emerge.depgraph.display(install_plan) | 1084 self.emerge.depgraph.display(install_plan) |
1073 | 1085 |
1074 | 1086 |
1075 def PrintDepsMap(deps_map): | 1087 def PrintDepsMap(deps_map): |
1076 """Print dependency graph, for each package list it's prerequisites.""" | 1088 """Print dependency graph, for each package list it's prerequisites.""" |
1077 for i in deps_map: | 1089 for i in sorted(deps_map): |
1078 print "%s: (%s) needs" % (i, deps_map[i]["action"]) | 1090 print "%s: (%s) needs" % (i, deps_map[i]["action"]) |
1079 needs = deps_map[i]["needs"] | 1091 needs = deps_map[i]["needs"] |
1080 for j in needs: | 1092 for j in sorted(needs): |
1081 print " %s" % (j) | 1093 print " %s" % (j) |
1082 if not needs: | 1094 if not needs: |
1083 print " no dependencies" | 1095 print " no dependencies" |
1084 | 1096 |
1085 | 1097 |
1086 class EmergeJobState(object): | 1098 class EmergeJobState(object): |
1087 __slots__ = ["done", "filename", "last_output_seek", "last_output_timestamp", | 1099 __slots__ = ["done", "filename", "last_output_seek", "last_output_timestamp", |
1088 "pkgname", "retcode", "start_timestamp", "target"] | 1100 "pkgname", "retcode", "start_timestamp", "target"] |
1089 | 1101 |
1090 def __init__(self, target, pkgname, done, filename, start_timestamp, | 1102 def __init__(self, target, pkgname, done, filename, start_timestamp, |
(...skipping 463 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1554 world_set.update(new_world_pkgs) | 1566 world_set.update(new_world_pkgs) |
1555 | 1567 |
1556 # Update environment (library cache, symlinks, etc.) | 1568 # Update environment (library cache, symlinks, etc.) |
1557 if deps.board and "--pretend" not in emerge.opts: | 1569 if deps.board and "--pretend" not in emerge.opts: |
1558 portage.env_update() | 1570 portage.env_update() |
1559 | 1571 |
1560 print "Done" | 1572 print "Done" |
1561 | 1573 |
1562 if __name__ == "__main__": | 1574 if __name__ == "__main__": |
1563 main() | 1575 main() |
OLD | NEW |