Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / cbuildbot_run.py
1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Provide a class for collecting info on one builder run.
6
7 There are two public classes, BuilderRun and ChildBuilderRun, that serve
8 this function.  The first is for most situations, the second is for "child"
9 configs within a builder config that has entries in "child_configs".
10
11 Almost all functionality is within the common _BuilderRunBase class.  The
12 only thing the BuilderRun and ChildBuilderRun classes are responsible for
13 is overriding the self.config value in the _BuilderRunBase object whenever
14 it is accessed.
15
16 It is important to note that for one overall run, there will be one
17 BuilderRun object and zero or more ChildBuilderRun objects, but they
18 will all share the same _BuilderRunBase *object*.  This means, for example,
19 that run attributes (e.g. self.attrs.release_tag) are shared between them
20 all, as intended.
21 """
22
23 import cPickle
24 import functools
25 import os
26 try:
27   import Queue
28 except ImportError:
29   # Python-3 renamed to "queue".  We still use Queue to avoid collisions
30   # with naming variables as "queue".  Maybe we'll transition at some point.
31   # pylint: disable=F0401
32   import queue as Queue
33 import types
34
35 from chromite.cbuildbot import archive_lib
36 from chromite.cbuildbot import metadata_lib
37 from chromite.cbuildbot import constants
38 from chromite.cbuildbot import manifest_version
39 from chromite.cbuildbot import portage_utilities
40 from chromite.cbuildbot import validation_pool
41
42
43 class RunAttributesError(Exception):
44   """Base class for exceptions related to RunAttributes behavior."""
45
46   def __str__(self):
47     """Handle stringify because base class will just spit out self.args."""
48     return self.msg
49
50
51 class ParallelAttributeError(AttributeError):
52   """Custom version of AttributeError."""
53
54   def __init__(self, attr, board=None, target=None, *args):
55     if board or target:
56       self.msg = ('No such board-specific parallel run attribute %r for %s/%s' %
57                   (attr, board, target))
58     else:
59       self.msg = 'No such parallel run attribute %r' % attr
60     super(ParallelAttributeError, self).__init__(self.msg, *args)
61     self.args = (attr, board, target) + tuple(args)
62
63   def __str__(self):
64     return self.msg
65
66
67 class AttrSepCountError(ValueError):
68   """Custom version of ValueError for when BOARD_ATTR_SEP is misused."""
69   def __init__(self, attr, *args):
70     self.msg = ('Attribute name has an unexpected number of "%s" occurrences'
71                 ' in it: %s' % (RunAttributes.BOARD_ATTR_SEP, attr))
72     super(AttrSepCountError, self).__init__(self.msg, *args)
73     self.args = (attr, ) + tuple(args)
74
75   def __str__(self):
76     return self.msg
77
78
79 class AttrNotPickleableError(RunAttributesError):
80   """For when attribute value to queue is not pickleable."""
81
82   def __init__(self, attr, value, *args):
83     self.msg = 'Run attribute "%s" value cannot be pickled: %r' % (attr, value)
84     super(AttrNotPickleableError, self).__init__(self.msg, *args)
85     self.args = (attr, value) + tuple(args)
86
87
88 class AttrTimeoutError(RunAttributesError):
89   """For when timeout is reached while waiting for attribute value."""
90
91   def __init__(self, attr, *args):
92     self.msg = 'Timed out waiting for value for run attribute "%s".' % attr
93     super(AttrTimeoutError, self).__init__(self.msg, *args)
94     self.args = (attr, ) + tuple(args)
95
96
97 class LockableQueue(object):
98   """Multiprocessing queue with associated recursive lock.
99
100   Objects of this class function just like a regular multiprocessing Queue,
101   except that there is also an rlock attribute for getting a multiprocessing
102   RLock associated with this queue.  Actual locking must still be handled by
103   the calling code.  Example usage:
104
105   with queue.rlock:
106     ... process the queue in some way.
107   """
108
109   def __init__(self, manager):
110     self._queue = manager.Queue()
111     self.rlock = manager.RLock()
112
113   def __getattr__(self, attr):
114     """Relay everything to the underlying Queue object at self._queue."""
115     return getattr(self._queue, attr)
116
117
118 class RunAttributes(object):
119   """Hold all run attributes for a particular builder run.
120
121   There are two supported flavors of run attributes: REGULAR attributes are
122   available only to stages that are run sequentially as part of the main (top)
123   process and PARALLEL attributes are available to all stages, no matter what
124   process they are in.  REGULAR attributes are accessed directly as normal
125   attributes on a RunAttributes object, while PARALLEL attributes are accessed
126   through the {Set|Has|Get}Parallel methods.  PARALLEL attributes also have the
127   restriction that their values must be pickle-able (in order to be sent
128   through multiprocessing queue).
129
130   The currently supported attributes of each kind are listed in REGULAR_ATTRS
131   and PARALLEL_ATTRS below.  To add support for a new run attribute simply
132   add it to one of those sets.
133
134   A subset of PARALLEL_ATTRS is BOARD_ATTRS.  These attributes only have meaning
135   in the context of a specific board and config target.  The attributes become
136   available once a board/config is registered for a run, and then they can be
137   accessed through the {Set|Has|Get}BoardParallel methods or through the
138   {Get|Set|Has}Parallel methods of a BoardRunAttributes object.  The latter is
139   encouraged.
140
141   To add a new BOARD attribute simply add it to the BOARD_ATTRS set below, which
142   will also add it to PARALLEL_ATTRS (all BOARD attributes are assumed to need
143   PARALLEL support).
144   """
145
146   REGULAR_ATTRS = frozenset((
147       'chrome_version',   # Set by SyncChromeStage, if it runs.
148       'manifest_manager', # Set by ManifestVersionedSyncStage.
149       'release_tag',      # Set by cbuildbot after sync stage.
150       'metadata',         # Used by various build stages to record metadata.
151   ))
152
153   # TODO(mtennant): It might be useful to have additional info for each board
154   # attribute:  1) a log-friendly pretty name, 2) a rough upper bound timeout
155   # value for consumers of the attribute to use when waiting for it.
156   BOARD_ATTRS = frozenset((
157       'breakpad_symbols_generated', # Set by DebugSymbolsStage.
158       'debug_tarball_generated',    # Set by DebugSymbolsStage.
159       'images_generated',           # Set by BuildImageStage.
160       'instruction_urls_per_channel', # Set by ArchiveStage
161       'success',                    # Set by cbuildbot.py:Builder
162   ))
163
164   # Attributes that need to be set by stages that can run in parallel
165   # (i.e. in a subprocess) must be included here.  All BOARD_ATTRS are
166   # assumed to fit into this category.
167   PARALLEL_ATTRS = BOARD_ATTRS | frozenset((
168       'unittest_value',   # For unittests.  An example of a PARALLEL attribute
169                           # that is not also a BOARD attribute.
170   ))
171
172   # This separator is used to create a unique attribute name for any
173   # board-specific attribute.  For example:
174   # breakpad_symbols_generated||stumpy||stumpy-full-config
175   BOARD_ATTR_SEP = '||'
176
177   # Sanity check, make sure there is no overlap between the attr groups.
178   assert not REGULAR_ATTRS & PARALLEL_ATTRS
179
180   # REGULAR_ATTRS show up as attributes directly on the RunAttributes object.
181   __slots__ = tuple(REGULAR_ATTRS) + (
182       '_board_targets', # Set of registered board/target combinations.
183       '_manager',       # The multiprocessing.Manager to use.
184       '_queues',        # Dict of parallel attribute names to LockableQueues.
185   )
186
187   def __init__(self, multiprocess_manager):
188     # Create queues for all non-board-specific parallel attributes now.
189     # Parallel board attributes must wait for the board to be registered.
190     self._manager = multiprocess_manager
191     self._queues = {}
192     for attr in RunAttributes.PARALLEL_ATTRS:
193       if attr not in RunAttributes.BOARD_ATTRS:
194         # pylint: disable=E1101
195         self._queues[attr] = LockableQueue(self._manager)
196
197     # Set of known <board>||<target> combinations.
198     self._board_targets = set()
199
200   def RegisterBoardAttrs(self, board, target):
201     """Register a new valid board/target combination.  Safe to repeat.
202
203     Args:
204       board: Board name to register.
205       target: Build config name to register.
206
207     Returns:
208       A new BoardRunAttributes object for more convenient access to the newly
209         registered attributes specific to this board/target combination.
210     """
211     board_target = RunAttributes.BOARD_ATTR_SEP.join((board, target))
212
213     if not board_target in self._board_targets:
214       # Register board/target as a known board/target.
215       self._board_targets.add(board_target)
216
217       # For each board attribute that should be queue-able, create its queue
218       # now.  Queues are kept by the uniquified run attribute name.
219       for attr in RunAttributes.BOARD_ATTRS:
220         # Every attr in BOARD_ATTRS is in PARALLEL_ATTRS, by construction.
221         # pylint: disable=E1101
222         uniquified_attr = self._GetBoardAttrName(attr, board, target)
223         self._queues[uniquified_attr] = LockableQueue(self._manager)
224
225     return BoardRunAttributes(self, board, target)
226
227   # TODO(mtennant): Complain if a child process attempts to set a non-parallel
228   # run attribute?  It could be done something like this:
229   #def __setattr__(self, attr, value):
230   #  """Override __setattr__ to prevent misuse of run attributes."""
231   #  if attr in self.REGULAR_ATTRS:
232   #    assert not self._IsChildProcess()
233   #  super(RunAttributes, self).__setattr__(attr, value)
234
235   def _GetBoardAttrName(self, attr, board, target):
236     """Translate plain |attr| to uniquified board attribute name.
237
238     Args:
239       attr: Plain run attribute name.
240       board: Board name.
241       target: Build config name.
242
243     Returns:
244       The uniquified board-specific attribute name.
245
246     Raises:
247       AssertionError if the board/target combination does not exist.
248     """
249     board_target = RunAttributes.BOARD_ATTR_SEP.join((board, target))
250     assert board_target in self._board_targets, \
251         'Unknown board/target combination: %s/%s' % (board, target)
252
253     # Translate to the unique attribute name for attr/board/target.
254     return RunAttributes.BOARD_ATTR_SEP.join((attr, board, target))
255
256   def SetBoardParallel(self, attr, value, board, target):
257     """Set board-specific parallel run attribute value.
258
259     Args:
260       attr: Plain board run attribute name.
261       value: Value to set.
262       board: Board name.
263       target: Build config name.
264     """
265     unique_attr = self._GetBoardAttrName(attr, board, target)
266     try:
267       self.SetParallel(unique_attr, value)
268     except ParallelAttributeError:
269       # Clarify the AttributeError.
270       raise ParallelAttributeError(attr, board=board, target=target)
271
272   def HasBoardParallel(self, attr, board, target):
273     """Return True if board-specific parallel run attribute is known and set.
274
275     Args:
276       attr: Plain board run attribute name.
277       board: Board name.
278       target: Build config name.
279     """
280     unique_attr = self._GetBoardAttrName(attr, board, target)
281     return self.HasParallel(unique_attr)
282
283   def SetBoardParallelDefault(self, attr, default_value, board, target):
284     """Set board-specific parallel run attribute value, if not already set.
285
286     Args:
287       attr: Plain board run attribute name.
288       default_value: Value to set.
289       board: Board name.
290       target: Build config name.
291     """
292     if not self.HasBoardParallel(attr, board, target):
293       self.SetBoardParallel(attr, default_value, board, target)
294
295   def GetBoardParallel(self, attr, board, target, timeout=0):
296     """Get board-specific parallel run attribute value.
297
298     Args:
299       attr: Plain board run attribute name.
300       board: Board name.
301       target: Build config name.
302       timeout: See GetParallel for description.
303
304     Returns:
305       The value found.
306     """
307     unique_attr = self._GetBoardAttrName(attr, board, target)
308     try:
309       return self.GetParallel(unique_attr, timeout=timeout)
310     except ParallelAttributeError:
311       # Clarify the AttributeError.
312       raise ParallelAttributeError(attr, board=board, target=target)
313
314   def _GetQueue(self, attr, strict=False):
315     """Return the queue for the given attribute, if it exists.
316
317     Args:
318       attr: The run attribute name.
319       strict: If True, then complain if queue for |attr| is not found.
320
321     Returns:
322       The LockableQueue for this attribute, if it has one, or None
323         (assuming strict is False).
324
325     Raises:
326       ParallelAttributeError if no queue for this attribute is registered,
327         meaning no parallel attribute by this name is known.
328     """
329     queue = self._queues.get(attr)
330
331     if queue is None and strict:
332       raise ParallelAttributeError(attr)
333
334     return queue
335
336   def SetParallel(self, attr, value):
337     """Set the given parallel run attribute value.
338
339     Called to set the value of any parallel run attribute.  The value is
340     saved onto a multiprocessing queue for that attribute.
341
342     Args:
343       attr: Name of the attribute.
344       value: Value to give the attribute.  This value must be pickleable.
345
346     Raises:
347       ParallelAttributeError if attribute is not a valid parallel attribute.
348       AttrNotPickleableError if value cannot be pickled, meaning it cannot
349         go through the queue system.
350     """
351     # Confirm that value can be pickled, because otherwise it will fail
352     # in the queue.
353     try:
354       cPickle.dumps(value, cPickle.HIGHEST_PROTOCOL)
355     except cPickle.PicklingError:
356       raise AttrNotPickleableError(attr, value)
357
358     queue = self._GetQueue(attr, strict=True)
359
360     with queue.rlock:
361       # First empty the queue.  Any value already on the queue is now stale.
362       while True:
363         try:
364           queue.get(False)
365         except Queue.Empty:
366           break
367
368       queue.put(value)
369
370   def HasParallel(self, attr):
371     """Return True if the given parallel run attribute is known and set.
372
373     Args:
374       attr: Name of the attribute.
375     """
376     try:
377       queue = self._GetQueue(attr, strict=True)
378
379       with queue.rlock:
380         return not queue.empty()
381     except ParallelAttributeError:
382       return False
383
384   def SetParallelDefault(self, attr, default_value):
385     """Set the given parallel run attribute only if it is not already set.
386
387     This leverages HasParallel and SetParallel in a convenient pattern.
388
389     Args:
390       attr: Name of the attribute.
391       default_value: Value to give the attribute if it is not set.  This value
392         must be pickleable.
393
394     Raises:
395       ParallelAttributeError if attribute is not a valid parallel attribute.
396       AttrNotPickleableError if value cannot be pickled, meaning it cannot
397         go through the queue system.
398     """
399     if not self.HasParallel(attr):
400       self.SetParallel(attr, default_value)
401
402   # TODO(mtennant): Add an option to log access, including the time to wait
403   # or waited.  It could be enabled with an optional announce=False argument.
404   # See GetParallel helper on BoardSpecificBuilderStage class for ideas.
405   def GetParallel(self, attr, timeout=0):
406     """Get value for the given parallel run attribute, optionally waiting.
407
408     If the given parallel run attr already has a value in the queue it will
409     return that value right away.  Otherwise, it will wait for a value to
410     appear in the queue up to the timeout specified (timeout of None means
411     wait forever) before returning the value found or raising AttrTimeoutError
412     if a timeout was reached.
413
414     Args:
415       attr: The name of the run attribute.
416       timeout: Timeout, in seconds.  A None value means wait forever,
417         which is probably never a good idea.  A value of 0 does not wait at all.
418
419     Raises:
420       ParallelAttributeError if attribute is not set and timeout was 0.
421       AttrTimeoutError if timeout is greater than 0 and timeout is reached
422         before a value is available on the queue.
423     """
424     got_value = False
425     queue = self._GetQueue(attr, strict=True)
426
427     # First attempt to get a value off the queue, without the lock.  This
428     # allows a blocking get to wait for a value to appear.
429     try:
430       value = queue.get(True, timeout)
431       got_value = True
432     except Queue.Empty:
433       # This means there is nothing on the queue.  Let this fall through to
434       # the locked code block to see if another process is in the process
435       # of re-queuing a value.  Any process doing that will have a lock.
436       pass
437
438     # Now grab the queue lock and flush any other values that are on the queue.
439     # This should only happen if another process put a value in after our first
440     # queue.get above.  If so, accept the updated value.
441     with queue.rlock:
442       while True:
443         try:
444           value = queue.get(False)
445           got_value = True
446         except Queue.Empty:
447           break
448
449       if got_value:
450         # First re-queue the value, then return it.
451         queue.put(value)
452         return value
453
454       else:
455         # Handle no value differently depending on whether timeout is 0.
456         if timeout == 0:
457           raise ParallelAttributeError(attr)
458         else:
459           raise AttrTimeoutError(attr)
460
461
462 class BoardRunAttributes(object):
463   """Convenience class for accessing board-specific run attributes.
464
465   Board-specific run attributes (actually board/target-specific) are saved in
466   the RunAttributes object but under uniquified names.  A BoardRunAttributes
467   object provides access to these attributes using their plain names by
468   providing the board/target information where needed.
469
470   For example, to access the breakpad_symbols_generated board run attribute on
471   a regular RunAttributes object requires this:
472
473     value = attrs.GetBoardParallel('breakpad_symbols_generated', board, target)
474
475   But on a BoardRunAttributes object:
476
477     boardattrs = BoardRunAttributes(attrs, board, target)
478     ...
479     value = boardattrs.GetParallel('breakpad_symbols_generated')
480
481   The same goes for setting values.
482   """
483
484   __slots__ = ('_attrs', '_board', '_target')
485
486   def __init__(self, attrs, board, target):
487     """Initialize.
488
489     Args:
490       attrs: The main RunAttributes object.
491       board: The board name this is specific to.
492       target: The build config name this is specific to.
493     """
494     self._attrs = attrs
495     self._board = board
496     self._target = target
497
498   def SetParallel(self, attr, value, *args, **kwargs):
499     """Set the value of parallel board attribute |attr| to |value|.
500
501     Relay to SetBoardParallel on self._attrs, supplying board and target.
502     See documentation on RunAttributes.SetBoardParallel for more details.
503     """
504     self._attrs.SetBoardParallel(attr, value, self._board, self._target,
505                                  *args, **kwargs)
506
507   def HasParallel(self, attr, *args, **kwargs):
508     """Return True if parallel board attribute |attr| exists.
509
510     Relay to HasBoardParallel on self._attrs, supplying board and target.
511     See documentation on RunAttributes.HasBoardParallel for more details.
512     """
513     return self._attrs.HasBoardParallel(attr, self._board, self._target,
514                                         *args, **kwargs)
515
516   def SetParallelDefault(self, attr, default_value, *args, **kwargs):
517     """Set the value of parallel board attribute |attr| to |value|, if not set.
518
519     Relay to SetBoardParallelDefault on self._attrs, supplying board and target.
520     See documentation on RunAttributes.SetBoardParallelDefault for more details.
521     """
522     self._attrs.SetBoardParallelDefault(attr, default_value, self._board,
523                                         self._target, *args, **kwargs)
524
525   def GetParallel(self, attr, *args, **kwargs):
526     """Get the value of parallel board attribute |attr|.
527
528     Relay to GetBoardParallel on self._attrs, supplying board and target.
529     See documentation on RunAttributes.GetBoardParallel for more details.
530     """
531     return self._attrs.GetBoardParallel(attr, self._board, self._target,
532                                         *args, **kwargs)
533
534
535 # TODO(mtennant): Consider renaming this _BuilderRunState, then renaming
536 # _RealBuilderRun to _BuilderRunBase.
537 class _BuilderRunBase(object):
538   """Class to represent one run of a builder.
539
540   This class should never be instantiated directly, but instead be
541   instantiated as part of a BuilderRun object.
542   """
543
544   # Class-level dict of RunAttributes objects to make it less
545   # problematic to send BuilderRun objects between processes through
546   # pickle.  The 'attrs' attribute on a BuilderRun object will look
547   # up the RunAttributes for that particular BuilderRun here.
548   _ATTRS = {}
549
550   __slots__ = (
551       'config',          # BuildConfig for this run.
552       'options',         # The cbuildbot options object for this run.
553
554       # Run attributes set/accessed by stages during the run.  To add support
555       # for a new run attribute add it to the RunAttributes class above.
556       '_attrs_id',       # Object ID for looking up self.attrs.
557
558       # Some pre-computed run configuration values.
559       'buildnumber',     # The build number for this run.
560       'buildroot',       # The build root path for this run.
561       'debug',           # Boolean, represents "dry run" concept, really.
562       'manifest_branch', # The manifest branch to build and test for this run.
563
564       # Some attributes are available as properties.  In particular, attributes
565       # that use self.config must be determined after __init__.
566       # self.bot_id      # Effective name of builder for this run.
567
568       # TODO(mtennant): Other candidates here include:
569       # trybot, buildbot, remote_trybot, chrome_root,
570       # test = (config build_tests AND option tests)
571   )
572
573   def __init__(self, options, multiprocess_manager):
574     self.options = options
575
576     # Note that self.config is filled in dynamically by either of the classes
577     # that are actually instantiated: BuilderRun and ChildBuilderRun.  In other
578     # words, self.config can be counted on anywhere except in this __init__.
579     # The implication is that any plain attributes that are calculated from
580     # self.config contents must be provided as properties (or methods).
581     # See the _RealBuilderRun class and its __getattr__ method for details.
582     self.config = None
583
584     # Create the RunAttributes object for this BuilderRun and save
585     # the id number for it in order to look it up via attrs property.
586     attrs = RunAttributes(multiprocess_manager)
587     self._ATTRS[id(attrs)] = attrs
588     self._attrs_id = id(attrs)
589
590     # Fill in values for all pre-computed "run configs" now, which are frozen
591     # by this time.
592
593     # TODO(mtennant): Should this use os.path.abspath like builderstage does?
594     self.buildroot = self.options.buildroot
595     self.buildnumber = self.options.buildnumber
596     self.manifest_branch = self.options.branch
597
598     # For remote_trybot runs, options.debug is implied, but we want true dryrun
599     # mode only if --debug was actually specified (i.e. options.debug_forced).
600     # TODO(mtennant): Get rid of confusing debug and debug_forced, if at all
601     # possible.  Also, eventually use "dry_run" and "verbose" options instead to
602     # represent two distinct concepts.
603     self.debug = self.options.debug
604     if self.options.remote_trybot:
605       self.debug = self.options.debug_forced
606
607     # Certain run attributes have sensible defaults which can be set here.
608     # This allows all code to safely assume that the run attribute exists.
609     attrs.chrome_version = None
610     attrs.metadata = metadata_lib.CBuildbotMetadata(
611         multiprocess_manager=multiprocess_manager)
612
613   @property
614   def bot_id(self):
615     """Return the bot_id for this run."""
616     return self.config.GetBotId(remote_trybot=self.options.remote_trybot)
617
618   @property
619   def attrs(self):
620     """Look up the RunAttributes object for this BuilderRun object."""
621     return self._ATTRS[self._attrs_id]
622
623   def IsToTBuild(self):
624     """Returns True if Builder is running on ToT."""
625     return self.manifest_branch == 'master'
626
627   def GetArchive(self):
628     """Create an Archive object for this BuilderRun object."""
629     # The Archive class is very lightweight, and is read-only, so it
630     # is ok to generate a new one on demand.  This also avoids worrying
631     # about whether it can go through pickle.
632     # Almost everything the Archive class does requires GetVersion(),
633     # which means it cannot be used until the version has been settled on.
634     # However, because it does have some use before then we provide
635     # the GetVersion function itself to be called when needed later.
636     return archive_lib.Archive(self.bot_id, self.GetVersion, self.options,
637                                self.config)
638
639   def GetBoardRunAttrs(self, board):
640     """Create a BoardRunAttributes object for this run and given |board|."""
641     return BoardRunAttributes(self.attrs, board, self.config.name)
642
643   def ConstructDashboardURL(self, stage=None):
644     """Return the dashboard URL
645
646     This is the direct link to buildbot logs as seen in build.chromium.org
647
648     Args:
649       stage: Link to a specific |stage|, otherwise the general buildbot log
650
651     Returns:
652       The fully formed URL
653     """
654     return validation_pool.ValidationPool.ConstructDashboardURL(
655         self.config.overlays, self.options.remote_trybot,
656         os.environ.get('BUILDBOT_BUILDERNAME', self.config.name),
657         self.options.buildnumber, stage=stage)
658
659   def ShouldBuildAutotest(self):
660     """Return True if this run should build autotest and artifacts."""
661     return self.config.build_tests and self.options.tests
662
663   def ShouldUploadPrebuilts(self):
664     """Return True if this run should upload prebuilts."""
665     return self.options.prebuilts and self.config.prebuilts
666
667   def ShouldReexecAfterSync(self):
668     """Return True if this run should re-exec itself after sync stage."""
669     if self.options.postsync_reexec and self.config.postsync_reexec:
670       # Return True if this source is not in designated buildroot.
671       abs_buildroot = os.path.abspath(self.buildroot)
672       return not os.path.abspath(__file__).startswith(abs_buildroot)
673
674     return False
675
676   def ShouldPatchAfterSync(self):
677     """Return True if this run should patch changes after sync stage."""
678     return self.options.postsync_patch and self.config.postsync_patch
679
680   @classmethod
681   def GetVersionInfo(cls, buildroot):
682     """Helper for picking apart various version bits.
683
684     This method only exists so that tests can override it.
685     """
686     return manifest_version.VersionInfo.from_repo(buildroot)
687
688   def GetVersion(self):
689     """Calculate full R<chrome_version>-<chromeos_version> version string.
690
691     It is required that the sync stage be run before this method is called.
692
693     Returns:
694       The version string for this run.
695
696     Raises:
697       AssertionError if the sync stage has not been run first.
698     """
699     # This method should never be called before the sync stage has run, or
700     # it would return a confusing value.
701     assert hasattr(self.attrs, 'release_tag'), 'Sync stage must run first.'
702
703     verinfo = self.GetVersionInfo(self.buildroot)
704     release_tag = self.attrs.release_tag
705     if release_tag:
706       calc_version = 'R%s-%s' % (verinfo.chrome_branch, release_tag)
707     else:
708       # Non-versioned builds need the build number to uniquify the image.
709       calc_version = 'R%s-%s-b%s' % (verinfo.chrome_branch,
710                                      verinfo.VersionString(),
711                                      self.buildnumber)
712
713     return calc_version
714
715   def DetermineChromeVersion(self):
716     """Determine the current Chrome version in buildroot now and return it.
717
718     This uses the typical portage logic to determine which version of Chrome
719     is active right now in the buildroot.
720
721     Returns:
722       The new value of attrs.chrome_version (e.g. "35.0.1863.0").
723     """
724     cpv = portage_utilities.BestVisible(constants.CHROME_CP,
725                                         buildroot=self.buildroot)
726     return cpv.version_no_rev.partition('_')[0]
727
728
729 class _RealBuilderRun(object):
730   """Base BuilderRun class that manages self.config access.
731
732   For any builder run, sometimes the build config is the top-level config and
733   sometimes it is a "child" config.  In either case, the config to use should
734   override self.config for all cases.  This class provides a mechanism for
735   overriding self.config access generally.
736
737   Also, methods that do more than access state for a BuilderRun should
738   live here.  In particular, any method that uses 'self' as an object
739   directly should be here rather than _BuilderRunBase.
740   """
741
742   __slots__ = _BuilderRunBase.__slots__ + (
743       '_run_base',  # The _BuilderRunBase object where most functionality is.
744       '_config',    # Config to use for dynamically overriding self.config.
745   )
746
747   def __init__(self, run_base, build_config):
748     self._run_base = run_base
749     self._config = build_config
750
751     # Make sure self.attrs has board-specific attributes for each board
752     # in build_config.
753     for board in build_config.boards:
754       self.attrs.RegisterBoardAttrs(board, build_config.name)
755
756   def __getattr__(self, attr):
757     # Remember, __getattr__ only called if attribute was not found normally.
758     # In normal usage, the __init__ guarantees that self._run_base and
759     # self._config will be present.  However, the unpickle process bypasses
760     # __init__, and this object must be pickle-able.  That is why we access
761     # self._run_base and self._config through __getattribute__ here, otherwise
762     # unpickling results in infinite recursion.
763     # TODO(mtennant): Revisit this if pickling support is changed to go through
764     # the __init__ method, such as by supplying __reduce__ method.
765     run_base = self.__getattribute__('_run_base')
766     config = self.__getattribute__('_config')
767
768     try:
769       # run_base.config should always be None except when accessed through
770       # this routine.  Override the value here, then undo later.
771       run_base.config = config
772
773       result = getattr(run_base, attr)
774       if isinstance(result, types.MethodType):
775         # Make sure run_base.config is also managed when the method is called.
776         @functools.wraps(result)
777         def FuncWrapper(*args, **kwargs):
778           run_base.config = config
779           try:
780             return result(*args, **kwargs)
781           finally:
782             run_base.config = None
783
784         # TODO(mtennant): Find a way to make the following actually work.  It
785         # makes pickling more complicated, unfortunately.
786         # Cache this function wrapper to re-use next time without going through
787         # __getattr__ again.  This ensures that the same wrapper object is used
788         # each time, which is nice for identity and equality checks.  Subtle
789         # gotcha that we accept: if the function itself on run_base is replaced
790         # then this will continue to provide the behavior of the previous one.
791         #setattr(self, attr, FuncWrapper)
792
793         return FuncWrapper
794       else:
795         return result
796
797     finally:
798       run_base.config = None
799
800   def GetChildren(self):
801     """Get ChildBuilderRun objects for child configs, if they exist.
802
803     Returns:
804       List of ChildBuilderRun objects if self.config has child_configs.  []
805         otherwise.
806     """
807     # If there are child configs, construct a list of ChildBuilderRun objects
808     # for those child configs and return that.
809     return [ChildBuilderRun(self, ix)
810             for ix in range(len(self.config.child_configs))]
811
812   def GetUngroupedBuilderRuns(self):
813     """Same as GetChildren, but defaults to [self] if no children exist.
814
815     Returns:
816       Result of self.GetChildren, if children exist, otherwise [self].
817     """
818     return self.GetChildren() or [self]
819
820   def GetBuilderIds(self):
821     """Return a list of builder names for this config and the child configs."""
822     bot_ids = [self.config.name]
823     for config in self.config.child_configs:
824       if config.name:
825         bot_ids.append(config.name)
826     return bot_ids
827
828 class BuilderRun(_RealBuilderRun):
829   """A standard BuilderRun for a top-level build config."""
830
831   def __init__(self, options, build_config, multiprocess_manager):
832     """Initialize.
833
834     Args:
835       options: Command line options from this cbuildbot run.
836       build_config: Build config for this cbuildbot run.
837       multiprocess_manager: A multiprocessing.Manager.
838     """
839     run_base = _BuilderRunBase(options, multiprocess_manager)
840     super(BuilderRun, self).__init__(run_base, build_config)
841
842
843 class ChildBuilderRun(_RealBuilderRun):
844   """A BuilderRun for a "child" build config."""
845
846   def __init__(self, builder_run, child_index):
847     """Initialize.
848
849     Args:
850       builder_run: BuilderRun for the parent (main) cbuildbot run.  Extract
851         the _BuilderRunBase from it to make sure the same base is used for
852         both the main cbuildbot run and any child runs.
853       child_index: The child index of this child run, used to index into
854         the main run's config.child_configs.
855     """
856     # pylint: disable=W0212
857     run_base = builder_run._run_base
858     config = builder_run.config.child_configs[child_index]
859     super(ChildBuilderRun, self).__init__(run_base, config)