Index: parallel_emerge |
diff --git a/parallel_emerge b/parallel_emerge |
index 4f54927e9192edcb7bd1496c0edd234370e0c487..dbcdb7a6cbcc59089c2b6144a365b54d253374dd 100755 |
--- a/parallel_emerge |
+++ b/parallel_emerge |
@@ -31,14 +31,10 @@ Basic operation: |
Caveats: |
* Some ebuild packages have incorrectly specified deps, and running |
them in parallel is more likely to bring out these failures. |
- * Portage "world" is a record of explicitly installed packages. In |
- this parallel scheme, explicitly installed packages are installed |
- twice, once for the real install, and once for world file addition. |
* Some ebuilds (especially the build part) have complex dependencies |
that are not captured well by this script (it may be necessary to |
install an old package to build, but then install a newer version |
- of the same package for a runtime dep). This script is only |
- currently stable for binpkg installs. |
+ of the same package for a runtime dep). |
""" |
import os |
@@ -52,7 +48,7 @@ import time |
def Usage(): |
print "Usage:" |
- print " ./parallel_emerge --board=BOARD [emerge args] package" |
+ print " ./parallel_emerge --board=BOARD --jobs=JOBS [emerge args] package" |
sys.exit(1) |
@@ -66,8 +62,8 @@ PACKAGE = None |
EMERGE_ARGS = "" |
BOARD = None |
-# Runtime flags. TODO(): maybe make these commandline options or |
-# environment veriables. |
+# Runtime flags. TODO(): Maybe make these command-line options or |
+# environment variables. |
VERBOSE = False |
AUTOCLEAN = False |
@@ -76,12 +72,12 @@ def ParseArgs(argv): |
"""Set global vars based on command line. |
We need to be compatible with emerge arg format. |
- We scrape --board-XXX, and distinguish between args |
+ We scrape --board=XXX and --jobs=XXX, and distinguish between args |
and package names. |
- TODO(): robustify argument processing, as it's possible to |
+ TODO(): Robustify argument processing, as it's possible to |
pass in many two argument parameters that are difficult |
- to programmaitcally identify, although we don't currently |
- use any besides --bdeps <y|n>. |
+ to programmatically identify, although we don't currently |
+ use any besides --with-bdeps <y|n>. |
Args: |
argv: arguments list |
Returns: |
@@ -90,36 +86,43 @@ def ParseArgs(argv): |
if VERBOSE: |
print argv |
board_arg = None |
+ jobs_arg = 0 |
package_args = [] |
emerge_passthru_args = "" |
- re_board = re.compile(r"--board=(?P<board>.*)") |
for arg in argv[1:]: |
- # Check if the arg begins with '-' |
- if arg[0] == "-" or arg == "y" or arg == "n": |
- # Specifically match "--board=" |
- m = re_board.match(arg) |
- if m: |
- board_arg = m.group("board") |
- else: |
- # Pass through to emerge. |
- emerge_passthru_args = emerge_passthru_args + " " + arg |
+ # Specifically match "--board=" and "--jobs=". |
+ if arg.startswith("--board="): |
+ board_arg = arg.replace("--board=", "") |
+ elif arg.startswith("--jobs="): |
+ try: |
+ jobs_arg = int(arg.replace("--jobs=", "")) |
+ except ValueError: |
+ print "Unrecognized argument:", arg |
+ Usage() |
+ sys.exit(1) |
+ elif arg.startswith("-") or arg == "y" or arg == "n": |
+ # Not a package name, so pass through to emerge. |
+ emerge_passthru_args = emerge_passthru_args + " " + arg |
else: |
- # Only non-dashed arg should be the target package. |
package_args.append(arg) |
- if not package_args: |
+ if not package_args and not emerge_passthru_args: |
Usage() |
sys.exit(1) |
+ # Default to lots of jobs |
+ if jobs_arg <= 0: |
+ jobs_arg = 256 |
+ |
# Set globals. |
- return " ".join(package_args), emerge_passthru_args, board_arg |
+ return " ".join(package_args), emerge_passthru_args, board_arg, jobs_arg |
def EmergeCommand(): |
"""Helper function to return the base emerge commandline. |
This is configured for board type, and including pass thru args, |
- using global variables. TODO(): unglobalfy. |
+ using global variables. TODO(): Unglobalfy. |
Returns: |
string containing emerge command. |
""" |
@@ -133,12 +136,12 @@ def GetDepsFromPortage(package): |
"""Get dependency tree info by running emerge. |
Run 'emerge -p --debug package', and get a text output of all deps. |
- TODO(): Put dep caclation in a library, as cros_extract_deps |
+ TODO(): Put dep calculation in a library, as cros_extract_deps |
also uses this code. |
Args: |
- package: string containing the packages to build. |
+ package: String containing the packages to build. |
Returns: |
- text output of emerge -p --debug, which can be processed elsewhere. |
+ Text output of emerge -p --debug, which can be processed elsewhere. |
""" |
print "Calculating deps for package %s" % package |
cmdline = EmergeCommand() + " -p --debug --color=n " + package |
@@ -177,10 +180,10 @@ def DepsToTree(lines): |
"""Regex the output from 'emerge --debug' to generate a nested dict of deps. |
Args: |
- lines: output from 'emerge -p --debug package' |
+ lines: Output from 'emerge -p --debug package'. |
Returns: |
- dep_tree: nested dict of dependencies, as specified by emerge. |
- there may be dupes, or circular deps. |
+ dep_tree: Nested dict of dependencies, as specified by emerge. |
+ There may be dupes, or circular deps. |
We need to regex lines as follows: |
hard-host-depends depends on |
@@ -203,7 +206,7 @@ def DepsToTree(lines): |
r"(?P<version>\d+[\w\.-]*)( \[" |
r"(?P<oldversion>\d+[\w\.-]*)\])?" |
) |
- re_failed = re.compile(r".*depends on.*") |
+ re_failed = re.compile(r".*\) depends on.*") |
deps_tree = {} |
deps_stack = [] |
deps_info = {} |
@@ -232,7 +235,7 @@ def DepsToTree(lines): |
sys.exit(1) |
# Go step by step through stack and tree |
- # until we find our parent. Generate |
+ # until we find our parent. |
updatedep = deps_tree |
for i in range(0, depth): |
updatedep = updatedep[deps_stack[i]]["deps"] |
@@ -301,6 +304,7 @@ def DepsToTree(lines): |
# Is this a package that failed to match our huge regex? |
m = re_failed.match(line) |
if m: |
+ print "\n".join(lines) |
print "FAIL: Couldn't understand line:" |
print line |
sys.exit(1) |
@@ -312,8 +316,8 @@ def PrintTree(deps, depth=""): |
"""Print the deps we have seen in the emerge output. |
Args: |
- deps: dependency tree structure. |
- depth: allows printing the tree recursively, with indentation. |
+ deps: Dependency tree structure. |
+ depth: Allows printing the tree recursively, with indentation. |
""" |
for entry in deps: |
action = deps[entry]["action"] |
@@ -325,8 +329,8 @@ def GenDependencyGraph(deps_tree, deps_info): |
"""Generate a doubly linked dependency graph. |
Args: |
- deps_tree: dependency tree structure. |
- deps_info: more info on the dependencies. |
+ deps_tree: Dependency tree structure. |
+ deps_info: More details on the dependencies. |
Returns: |
Deps graph in the form of a dict of packages, with each package |
specifying a "needs" list and "provides" list. |
@@ -337,11 +341,11 @@ def GenDependencyGraph(deps_tree, deps_info): |
"""Convert tree to digraph. |
Take the tree of package -> requirements and reverse it to a digraph of |
- buildable packages -> packages they unblock |
+ buildable packages -> packages they unblock. |
Args: |
- packages: tree(s) of dependencies |
+ packages: Tree(s) of dependencies. |
Returns: |
- unsanitized digraph |
+ Unsanitized digraph. |
""" |
for pkg in packages: |
action = packages[pkg]["action"] |
@@ -359,7 +363,7 @@ def GenDependencyGraph(deps_tree, deps_info): |
this_pkg["needs"].add(dep) |
def RemoveInstalledPackages(): |
- """Remove installed packages, propagating dependencies""" |
+ """Remove installed packages, propagating dependencies.""" |
rm_pkgs = set(deps_map.keys()) - set(deps_info.keys()) |
for pkg in rm_pkgs: |
@@ -378,15 +382,14 @@ def GenDependencyGraph(deps_tree, deps_info): |
target_needs.discard(target) |
del deps_map[pkg] |
- |
def SanitizeDep(basedep, currdep, oldstack, limit): |
"""Search for circular deps between basedep and currdep, then recurse. |
Args: |
- basedep: original dependency, top of stack. |
- currdep: bottom of our current recursion, bottom of stack. |
- oldstack: current dependency chain. |
- limit: how many more levels of recusion to go through, max. |
+ basedep: Original dependency, top of stack. |
+ currdep: Bottom of our current recursion, bottom of stack. |
+ oldstack: Current dependency chain. |
+ limit: How many more levels of recusion to go through, max. |
TODO(): Break RDEPEND preferentially. |
Returns: |
True iff circular dependencies are found. |
@@ -397,7 +400,7 @@ def GenDependencyGraph(deps_tree, deps_info): |
stack = oldstack + [dep] |
if basedep in deps_map[dep]["needs"] or dep == basedep: |
if dep != basedep: |
- stack += [basedep] |
+ stack += [basedep] |
print "Remove cyclic dependency from:" |
for i in xrange(0, len(stack) - 1): |
print " %s -> %s " % (stack[i], stack[i+1]) |
@@ -482,10 +485,10 @@ class EmergeQueue(object): |
If this is a pseudopackage, that means we're done, and can select in in the |
world file. |
Args: |
- target: the full package name of the package to install. |
+ target: The full package name of the package to install. |
eg. "sys-apps/portage-2.17" |
Returns: |
- triplet containing (target name, subprocess object, output buffer object) |
+ Triplet containing (target name, subprocess object, output buffer object). |
""" |
if target.startswith("original-"): |
# "original-" signifies one of the packages we originally requested. |
@@ -515,9 +518,9 @@ class EmergeQueue(object): |
portage_env["PORTAGE_LOCKS"] = "false" |
portage_env["UNMERGE_DELAY"] = "0" |
# Autoclean rummages around in the portage database and uninstalls |
- # old packages. Definitely not necessary for build_image. However |
- # it may be necessary for incremental build_packages. It may also |
- # not be parallel safe. |
+ # old packages. It's not parallel safe, so we skip it. Instead, we |
+ # handle the cleaning ourselves by uninstalling old versions of any |
+ # new packages we install. |
if not AUTOCLEAN: |
portage_env["AUTOCLEAN"] = "no" |
# Launch the subprocess. |
@@ -551,7 +554,7 @@ class EmergeQueue(object): |
""" |
while self._deps_map: |
# If we have packages that are ready, kick them off. |
- if self._emerge_queue: |
+ if self._emerge_queue and len(self._jobs) < JOBS: |
target = self._emerge_queue.pop(0) |
action = self._deps_map[target]["action"] |
# We maintain a tree of all deps, if this doesn't need |
@@ -576,7 +579,7 @@ class EmergeQueue(object): |
if (not self._emerge_queue and |
not self._jobs and |
self._deps_map): |
- # If we have failed on a package retry it now. |
+ # If we have failed on a package, retry it now. |
if self._retry_queue: |
self._Retry() |
# If we have failed a package twice, just give up. |
@@ -607,7 +610,7 @@ class EmergeQueue(object): |
self._jobs.remove((target, job, stdout)) |
# Print if necessary. |
- if VERBOSE: |
+ if VERBOSE or job.returncode != 0: |
print output |
if job.returncode != 0: |
# Handle job failure. |
@@ -635,10 +638,15 @@ class EmergeQueue(object): |
# Main control code. |
+PACKAGE, EMERGE_ARGS, BOARD, JOBS = ParseArgs(sys.argv) |
+ |
+if not PACKAGE: |
+ # No packages. Pass straight through to emerge. |
+ # Allows users to just type ./parallel_emerge --depclean |
+ sys.exit(os.system(EmergeCommand())) |
+ |
print "Starting fast-emerge." |
-PACKAGE, EMERGE_ARGS, BOARD = ParseArgs(sys.argv) |
print " Building package %s on %s (%s)" % (PACKAGE, EMERGE_ARGS, BOARD) |
- |
print "Running emerge to generate deps" |
deps_output = GetDepsFromPortage(PACKAGE) |
print "Processing emerge output" |