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

Side by Side Diff: parallel_emerge

Issue 2853031: Cleanup parallel_emerge a bit. (Closed) Base URL: ssh://git@chromiumos-git/crosutils.git
Patch Set: Address review comments. Other minor fixes. 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 [emerge args] package 9 ./parallel_emerge --board=BOARD [emerge args] package
10 10
(...skipping 13 matching lines...) Expand all
24 Parallel Emerge unlocks two things during operation, here's what you 24 Parallel Emerge unlocks two things during operation, here's what you
25 must do to keep this safe: 25 must do to keep this safe:
26 * Storage dir containing binary packages. - Don't emerge new 26 * Storage dir containing binary packages. - Don't emerge new
27 packages while installing the existing ones. 27 packages while installing the existing ones.
28 * Portage database - You must not examine deps while modifying the 28 * Portage database - You must not examine deps while modifying the
29 database. Therefore you may only parallelize "-p" read only access, 29 database. Therefore you may only parallelize "-p" read only access,
30 or "--nodeps" write only access. 30 or "--nodeps" write only access.
31 Caveats: 31 Caveats:
32 * Some ebuild packages have incorrectly specified deps, and running 32 * Some ebuild packages have incorrectly specified deps, and running
33 them in parallel is more likely to bring out these failures. 33 them in parallel is more likely to bring out these failures.
34 * Portage "world" is a record of explicitly installed packages. In
35 this parallel scheme, explicitly installed packages are installed
36 twice, once for the real install, and once for world file addition.
37 * Some ebuilds (especially the build part) have complex dependencies 34 * Some ebuilds (especially the build part) have complex dependencies
38 that are not captured well by this script (it may be necessary to 35 that are not captured well by this script (it may be necessary to
39 install an old package to build, but then install a newer version 36 install an old package to build, but then install a newer version
40 of the same package for a runtime dep). This script is only 37 of the same package for a runtime dep).
41 currently stable for binpkg installs.
42 """ 38 """
43 39
44 import os 40 import os
45 import re 41 import re
46 import shlex 42 import shlex
47 import subprocess 43 import subprocess
48 import sys 44 import sys
49 import tempfile 45 import tempfile
50 import time 46 import time
51 47
52 48
53 def Usage(): 49 def Usage():
54 print "Usage:" 50 print "Usage:"
55 print " ./parallel_emerge --board=BOARD [emerge args] package" 51 print " ./parallel_emerge --board=BOARD --jobs=JOBS [emerge args] package"
56 sys.exit(1) 52 sys.exit(1)
57 53
58 54
59 # These are dependencies that are not specified in the package, 55 # These are dependencies that are not specified in the package,
60 # but will prevent the package from installing. 56 # but will prevent the package from installing.
61 secret_deps = {} 57 secret_deps = {}
62 58
63 # Globals: package we are building, board we are targeting, 59 # Globals: package we are building, board we are targeting,
64 # emerge args we are passing through. 60 # emerge args we are passing through.
65 PACKAGE = None 61 PACKAGE = None
66 EMERGE_ARGS = "" 62 EMERGE_ARGS = ""
67 BOARD = None 63 BOARD = None
68 64
69 # Runtime flags. TODO(): maybe make these commandline options or 65 # Runtime flags. TODO(): Maybe make these command-line options or
70 # environment veriables. 66 # environment variables.
71 VERBOSE = False 67 VERBOSE = False
72 AUTOCLEAN = False 68 AUTOCLEAN = False
73 69
74 70
75 def ParseArgs(argv): 71 def ParseArgs(argv):
76 """Set global vars based on command line. 72 """Set global vars based on command line.
77 73
78 We need to be compatible with emerge arg format. 74 We need to be compatible with emerge arg format.
79 We scrape --board-XXX, and distinguish between args 75 We scrape --board=XXX and --jobs=XXX, and distinguish between args
80 and package names. 76 and package names.
81 TODO(): robustify argument processing, as it's possible to 77 TODO(): Robustify argument processing, as it's possible to
82 pass in many two argument parameters that are difficult 78 pass in many two argument parameters that are difficult
83 to programmaitcally identify, although we don't currently 79 to programmatically identify, although we don't currently
84 use any besides --bdeps <y|n>. 80 use any besides --with-bdeps <y|n>.
85 Args: 81 Args:
86 argv: arguments list 82 argv: arguments list
87 Returns: 83 Returns:
88 triplet of (package list, emerge argumens, board string) 84 triplet of (package list, emerge argumens, board string)
89 """ 85 """
90 if VERBOSE: 86 if VERBOSE:
91 print argv 87 print argv
92 board_arg = None 88 board_arg = None
89 jobs_arg = 0
93 package_args = [] 90 package_args = []
94 emerge_passthru_args = "" 91 emerge_passthru_args = ""
95 re_board = re.compile(r"--board=(?P<board>.*)")
96 for arg in argv[1:]: 92 for arg in argv[1:]:
97 # Check if the arg begins with '-' 93 # Specifically match "--board=" and "--jobs=".
98 if arg[0] == "-" or arg == "y" or arg == "n": 94 if arg.startswith("--board="):
99 # Specifically match "--board=" 95 board_arg = arg.replace("--board=", "")
100 m = re_board.match(arg) 96 elif arg.startswith("--jobs="):
101 if m: 97 try:
102 board_arg = m.group("board") 98 jobs_arg = int(arg.replace("--jobs=", ""))
103 else: 99 except ValueError:
104 # Pass through to emerge. 100 print "Unrecognized argument:", arg
105 emerge_passthru_args = emerge_passthru_args + " " + arg 101 Usage()
102 sys.exit(1)
103 elif arg.startswith("-") or arg == "y" or arg == "n":
104 # Not a package name, so pass through to emerge.
105 emerge_passthru_args = emerge_passthru_args + " " + arg
106 else: 106 else:
107 # Only non-dashed arg should be the target package.
108 package_args.append(arg) 107 package_args.append(arg)
109 108
110 if not package_args: 109 if not package_args and not emerge_passthru_args:
111 Usage() 110 Usage()
112 sys.exit(1) 111 sys.exit(1)
113 112
113 # Default to lots of jobs
114 if jobs_arg <= 0:
115 jobs_arg = 256
116
114 # Set globals. 117 # Set globals.
115 return " ".join(package_args), emerge_passthru_args, board_arg 118 return " ".join(package_args), emerge_passthru_args, board_arg, jobs_arg
116 119
117 120
118 def EmergeCommand(): 121 def EmergeCommand():
119 """Helper function to return the base emerge commandline. 122 """Helper function to return the base emerge commandline.
120 123
121 This is configured for board type, and including pass thru args, 124 This is configured for board type, and including pass thru args,
122 using global variables. TODO(): unglobalfy. 125 using global variables. TODO(): Unglobalfy.
123 Returns: 126 Returns:
124 string containing emerge command. 127 string containing emerge command.
125 """ 128 """
126 emerge = "emerge" 129 emerge = "emerge"
127 if BOARD: 130 if BOARD:
128 emerge += "-" + BOARD 131 emerge += "-" + BOARD
129 return emerge + " " + EMERGE_ARGS 132 return emerge + " " + EMERGE_ARGS
130 133
131 134
132 def GetDepsFromPortage(package): 135 def GetDepsFromPortage(package):
133 """Get dependency tree info by running emerge. 136 """Get dependency tree info by running emerge.
134 137
135 Run 'emerge -p --debug package', and get a text output of all deps. 138 Run 'emerge -p --debug package', and get a text output of all deps.
136 TODO(): Put dep caclation in a library, as cros_extract_deps 139 TODO(): Put dep calculation in a library, as cros_extract_deps
137 also uses this code. 140 also uses this code.
138 Args: 141 Args:
139 package: string containing the packages to build. 142 package: String containing the packages to build.
140 Returns: 143 Returns:
141 text output of emerge -p --debug, which can be processed elsewhere. 144 Text output of emerge -p --debug, which can be processed elsewhere.
142 """ 145 """
143 print "Calculating deps for package %s" % package 146 print "Calculating deps for package %s" % package
144 cmdline = EmergeCommand() + " -p --debug --color=n " + package 147 cmdline = EmergeCommand() + " -p --debug --color=n " + package
145 print "+ %s" % cmdline 148 print "+ %s" % cmdline
146 149
147 # Store output in a temp file as it is too big for a unix pipe. 150 # Store output in a temp file as it is too big for a unix pipe.
148 stderr_buffer = tempfile.TemporaryFile() 151 stderr_buffer = tempfile.TemporaryFile()
149 stdout_buffer = tempfile.TemporaryFile() 152 stdout_buffer = tempfile.TemporaryFile()
150 # Launch the subprocess. 153 # Launch the subprocess.
151 start = time.time() 154 start = time.time()
(...skipping 18 matching lines...) Expand all
170 print "Failed to generate deps" 173 print "Failed to generate deps"
171 sys.exit(1) 174 sys.exit(1)
172 175
173 return lines 176 return lines
174 177
175 178
176 def DepsToTree(lines): 179 def DepsToTree(lines):
177 """Regex the output from 'emerge --debug' to generate a nested dict of deps. 180 """Regex the output from 'emerge --debug' to generate a nested dict of deps.
178 181
179 Args: 182 Args:
180 lines: output from 'emerge -p --debug package' 183 lines: Output from 'emerge -p --debug package'.
181 Returns: 184 Returns:
182 dep_tree: nested dict of dependencies, as specified by emerge. 185 dep_tree: Nested dict of dependencies, as specified by emerge.
183 there may be dupes, or circular deps. 186 There may be dupes, or circular deps.
184 187
185 We need to regex lines as follows: 188 We need to regex lines as follows:
186 hard-host-depends depends on 189 hard-host-depends depends on
187 ('ebuild', '/', 'dev-lang/swig-1.3.36', 'merge') depends on 190 ('ebuild', '/', 'dev-lang/swig-1.3.36', 'merge') depends on
188 ('ebuild', '/', 'dev-lang/perl-5.8.8-r8', 'merge') (buildtime) 191 ('ebuild', '/', 'dev-lang/perl-5.8.8-r8', 'merge') (buildtime)
189 ('binary', '/.../rootfs/', 'sys-auth/policykit-0.9-r1', 'merge') depends on 192 ('binary', '/.../rootfs/', 'sys-auth/policykit-0.9-r1', 'merge') depends on
190 ('binary', '/.../rootfs/', 'x11-misc/xbitmaps-1.1.0', 'merge') (no children) 193 ('binary', '/.../rootfs/', 'x11-misc/xbitmaps-1.1.0', 'merge') (no children)
191 """ 194 """
192 195
193 re_deps = re.compile(r"(?P<indent>\W*)\(\'(?P<pkgtype>\w+)\', " 196 re_deps = re.compile(r"(?P<indent>\W*)\(\'(?P<pkgtype>\w+)\', "
194 r"\'(?P<destination>[\w/\.-]+)\'," 197 r"\'(?P<destination>[\w/\.-]+)\',"
195 r" \'(?P<pkgdir>[\w\+-]+)/(?P<pkgname>[\w\+-]+)-" 198 r" \'(?P<pkgdir>[\w\+-]+)/(?P<pkgname>[\w\+-]+)-"
196 r"(?P<version>\d+[\w\.-]*)\', \'(?P<action>\w+)\'\) " 199 r"(?P<version>\d+[\w\.-]*)\', \'(?P<action>\w+)\'\) "
197 r"(?P<deptype>(depends on|\(.*\)))") 200 r"(?P<deptype>(depends on|\(.*\)))")
198 re_origdeps = re.compile(r"(?P<pkgname>[\w\+/-]+) depends on") 201 re_origdeps = re.compile(r"(?P<pkgname>[\w\+/-]+) depends on")
199 re_installed_package = re.compile( 202 re_installed_package = re.compile(
200 r"\[(?P<desc>[^\]]*)\] " 203 r"\[(?P<desc>[^\]]*)\] "
201 r"(?P<pkgdir>[\w\+-]+)/" 204 r"(?P<pkgdir>[\w\+-]+)/"
202 r"(?P<pkgname>[\w\+-]+)-" 205 r"(?P<pkgname>[\w\+-]+)-"
203 r"(?P<version>\d+[\w\.-]*)( \[" 206 r"(?P<version>\d+[\w\.-]*)( \["
204 r"(?P<oldversion>\d+[\w\.-]*)\])?" 207 r"(?P<oldversion>\d+[\w\.-]*)\])?"
205 ) 208 )
206 re_failed = re.compile(r".*depends on.*") 209 re_failed = re.compile(r".*\) depends on.*")
207 deps_tree = {} 210 deps_tree = {}
208 deps_stack = [] 211 deps_stack = []
209 deps_info = {} 212 deps_info = {}
210 for line in lines: 213 for line in lines:
211 m = re_deps.match(line) 214 m = re_deps.match(line)
212 m_orig = re_origdeps.match(line) 215 m_orig = re_origdeps.match(line)
213 m_installed = re_installed_package.match(line) 216 m_installed = re_installed_package.match(line)
214 if m: 217 if m:
215 pkgname = m.group("pkgname") 218 pkgname = m.group("pkgname")
216 pkgdir = m.group("pkgdir") 219 pkgdir = m.group("pkgdir")
217 pkgtype = m.group("pkgtype") 220 pkgtype = m.group("pkgtype")
218 indent = m.group("indent") 221 indent = m.group("indent")
219 doins = m.group("action") 222 doins = m.group("action")
220 deptype = m.group("deptype") 223 deptype = m.group("deptype")
221 depth = 1 224 depth = 1
222 if not indent: 225 if not indent:
223 depth = 0 226 depth = 0
224 version = m.group("version") 227 version = m.group("version")
225 228
226 # If we are indented, we should have 229 # If we are indented, we should have
227 # found a "depends on" previously. 230 # found a "depends on" previously.
228 if len(deps_stack) < depth: 231 if len(deps_stack) < depth:
229 print "FAIL: corrupt input at:" 232 print "FAIL: corrupt input at:"
230 print line 233 print line
231 print "No Parent." 234 print "No Parent."
232 sys.exit(1) 235 sys.exit(1)
233 236
234 # Go step by step through stack and tree 237 # Go step by step through stack and tree
235 # until we find our parent. Generate 238 # until we find our parent.
236 updatedep = deps_tree 239 updatedep = deps_tree
237 for i in range(0, depth): 240 for i in range(0, depth):
238 updatedep = updatedep[deps_stack[i]]["deps"] 241 updatedep = updatedep[deps_stack[i]]["deps"]
239 242
240 # Pretty print what we've captured. 243 # Pretty print what we've captured.
241 indent = "|" + "".ljust(depth, "_") 244 indent = "|" + "".ljust(depth, "_")
242 fullpkg = "%s/%s-%s" % (pkgdir, pkgname, version) 245 fullpkg = "%s/%s-%s" % (pkgdir, pkgname, version)
243 if VERBOSE: 246 if VERBOSE:
244 print ("" + indent + " " + pkgdir + "/" + pkgname + " - " + 247 print ("" + indent + " " + pkgdir + "/" + pkgname + " - " +
245 version + " (" + pkgtype + ", " + doins + 248 version + " (" + pkgtype + ", " + doins +
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
294 fullpkg = "%s/%s-%s" % (pkgdir, pkgname, version) 297 fullpkg = "%s/%s-%s" % (pkgdir, pkgname, version)
295 deps_info[fullpkg] = {"idx": len(deps_info), 298 deps_info[fullpkg] = {"idx": len(deps_info),
296 "pkgdir": pkgdir, 299 "pkgdir": pkgdir,
297 "pkgname": pkgname, 300 "pkgname": pkgname,
298 "oldversion": oldversion, 301 "oldversion": oldversion,
299 "uninstall": uninstall} 302 "uninstall": uninstall}
300 else: 303 else:
301 # Is this a package that failed to match our huge regex? 304 # Is this a package that failed to match our huge regex?
302 m = re_failed.match(line) 305 m = re_failed.match(line)
303 if m: 306 if m:
307 print "\n".join(lines)
304 print "FAIL: Couldn't understand line:" 308 print "FAIL: Couldn't understand line:"
305 print line 309 print line
306 sys.exit(1) 310 sys.exit(1)
307 311
308 return deps_tree, deps_info 312 return deps_tree, deps_info
309 313
310 314
311 def PrintTree(deps, depth=""): 315 def PrintTree(deps, depth=""):
312 """Print the deps we have seen in the emerge output. 316 """Print the deps we have seen in the emerge output.
313 317
314 Args: 318 Args:
315 deps: dependency tree structure. 319 deps: Dependency tree structure.
316 depth: allows printing the tree recursively, with indentation. 320 depth: Allows printing the tree recursively, with indentation.
317 """ 321 """
318 for entry in deps: 322 for entry in deps:
319 action = deps[entry]["action"] 323 action = deps[entry]["action"]
320 print "%s %s (%s)" % (depth, entry, action) 324 print "%s %s (%s)" % (depth, entry, action)
321 PrintTree(deps[entry]["deps"], depth=depth + " ") 325 PrintTree(deps[entry]["deps"], depth=depth + " ")
322 326
323 327
324 def GenDependencyGraph(deps_tree, deps_info): 328 def GenDependencyGraph(deps_tree, deps_info):
325 """Generate a doubly linked dependency graph. 329 """Generate a doubly linked dependency graph.
326 330
327 Args: 331 Args:
328 deps_tree: dependency tree structure. 332 deps_tree: Dependency tree structure.
329 deps_info: more info on the dependencies. 333 deps_info: More details on the dependencies.
330 Returns: 334 Returns:
331 Deps graph in the form of a dict of packages, with each package 335 Deps graph in the form of a dict of packages, with each package
332 specifying a "needs" list and "provides" list. 336 specifying a "needs" list and "provides" list.
333 """ 337 """
334 deps_map = {} 338 deps_map = {}
335 339
336 def ReverseTree(packages): 340 def ReverseTree(packages):
337 """Convert tree to digraph. 341 """Convert tree to digraph.
338 342
339 Take the tree of package -> requirements and reverse it to a digraph of 343 Take the tree of package -> requirements and reverse it to a digraph of
340 buildable packages -> packages they unblock 344 buildable packages -> packages they unblock.
341 Args: 345 Args:
342 packages: tree(s) of dependencies 346 packages: Tree(s) of dependencies.
343 Returns: 347 Returns:
344 unsanitized digraph 348 Unsanitized digraph.
345 """ 349 """
346 for pkg in packages: 350 for pkg in packages:
347 action = packages[pkg]["action"] 351 action = packages[pkg]["action"]
348 this_pkg = deps_map.setdefault( 352 this_pkg = deps_map.setdefault(
349 pkg, {"needs": set(), "provides": set(), "action": "nomerge"}) 353 pkg, {"needs": set(), "provides": set(), "action": "nomerge"})
350 if action != "nomerge": 354 if action != "nomerge":
351 this_pkg["action"] = action 355 this_pkg["action"] = action
352 this_pkg["deps_info"] = deps_info.get(pkg) 356 this_pkg["deps_info"] = deps_info.get(pkg)
353 ReverseTree(packages[pkg]["deps"]) 357 ReverseTree(packages[pkg]["deps"])
354 for dep, dep_item in packages[pkg]["deps"].items(): 358 for dep, dep_item in packages[pkg]["deps"].items():
355 dep_pkg = deps_map[dep] 359 dep_pkg = deps_map[dep]
356 dep_type = dep_item["deptype"] 360 dep_type = dep_item["deptype"]
357 if dep_type != "(runtime_post)": 361 if dep_type != "(runtime_post)":
358 dep_pkg["provides"].add(pkg) 362 dep_pkg["provides"].add(pkg)
359 this_pkg["needs"].add(dep) 363 this_pkg["needs"].add(dep)
360 364
361 def RemoveInstalledPackages(): 365 def RemoveInstalledPackages():
362 """Remove installed packages, propagating dependencies""" 366 """Remove installed packages, propagating dependencies."""
363 367
364 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys()) 368 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
365 for pkg in rm_pkgs: 369 for pkg in rm_pkgs:
366 this_pkg = deps_map[pkg] 370 this_pkg = deps_map[pkg]
367 needs = this_pkg["needs"] 371 needs = this_pkg["needs"]
368 provides = this_pkg["provides"] 372 provides = this_pkg["provides"]
369 for dep in needs: 373 for dep in needs:
370 dep_provides = deps_map[dep]["provides"] 374 dep_provides = deps_map[dep]["provides"]
371 dep_provides.update(provides) 375 dep_provides.update(provides)
372 dep_provides.discard(pkg) 376 dep_provides.discard(pkg)
373 dep_provides.discard(dep) 377 dep_provides.discard(dep)
374 for target in provides: 378 for target in provides:
375 target_needs = deps_map[target]["needs"] 379 target_needs = deps_map[target]["needs"]
376 target_needs.update(needs) 380 target_needs.update(needs)
377 target_needs.discard(pkg) 381 target_needs.discard(pkg)
378 target_needs.discard(target) 382 target_needs.discard(target)
379 del deps_map[pkg] 383 del deps_map[pkg]
380 384
381
382 def SanitizeDep(basedep, currdep, oldstack, limit): 385 def SanitizeDep(basedep, currdep, oldstack, limit):
383 """Search for circular deps between basedep and currdep, then recurse. 386 """Search for circular deps between basedep and currdep, then recurse.
384 387
385 Args: 388 Args:
386 basedep: original dependency, top of stack. 389 basedep: Original dependency, top of stack.
387 currdep: bottom of our current recursion, bottom of stack. 390 currdep: Bottom of our current recursion, bottom of stack.
388 oldstack: current dependency chain. 391 oldstack: Current dependency chain.
389 limit: how many more levels of recusion to go through, max. 392 limit: How many more levels of recusion to go through, max.
390 TODO(): Break RDEPEND preferentially. 393 TODO(): Break RDEPEND preferentially.
391 Returns: 394 Returns:
392 True iff circular dependencies are found. 395 True iff circular dependencies are found.
393 """ 396 """
394 if limit == 0: 397 if limit == 0:
395 return 398 return
396 for dep in deps_map[currdep]["needs"]: 399 for dep in deps_map[currdep]["needs"]:
397 stack = oldstack + [dep] 400 stack = oldstack + [dep]
398 if basedep in deps_map[dep]["needs"] or dep == basedep: 401 if basedep in deps_map[dep]["needs"] or dep == basedep:
399 if dep != basedep: 402 if dep != basedep:
400 stack += [basedep] 403 stack += [basedep]
401 print "Remove cyclic dependency from:" 404 print "Remove cyclic dependency from:"
402 for i in xrange(0, len(stack) - 1): 405 for i in xrange(0, len(stack) - 1):
403 print " %s -> %s " % (stack[i], stack[i+1]) 406 print " %s -> %s " % (stack[i], stack[i+1])
404 return True 407 return True
405 if dep not in oldstack and SanitizeDep(basedep, dep, stack, limit - 1): 408 if dep not in oldstack and SanitizeDep(basedep, dep, stack, limit - 1):
406 return True 409 return True
407 return 410 return
408 411
409 def SanitizeTree(): 412 def SanitizeTree():
410 """Remove circular dependencies up to cycle length 32.""" 413 """Remove circular dependencies up to cycle length 32."""
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
475 print "Pending %s, Ready %s, Running %s, Retrying %s, Total %s" % ( 478 print "Pending %s, Ready %s, Running %s, Retrying %s, Total %s" % (
476 len(self._deps_map), len(self._emerge_queue), 479 len(self._deps_map), len(self._emerge_queue),
477 len(self._jobs), len(self._retry_queue), self._total_jobs) 480 len(self._jobs), len(self._retry_queue), self._total_jobs)
478 481
479 def _LaunchOneEmerge(self, target): 482 def _LaunchOneEmerge(self, target):
480 """Run emerge --nodeps to do a single package install. 483 """Run emerge --nodeps to do a single package install.
481 484
482 If this is a pseudopackage, that means we're done, and can select in in the 485 If this is a pseudopackage, that means we're done, and can select in in the
483 world file. 486 world file.
484 Args: 487 Args:
485 target: the full package name of the package to install. 488 target: The full package name of the package to install.
486 eg. "sys-apps/portage-2.17" 489 eg. "sys-apps/portage-2.17"
487 Returns: 490 Returns:
488 triplet containing (target name, subprocess object, output buffer object) 491 Triplet containing (target name, subprocess object, output buffer object).
489 """ 492 """
490 if target.startswith("original-"): 493 if target.startswith("original-"):
491 # "original-" signifies one of the packages we originally requested. 494 # "original-" signifies one of the packages we originally requested.
492 # Since we have explicitly installed the versioned package as a dep of 495 # Since we have explicitly installed the versioned package as a dep of
493 # this, we only need to tag in "world" that we are done with this 496 # this, we only need to tag in "world" that we are done with this
494 # install request. "--select -n" indicates an addition to "world" 497 # install request. "--select -n" indicates an addition to "world"
495 # without an actual install. 498 # without an actual install.
496 newtarget = target.replace("original-", "") 499 newtarget = target.replace("original-", "")
497 cmdline = EmergeCommand() + " --nodeps --select --noreplace " + newtarget 500 cmdline = EmergeCommand() + " --nodeps --select --noreplace " + newtarget
498 else: 501 else:
499 # This package is a dependency of something we specifically 502 # This package is a dependency of something we specifically
500 # requested. Therefore we should install it but not allow it 503 # requested. Therefore we should install it but not allow it
501 # in the "world" file, which represents explicit intalls. 504 # in the "world" file, which represents explicit intalls.
502 # "--oneshot" here will prevent it from being tagged in world. 505 # "--oneshot" here will prevent it from being tagged in world.
503 cmdline = EmergeCommand() + " --nodeps --oneshot =" + target 506 cmdline = EmergeCommand() + " --nodeps --oneshot =" + target
504 deps_info = self._deps_map[target]["deps_info"] 507 deps_info = self._deps_map[target]["deps_info"]
505 if deps_info["uninstall"]: 508 if deps_info["uninstall"]:
506 package = "%(pkgdir)s/%(pkgname)s-%(oldversion)s" % deps_info 509 package = "%(pkgdir)s/%(pkgname)s-%(oldversion)s" % deps_info
507 cmdline += " && %s -1C =%s" % (EmergeCommand(), package) 510 cmdline += " && %s -1C =%s" % (EmergeCommand(), package)
508 511
509 print "+ %s" % cmdline 512 print "+ %s" % cmdline
510 513
511 # Store output in a temp file as it is too big for a unix pipe. 514 # Store output in a temp file as it is too big for a unix pipe.
512 stdout_buffer = tempfile.TemporaryFile() 515 stdout_buffer = tempfile.TemporaryFile()
513 # Modify the environment to disable locking. 516 # Modify the environment to disable locking.
514 portage_env = os.environ.copy() 517 portage_env = os.environ.copy()
515 portage_env["PORTAGE_LOCKS"] = "false" 518 portage_env["PORTAGE_LOCKS"] = "false"
516 portage_env["UNMERGE_DELAY"] = "0" 519 portage_env["UNMERGE_DELAY"] = "0"
517 # Autoclean rummages around in the portage database and uninstalls 520 # Autoclean rummages around in the portage database and uninstalls
518 # old packages. Definitely not necessary for build_image. However 521 # old packages. It's not parallel safe, so we skip it. Instead, we
519 # it may be necessary for incremental build_packages. It may also 522 # handle the cleaning ourselves by uninstalling old versions of any
520 # not be parallel safe. 523 # new packages we install.
521 if not AUTOCLEAN: 524 if not AUTOCLEAN:
522 portage_env["AUTOCLEAN"] = "no" 525 portage_env["AUTOCLEAN"] = "no"
523 # Launch the subprocess. 526 # Launch the subprocess.
524 emerge_proc = subprocess.Popen( 527 emerge_proc = subprocess.Popen(
525 cmdline, shell=True, stdout=stdout_buffer, 528 cmdline, shell=True, stdout=stdout_buffer,
526 stderr=subprocess.STDOUT, bufsize=64*1024, env=portage_env) 529 stderr=subprocess.STDOUT, bufsize=64*1024, env=portage_env)
527 530
528 return (target, emerge_proc, stdout_buffer) 531 return (target, emerge_proc, stdout_buffer)
529 532
530 def _Finish(self, target): 533 def _Finish(self, target):
(...skipping 13 matching lines...) Expand all
544 print "Retrying emerge of %s." % target 547 print "Retrying emerge of %s." % target
545 548
546 def Run(self): 549 def Run(self):
547 """Run through the scheduled ebuilds. 550 """Run through the scheduled ebuilds.
548 551
549 Keep running so long as we have uninstalled packages in the 552 Keep running so long as we have uninstalled packages in the
550 dependency graph to merge. 553 dependency graph to merge.
551 """ 554 """
552 while self._deps_map: 555 while self._deps_map:
553 # If we have packages that are ready, kick them off. 556 # If we have packages that are ready, kick them off.
554 if self._emerge_queue: 557 if self._emerge_queue and len(self._jobs) < JOBS:
555 target = self._emerge_queue.pop(0) 558 target = self._emerge_queue.pop(0)
556 action = self._deps_map[target]["action"] 559 action = self._deps_map[target]["action"]
557 # We maintain a tree of all deps, if this doesn't need 560 # We maintain a tree of all deps, if this doesn't need
558 # to be installed just free up it's children and continue. 561 # to be installed just free up it's children and continue.
559 # It is possible to reinstall deps of deps, without reinstalling 562 # It is possible to reinstall deps of deps, without reinstalling
560 # first level deps, like so: 563 # first level deps, like so:
561 # chromeos (merge) -> eselect (nomerge) -> python (merge) 564 # chromeos (merge) -> eselect (nomerge) -> python (merge)
562 if action == "nomerge": 565 if action == "nomerge":
563 self._Finish(target) 566 self._Finish(target)
564 else: 567 else:
565 # Kick off the build if it's marked to be built. 568 # Kick off the build if it's marked to be built.
566 print "Emerging %s (%s)" % (target, action) 569 print "Emerging %s (%s)" % (target, action)
567 job = self._LaunchOneEmerge(target) 570 job = self._LaunchOneEmerge(target)
568 # Append it to the active jobs list. 571 # Append it to the active jobs list.
569 self._jobs.append(job) 572 self._jobs.append(job)
570 continue 573 continue
571 # Wait a bit to see if maybe some jobs finish. You can't 574 # Wait a bit to see if maybe some jobs finish. You can't
572 # wait on a set of jobs in python, so we'll just poll. 575 # wait on a set of jobs in python, so we'll just poll.
573 time.sleep(1) 576 time.sleep(1)
574 577
575 # Check here that we are actually waiting for something. 578 # Check here that we are actually waiting for something.
576 if (not self._emerge_queue and 579 if (not self._emerge_queue and
577 not self._jobs and 580 not self._jobs and
578 self._deps_map): 581 self._deps_map):
579 # If we have failed on a package retry it now. 582 # If we have failed on a package, retry it now.
580 if self._retry_queue: 583 if self._retry_queue:
581 self._Retry() 584 self._Retry()
582 # If we have failed a package twice, just give up. 585 # If we have failed a package twice, just give up.
583 elif self._failed: 586 elif self._failed:
584 for failure, output in self._failed.items(): 587 for failure, output in self._failed.items():
585 print "Package failed: %s" % failure 588 print "Package failed: %s" % failure
586 print output 589 print output
587 PrintDepsMap(self._deps_map) 590 PrintDepsMap(self._deps_map)
588 print "Packages failed: %s" % " ,".join(self._failed.keys()) 591 print "Packages failed: %s" % " ,".join(self._failed.keys())
589 sys.exit(1) 592 sys.exit(1)
(...skipping 10 matching lines...) Expand all
600 # Clean up the subprocess. 603 # Clean up the subprocess.
601 job.wait() 604 job.wait()
602 # Get the output if we want to print it. 605 # Get the output if we want to print it.
603 stdout.seek(0) 606 stdout.seek(0)
604 output = stdout.read() 607 output = stdout.read()
605 608
606 # Remove from active jobs list, we are done with this process. 609 # Remove from active jobs list, we are done with this process.
607 self._jobs.remove((target, job, stdout)) 610 self._jobs.remove((target, job, stdout))
608 611
609 # Print if necessary. 612 # Print if necessary.
610 if VERBOSE: 613 if VERBOSE or job.returncode != 0:
611 print output 614 print output
612 if job.returncode != 0: 615 if job.returncode != 0:
613 # Handle job failure. 616 # Handle job failure.
614 if target in self._failed: 617 if target in self._failed:
615 # If this job has failed previously, give up. 618 # If this job has failed previously, give up.
616 print "Failed %s. Your build has failed." % target 619 print "Failed %s. Your build has failed." % target
617 else: 620 else:
618 # Queue up this build to try again after a long while. 621 # Queue up this build to try again after a long while.
619 self._retry_queue.append(target) 622 self._retry_queue.append(target)
620 self._failed[target] = output 623 self._failed[target] = output
621 print "Failed %s, retrying later." % target 624 print "Failed %s, retrying later." % target
622 else: 625 else:
623 if target in self._failed and self._retry_queue: 626 if target in self._failed and self._retry_queue:
624 # If we have successfully retried a failed package, and there 627 # If we have successfully retried a failed package, and there
625 # are more failed packages, try the next one. We will only have 628 # are more failed packages, try the next one. We will only have
626 # one retrying package actively running at a time. 629 # one retrying package actively running at a time.
627 self._Retry() 630 self._Retry()
628 631
629 print "Completed %s" % target 632 print "Completed %s" % target
630 # Mark as completed and unblock waiting ebuilds. 633 # Mark as completed and unblock waiting ebuilds.
631 self._Finish(target) 634 self._Finish(target)
632 635
633 # Print an update. 636 # Print an update.
634 self._Status() 637 self._Status()
635 638
636 639
637 # Main control code. 640 # Main control code.
641 PACKAGE, EMERGE_ARGS, BOARD, JOBS = ParseArgs(sys.argv)
642
643 if not PACKAGE:
644 # No packages. Pass straight through to emerge.
645 # Allows users to just type ./parallel_emerge --depclean
646 sys.exit(os.system(EmergeCommand()))
647
638 print "Starting fast-emerge." 648 print "Starting fast-emerge."
639 PACKAGE, EMERGE_ARGS, BOARD = ParseArgs(sys.argv)
640 print " Building package %s on %s (%s)" % (PACKAGE, EMERGE_ARGS, BOARD) 649 print " Building package %s on %s (%s)" % (PACKAGE, EMERGE_ARGS, BOARD)
641
642 print "Running emerge to generate deps" 650 print "Running emerge to generate deps"
643 deps_output = GetDepsFromPortage(PACKAGE) 651 deps_output = GetDepsFromPortage(PACKAGE)
644 print "Processing emerge output" 652 print "Processing emerge output"
645 dependency_tree, dependency_info = DepsToTree(deps_output) 653 dependency_tree, dependency_info = DepsToTree(deps_output)
646 if VERBOSE: 654 if VERBOSE:
647 print "Print tree" 655 print "Print tree"
648 PrintTree(dependency_tree) 656 PrintTree(dependency_tree)
649 657
650 print "Generate dependency graph." 658 print "Generate dependency graph."
651 dependency_graph = GenDependencyGraph(dependency_tree, dependency_info) 659 dependency_graph = GenDependencyGraph(dependency_tree, dependency_info)
652 660
653 if VERBOSE: 661 if VERBOSE:
654 PrintDepsMap(dependency_graph) 662 PrintDepsMap(dependency_graph)
655 663
656 # Run the queued emerges. 664 # Run the queued emerges.
657 scheduler = EmergeQueue(dependency_graph) 665 scheduler = EmergeQueue(dependency_graph)
658 scheduler.Run() 666 scheduler.Run()
659 667
660 print "Done" 668 print "Done"
661 669
OLDNEW
« 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