Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Unified Diff: parallel_emerge

Issue 2959006: Update parallel_emerge to support --workon. (Closed) Base URL: ssh://git@chromiumos-git/crosutils.git
Patch Set: Typo fix Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: parallel_emerge
diff --git a/parallel_emerge b/parallel_emerge
index 82cac8846f69f3d5652f401cee3b3f2c62d4ee91..60f5eba3c57cc5249e42d32501a732efa1280063 100755
--- a/parallel_emerge
+++ b/parallel_emerge
@@ -6,7 +6,8 @@
"""Program to run emerge in parallel, for significant speedup.
Usage:
- ./parallel_emerge --board=BOARD [emerge args] package
+ ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]
+ [emerge args] package"
Basic operation:
Runs 'emerge -p --debug' to display dependencies, and stores a
@@ -44,11 +45,25 @@ import subprocess
import sys
import tempfile
import time
+import _emerge.main
def Usage():
+ """Print usage."""
print "Usage:"
- print " ./parallel_emerge --board=BOARD --jobs=JOBS [emerge args] package"
+ print " ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]"
+ print " [emerge args] package"
+ print
+ print "Packages specified as workon packages are always built from source."
+ print "Unless --no-workon-deps is specified, packages that depend on these"
+ print "packages are also built from source."
+ print
+ print "The --workon argument is mainly useful when you want to build and"
+ print "install packages that you are working on unconditionally, but do not"
+ print "to have to rev the package to indicate you want to build it from"
+ print "source. The build_packages script will automatically supply the"
+ print "workon argument to emerge, ensuring that packages selected using"
+ print "cros-workon are rebuilt."
sys.exit(1)
@@ -56,12 +71,6 @@ def Usage():
# but will prevent the package from installing.
secret_deps = {}
-# Globals: package we are building, board we are targeting,
-# emerge args we are passing through.
-PACKAGE = None
-EMERGE_ARGS = ""
-BOARD = None
-
# Runtime flags. TODO(): Maybe make these command-line options or
# environment variables.
VERBOSE = False
@@ -75,12 +84,8 @@ def ParseArgs(argv):
"""Set global vars based on command line.
We need to be compatible with emerge arg format.
- We scrape --board=XXX and --jobs=XXX, and distinguish between args
- and package names.
- TODO(): Robustify argument processing, as it's possible to
- pass in many two argument parameters that are difficult
- to programmatically identify, although we don't currently
- use any besides --with-bdeps <y|n>.
+ We scrape arguments that are specific to parallel_emerge, and pass through
+ the rest directly to emerge.
Args:
argv: arguments list
Returns:
@@ -88,37 +93,28 @@ def ParseArgs(argv):
"""
if VERBOSE:
print argv
- board_arg = None
- jobs_arg = 0
- package_args = []
- emerge_passthru_args = ""
+ workon_set = set()
+ myopts = {}
+ myopts["workon"] = workon_set
+ emerge_args = []
for arg in argv[1:]:
- # Specifically match "--board=" and "--jobs=".
+ # Specifically match arguments that are specific to parallel_emerge, and
+ # pass through the rest.
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
+ myopts["board"] = arg.replace("--board=", "")
+ elif arg.startswith("--workon="):
+ workon_str = arg.replace("--workon=", "")
+ workon_set.update(shlex.split(" ".join(shlex.split(workon_str))))
+ elif arg == "--no-workon-deps":
+ myopts["no-workon-deps"] = True
else:
- package_args.append(arg)
-
- if not package_args and not emerge_passthru_args:
- Usage()
- sys.exit(1)
+ # Not a package name, so pass through to emerge.
+ emerge_args.append(arg)
- # Default to lots of jobs
- if jobs_arg <= 0:
- jobs_arg = 256
+ emerge_action, emerge_opts, emerge_files = _emerge.main.parse_opts(
+ emerge_args)
- # Set globals.
- return " ".join(package_args), emerge_passthru_args, board_arg, jobs_arg
+ return myopts, emerge_action, emerge_opts, emerge_files
def EmergeCommand():
@@ -130,9 +126,15 @@ def EmergeCommand():
string containing emerge command.
"""
emerge = "emerge"
- if BOARD:
- emerge += "-" + BOARD
- return emerge + " " + EMERGE_ARGS
+ if "board" in OPTS:
+ emerge += "-" + OPTS["board"]
+ cmd = [emerge]
+ for key, val in EMERGE_OPTS.items():
+ if val is True:
+ cmd.append(key)
+ else:
+ cmd.extend([key, str(val)])
+ return " ".join(cmd)
def GetDepsFromPortage(package):
@@ -147,7 +149,10 @@ def GetDepsFromPortage(package):
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
+ cmdline = (EmergeCommand() + " -p --debug --color=n --with-bdeps=y " +
+ "--selective=n " + package)
+ if OPTS["workon"]:
+ cmdline += " " + " ".join(OPTS["workon"])
print "+ %s" % cmdline
# Store output in a temp file as it is too big for a unix pipe.
@@ -155,11 +160,11 @@ def GetDepsFromPortage(package):
stdout_buffer = tempfile.TemporaryFile()
# Launch the subprocess.
start = time.time()
- depsproc = subprocess.Popen(shlex.split(cmdline), stderr=stderr_buffer,
+ depsproc = subprocess.Popen(shlex.split(str(cmdline)), stderr=stderr_buffer,
stdout=stdout_buffer, bufsize=64*1024)
depsproc.wait()
seconds = time.time() - start
- print "Deps calculated in %d:%04.1fs" % (seconds / 60, seconds % 60)
+ print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
stderr_buffer.seek(0)
stderr_raw = stderr_buffer.read()
info_start = stderr_raw.find("digraph")
@@ -259,6 +264,10 @@ def DepsToTree(lines):
updatedep[fullpkg].setdefault("action", doins)
# Add the type of dep.
updatedep[fullpkg].setdefault("deptype", deptype)
+ # Add the long name of the package
+ updatedep[fullpkg].setdefault("pkgpath", "%s/%s" % (pkgdir, pkgname))
+ # Add the short name of the package
+ updatedep[fullpkg].setdefault("pkgname", pkgname)
# Drop any stack entries below our depth.
deps_stack = deps_stack[0:depth]
@@ -283,6 +292,8 @@ def DepsToTree(lines):
# Add the type of dep.
updatedep[pkgname].setdefault("action", "world")
updatedep[pkgname].setdefault("deptype", "normal")
+ updatedep[pkgname].setdefault("pkgpath", None)
+ updatedep[pkgname].setdefault("pkgname", None)
# Drop any obsolete stack entries.
deps_stack = deps_stack[0:depth]
@@ -297,12 +308,14 @@ def DepsToTree(lines):
uninstall = False
if oldversion and (desc.find("U") != -1 or desc.find("D") != -1):
uninstall = True
+ replace = desc.find("R") != -1
fullpkg = "%s/%s-%s" % (pkgdir, pkgname, version)
deps_info[fullpkg] = {"idx": len(deps_info),
"pkgdir": pkgdir,
"pkgname": pkgname,
"oldversion": oldversion,
- "uninstall": uninstall}
+ "uninstall": uninstall,
+ "replace": replace}
else:
# Is this a package that failed to match our huge regex?
m = re_failed.match(line)
@@ -328,17 +341,19 @@ def PrintTree(deps, depth=""):
PrintTree(deps[entry]["deps"], depth=depth + " ")
-def GenDependencyGraph(deps_tree, deps_info):
+def GenDependencyGraph(deps_tree, deps_info, package_names):
"""Generate a doubly linked dependency graph.
Args:
deps_tree: Dependency tree structure.
deps_info: More details on the dependencies.
+ package_names: Names of packages to add to the world file.
Returns:
Deps graph in the form of a dict of packages, with each package
specifying a "needs" list and "provides" list.
"""
deps_map = {}
+ pkgpaths = {}
def ReverseTree(packages):
"""Convert tree to digraph.
@@ -352,8 +367,13 @@ def GenDependencyGraph(deps_tree, deps_info):
"""
for pkg in packages:
action = packages[pkg]["action"]
+ pkgpath = packages[pkg]["pkgpath"]
+ pkgname = packages[pkg]["pkgname"]
+ pkgpaths[pkgpath] = pkg
+ pkgpaths[pkgname] = pkg
this_pkg = deps_map.setdefault(
- pkg, {"needs": set(), "provides": set(), "action": "nomerge"})
+ pkg, {"needs": {}, "provides": set(), "action": "nomerge",
+ "workon": False, "cmdline": False})
if action != "nomerge":
this_pkg["action"] = action
this_pkg["deps_info"] = deps_info.get(pkg)
@@ -363,14 +383,25 @@ def GenDependencyGraph(deps_tree, deps_info):
dep_type = dep_item["deptype"]
if dep_type != "(runtime_post)":
dep_pkg["provides"].add(pkg)
- this_pkg["needs"].add(dep)
+ this_pkg["needs"][dep] = dep_type
def RemoveInstalledPackages():
"""Remove installed packages, propagating dependencies."""
+ if "--selective" in EMERGE_OPTS:
+ selective = EMERGE_OPTS["--selective"] != "n"
+ else:
+ selective = "--noreplace" in EMERGE_OPTS or "--update" in EMERGE_OPTS
rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
+ for pkg, info in deps_info.items():
+ if selective and not deps_map[pkg]["workon"] and info["replace"]:
+ rm_pkgs.add(pkg)
for pkg in rm_pkgs:
this_pkg = deps_map[pkg]
+ if this_pkg["cmdline"] and "--oneshot" not in EMERGE_OPTS:
+ # If "cmdline" is set, this is a world update that was passed on the
+ # command-line. Keep these unless we're in --oneshot mode.
+ continue
needs = this_pkg["needs"]
provides = this_pkg["provides"]
for dep in needs:
@@ -381,47 +412,52 @@ def GenDependencyGraph(deps_tree, deps_info):
for target in provides:
target_needs = deps_map[target]["needs"]
target_needs.update(needs)
- target_needs.discard(pkg)
- target_needs.discard(target)
+ if pkg in target_needs:
+ del target_needs[pkg]
+ if target in target_needs:
+ del target_needs[target]
del deps_map[pkg]
- def SanitizeDep(basedep, currdep, oldstack, limit):
+ def SanitizeDep(basedep, currdep, visited, cycle):
"""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.
+ visited: Nodes visited so far.
+ cycle: Array where cycle of circular dependencies should be stored.
TODO(): Break RDEPEND preferentially.
Returns:
True iff circular dependencies are found.
"""
- if limit == 0:
- return
- for dep in deps_map[currdep]["needs"]:
- stack = oldstack + [dep]
- if basedep in deps_map[dep]["needs"] or dep == basedep:
- if dep != basedep:
- stack += [basedep]
- print "Remove cyclic dependency from:"
- for i in xrange(0, len(stack) - 1):
- print " %s -> %s " % (stack[i], stack[i+1])
- return True
- if dep not in oldstack and SanitizeDep(basedep, dep, stack, limit - 1):
- return True
- return
+ if currdep not in visited:
+ visited.add(currdep)
+ for dep in deps_map[currdep]["needs"]:
+ if dep == basedep or SanitizeDep(basedep, dep, visited, cycle):
+ cycle.insert(0, dep)
+ return True
+ return False
def SanitizeTree():
- """Remove circular dependencies up to cycle length 32."""
+ """Remove circular dependencies."""
start = time.time()
for basedep in deps_map:
- for dep in deps_map[basedep]["needs"].copy():
- if deps_info[basedep]["idx"] <= deps_info[dep]["idx"]:
- if SanitizeDep(basedep, dep, [basedep, dep], 31):
- print "Breaking", basedep, " -> ", dep
- deps_map[basedep]["needs"].remove(dep)
- deps_map[dep]["provides"].remove(basedep)
+ this_pkg = deps_map[basedep]
+ if this_pkg["action"] == "world":
+ # world file updates can't be involved in cycles,
+ # and they don't have deps_info, so skip them.
+ continue
+ for dep in this_pkg["needs"].copy():
+ cycle = []
+ if (deps_info[basedep]["idx"] <= deps_info[dep]["idx"] and
+ SanitizeDep(basedep, dep, set(), cycle)):
+ cycle[:0] = [basedep, dep]
+ print "Breaking cycle:"
+ for i in range(len(cycle) - 1):
+ deptype = deps_map[cycle[i]]["needs"][cycle[i+1]]
+ print " %s -> %s %s" % (cycle[i], cycle[i+1], deptype)
+ del this_pkg["needs"][dep]
+ deps_map[dep]["provides"].remove(basedep)
seconds = time.time() - start
print "Tree sanitized in %d:%04.1fs" % (seconds / 60, seconds % 60)
@@ -443,8 +479,49 @@ def GenDependencyGraph(deps_tree, deps_info):
deps_map[needed_pkg]["provides"].add(bad_pkg)
deps_map[bad_pkg]["needs"].add(needed_pkg)
+ def WorkOnChildren(pkg):
+ """Mark this package and all packages it provides as workon packages."""
+
+ this_pkg = deps_map[pkg]
+ if this_pkg["workon"]:
+ return False
+
+ this_pkg["workon"] = True
+ updated = False
+ for w in this_pkg["provides"]:
+ if WorkOnChildren(w):
+ updated = True
+
+ if this_pkg["action"] == "nomerge":
+ pkgpath = deps_tree[pkg]["pkgpath"]
+ if pkgpath is not None:
+ OPTS["workon"].add(pkgpath)
+ updated = True
+
+ return updated
+
ReverseTree(deps_tree)
AddSecretDeps()
+
+ if "no-workon-deps" in OPTS:
+ for pkgpath in OPTS["workon"].copy():
+ pkg = pkgpaths[pkgpath]
+ deps_map[pkg]["workon"] = True
+ else:
+ mergelist_updated = False
+ for pkgpath in OPTS["workon"].copy():
+ pkg = pkgpaths[pkgpath]
+ if WorkOnChildren(pkg):
+ mergelist_updated = True
+ if mergelist_updated:
+ print "List of packages to merge updated. Recalculate dependencies..."
+ return None
+
+ for pkgpath in package_names:
+ dep_pkg = deps_map.get("original-%s" % pkgpath)
+ if dep_pkg and len(dep_pkg["needs"]) == 1:
+ dep_pkg["cmdline"] = True
+
RemoveInstalledPackages()
SanitizeTree()
return deps_map
@@ -477,17 +554,17 @@ class EmergeQueue(object):
self._failed = {}
def _LoadAvg(self):
- loads = open('/proc/loadavg', 'r').readline().split()[:3]
- return ' '.join(loads)
+ loads = open("/proc/loadavg", "r").readline().split()[:3]
+ return " ".join(loads)
def _Status(self):
"""Print status."""
seconds = time.time() - GLOBAL_START
- print "Pending %s, Ready %s, Running %s, Retrying %s, Total %s " \
- "[Time %dm%ds Load %s]" % (
- len(self._deps_map), len(self._emerge_queue),
- len(self._jobs), len(self._retry_queue), self._total_jobs,
- seconds / 60, seconds % 60, self._LoadAvg())
+ line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
+ "[Time %dm%ds Load %s]")
+ print line % (len(self._deps_map), len(self._emerge_queue),
+ len(self._jobs), len(self._retry_queue), self._total_jobs,
+ seconds / 60, seconds % 60, self._LoadAvg())
def _LaunchOneEmerge(self, target):
"""Run emerge --nodeps to do a single package install.
@@ -504,20 +581,35 @@ class EmergeQueue(object):
# "original-" signifies one of the packages we originally requested.
# Since we have explicitly installed the versioned package as a dep of
# this, we only need to tag in "world" that we are done with this
- # install request. "--select -n" indicates an addition to "world"
- # without an actual install.
+ # install request.
+ # --nodeps: Ignore dependencies -- we handle them internally.
+ # --noreplace: Don't replace or upgrade any packages. (In this case, the
+ # package is already installed, so we are just updating the
+ # world file.)
+ # --selective: Make sure that --noreplace sticks even if --selective=n is
+ # specified by the user on the command-line.
+ # NOTE: If the user specifies --oneshot on the command-line, this command
+ # will do nothing. That is desired, since the user requested not to
+ # update the world file.
newtarget = target.replace("original-", "")
- cmdline = EmergeCommand() + " --nodeps --select --noreplace " + newtarget
+ cmdline = (EmergeCommand() + " --nodeps --selective --noreplace " +
+ newtarget)
else:
# This package is a dependency of something we specifically
# requested. Therefore we should install it but not allow it
- # in the "world" file, which represents explicit intalls.
- # "--oneshot" here will prevent it from being tagged in world.
- cmdline = EmergeCommand() + " --nodeps --oneshot =" + target
- deps_info = self._deps_map[target]["deps_info"]
+ # in the "world" file, which represents explicit installs.
+ # --oneshot" here will prevent it from being tagged in world.
+ cmdline = EmergeCommand() + " --nodeps --oneshot "
+ this_pkg = self._deps_map[target]
+ if this_pkg["workon"]:
+ # --usepkg=n --getbinpkg=n: Build from source
+ # --selective=n: Re-emerge even if package is already installed.
+ cmdline += "--usepkg=n --getbinpkg=n --selective=n "
+ cmdline += "=" + target
+ deps_info = this_pkg["deps_info"]
if deps_info["uninstall"]:
package = "%(pkgdir)s/%(pkgname)s-%(oldversion)s" % deps_info
- cmdline += " && %s -1C =%s" % (EmergeCommand(), package)
+ cmdline += " && %s -C =%s" % (EmergeCommand(), package)
print "+ %s" % cmdline
@@ -543,7 +635,7 @@ class EmergeQueue(object):
def _Finish(self, target):
"""Mark a target as completed and unblock dependecies."""
for dep in self._deps_map[target]["provides"]:
- self._deps_map[dep]["needs"].remove(target)
+ del self._deps_map[dep]["needs"][target]
if not self._deps_map[dep]["needs"]:
if VERBOSE:
print "Unblocking %s" % dep
@@ -563,9 +655,10 @@ class EmergeQueue(object):
dependency graph to merge.
"""
secs = 0
+ max_jobs = EMERGE_OPTS.get("--jobs", 256)
while self._deps_map:
# If we have packages that are ready, kick them off.
- if self._emerge_queue and len(self._jobs) < JOBS:
+ if self._emerge_queue and len(self._jobs) < max_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
@@ -653,25 +746,43 @@ class EmergeQueue(object):
# Main control code.
-PACKAGE, EMERGE_ARGS, BOARD, JOBS = ParseArgs(sys.argv)
+OPTS, EMERGE_ACTION, EMERGE_OPTS, EMERGE_FILES = ParseArgs(sys.argv)
-if not PACKAGE:
- # No packages. Pass straight through to emerge.
- # Allows users to just type ./parallel_emerge --depclean
+if EMERGE_ACTION is not None:
+ # Pass action arguments straight through to emerge
+ EMERGE_OPTS["--%s" % EMERGE_ACTION] = True
sys.exit(os.system(EmergeCommand()))
+elif not EMERGE_FILES:
+ Usage()
+ sys.exit(1)
print "Starting fast-emerge."
-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"
-dependency_tree, dependency_info = DepsToTree(deps_output)
-if VERBOSE:
- print "Print tree"
- PrintTree(dependency_tree)
+print " Building package %s on %s" % (" ".join(EMERGE_FILES),
+ OPTS.get("board", "root"))
-print "Generate dependency graph."
-dependency_graph = GenDependencyGraph(dependency_tree, dependency_info)
+# If the user supplied the --workon option, we may have to run emerge twice
+# to generate a dependency ordering for packages that depend on the workon
+# packages.
+for it in range(2):
+ print "Running emerge to generate deps"
+ deps_output = GetDepsFromPortage(" ".join(EMERGE_FILES))
+
+ print "Processing emerge output"
+ dependency_tree, dependency_info = DepsToTree(deps_output)
+
+ if VERBOSE:
+ print "Print tree"
+ PrintTree(dependency_tree)
+
+ print "Generate dependency graph."
+ dependency_graph = GenDependencyGraph(dependency_tree, dependency_info,
+ EMERGE_FILES)
+
+ if dependency_graph is not None:
+ break
+else:
+ print "Can't crack cycle"
+ sys.exit(1)
if VERBOSE:
PrintDepsMap(dependency_graph)
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698