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.
5 """Provide a class for collecting info on one builder run.
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".
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
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
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
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
43 class RunAttributesError(Exception):
44 """Base class for exceptions related to RunAttributes behavior."""
47 """Handle stringify because base class will just spit out self.args."""
51 class ParallelAttributeError(AttributeError):
52 """Custom version of AttributeError."""
54 def __init__(self, attr, board=None, target=None, *args):
56 self.msg = ('No such board-specific parallel run attribute %r for %s/%s' %
57 (attr, board, target))
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)
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)
79 class AttrNotPickleableError(RunAttributesError):
80 """For when attribute value to queue is not pickleable."""
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)
88 class AttrTimeoutError(RunAttributesError):
89 """For when timeout is reached while waiting for attribute value."""
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)
97 class LockableQueue(object):
98 """Multiprocessing queue with associated recursive lock.
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:
106 ... process the queue in some way.
109 def __init__(self, manager):
110 self._queue = manager.Queue()
111 self.rlock = manager.RLock()
113 def __getattr__(self, attr):
114 """Relay everything to the underlying Queue object at self._queue."""
115 return getattr(self._queue, attr)
118 class RunAttributes(object):
119 """Hold all run attributes for a particular builder run.
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).
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.
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
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
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.
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
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.
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 = '||'
177 # Sanity check, make sure there is no overlap between the attr groups.
178 assert not REGULAR_ATTRS & PARALLEL_ATTRS
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.
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
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)
197 # Set of known <board>||<target> combinations.
198 self._board_targets = set()
200 def RegisterBoardAttrs(self, board, target):
201 """Register a new valid board/target combination. Safe to repeat.
204 board: Board name to register.
205 target: Build config name to register.
208 A new BoardRunAttributes object for more convenient access to the newly
209 registered attributes specific to this board/target combination.
211 board_target = RunAttributes.BOARD_ATTR_SEP.join((board, target))
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)
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)
225 return BoardRunAttributes(self, board, target)
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)
235 def _GetBoardAttrName(self, attr, board, target):
236 """Translate plain |attr| to uniquified board attribute name.
239 attr: Plain run attribute name.
241 target: Build config name.
244 The uniquified board-specific attribute name.
247 AssertionError if the board/target combination does not exist.
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)
253 # Translate to the unique attribute name for attr/board/target.
254 return RunAttributes.BOARD_ATTR_SEP.join((attr, board, target))
256 def SetBoardParallel(self, attr, value, board, target):
257 """Set board-specific parallel run attribute value.
260 attr: Plain board run attribute name.
263 target: Build config name.
265 unique_attr = self._GetBoardAttrName(attr, board, target)
267 self.SetParallel(unique_attr, value)
268 except ParallelAttributeError:
269 # Clarify the AttributeError.
270 raise ParallelAttributeError(attr, board=board, target=target)
272 def HasBoardParallel(self, attr, board, target):
273 """Return True if board-specific parallel run attribute is known and set.
276 attr: Plain board run attribute name.
278 target: Build config name.
280 unique_attr = self._GetBoardAttrName(attr, board, target)
281 return self.HasParallel(unique_attr)
283 def SetBoardParallelDefault(self, attr, default_value, board, target):
284 """Set board-specific parallel run attribute value, if not already set.
287 attr: Plain board run attribute name.
288 default_value: Value to set.
290 target: Build config name.
292 if not self.HasBoardParallel(attr, board, target):
293 self.SetBoardParallel(attr, default_value, board, target)
295 def GetBoardParallel(self, attr, board, target, timeout=0):
296 """Get board-specific parallel run attribute value.
299 attr: Plain board run attribute name.
301 target: Build config name.
302 timeout: See GetParallel for description.
307 unique_attr = self._GetBoardAttrName(attr, board, target)
309 return self.GetParallel(unique_attr, timeout=timeout)
310 except ParallelAttributeError:
311 # Clarify the AttributeError.
312 raise ParallelAttributeError(attr, board=board, target=target)
314 def _GetQueue(self, attr, strict=False):
315 """Return the queue for the given attribute, if it exists.
318 attr: The run attribute name.
319 strict: If True, then complain if queue for |attr| is not found.
322 The LockableQueue for this attribute, if it has one, or None
323 (assuming strict is False).
326 ParallelAttributeError if no queue for this attribute is registered,
327 meaning no parallel attribute by this name is known.
329 queue = self._queues.get(attr)
331 if queue is None and strict:
332 raise ParallelAttributeError(attr)
336 def SetParallel(self, attr, value):
337 """Set the given parallel run attribute value.
339 Called to set the value of any parallel run attribute. The value is
340 saved onto a multiprocessing queue for that attribute.
343 attr: Name of the attribute.
344 value: Value to give the attribute. This value must be pickleable.
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.
351 # Confirm that value can be pickled, because otherwise it will fail
354 cPickle.dumps(value, cPickle.HIGHEST_PROTOCOL)
355 except cPickle.PicklingError:
356 raise AttrNotPickleableError(attr, value)
358 queue = self._GetQueue(attr, strict=True)
361 # First empty the queue. Any value already on the queue is now stale.
370 def HasParallel(self, attr):
371 """Return True if the given parallel run attribute is known and set.
374 attr: Name of the attribute.
377 queue = self._GetQueue(attr, strict=True)
380 return not queue.empty()
381 except ParallelAttributeError:
384 def SetParallelDefault(self, attr, default_value):
385 """Set the given parallel run attribute only if it is not already set.
387 This leverages HasParallel and SetParallel in a convenient pattern.
390 attr: Name of the attribute.
391 default_value: Value to give the attribute if it is not set. This value
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.
399 if not self.HasParallel(attr):
400 self.SetParallel(attr, default_value)
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.
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.
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.
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.
425 queue = self._GetQueue(attr, strict=True)
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.
430 value = queue.get(True, timeout)
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.
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.
444 value = queue.get(False)
450 # First re-queue the value, then return it.
455 # Handle no value differently depending on whether timeout is 0.
457 raise ParallelAttributeError(attr)
459 raise AttrTimeoutError(attr)
462 class BoardRunAttributes(object):
463 """Convenience class for accessing board-specific run attributes.
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.
470 For example, to access the breakpad_symbols_generated board run attribute on
471 a regular RunAttributes object requires this:
473 value = attrs.GetBoardParallel('breakpad_symbols_generated', board, target)
475 But on a BoardRunAttributes object:
477 boardattrs = BoardRunAttributes(attrs, board, target)
479 value = boardattrs.GetParallel('breakpad_symbols_generated')
481 The same goes for setting values.
484 __slots__ = ('_attrs', '_board', '_target')
486 def __init__(self, attrs, board, target):
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.
496 self._target = target
498 def SetParallel(self, attr, value, *args, **kwargs):
499 """Set the value of parallel board attribute |attr| to |value|.
501 Relay to SetBoardParallel on self._attrs, supplying board and target.
502 See documentation on RunAttributes.SetBoardParallel for more details.
504 self._attrs.SetBoardParallel(attr, value, self._board, self._target,
507 def HasParallel(self, attr, *args, **kwargs):
508 """Return True if parallel board attribute |attr| exists.
510 Relay to HasBoardParallel on self._attrs, supplying board and target.
511 See documentation on RunAttributes.HasBoardParallel for more details.
513 return self._attrs.HasBoardParallel(attr, self._board, self._target,
516 def SetParallelDefault(self, attr, default_value, *args, **kwargs):
517 """Set the value of parallel board attribute |attr| to |value|, if not set.
519 Relay to SetBoardParallelDefault on self._attrs, supplying board and target.
520 See documentation on RunAttributes.SetBoardParallelDefault for more details.
522 self._attrs.SetBoardParallelDefault(attr, default_value, self._board,
523 self._target, *args, **kwargs)
525 def GetParallel(self, attr, *args, **kwargs):
526 """Get the value of parallel board attribute |attr|.
528 Relay to GetBoardParallel on self._attrs, supplying board and target.
529 See documentation on RunAttributes.GetBoardParallel for more details.
531 return self._attrs.GetBoardParallel(attr, self._board, self._target,
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.
540 This class should never be instantiated directly, but instead be
541 instantiated as part of a BuilderRun object.
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.
551 'config', # BuildConfig for this run.
552 'options', # The cbuildbot options object for this run.
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.
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.
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.
568 # TODO(mtennant): Other candidates here include:
569 # trybot, buildbot, remote_trybot, chrome_root,
570 # test = (config build_tests AND option tests)
573 def __init__(self, options, multiprocess_manager):
574 self.options = options
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.
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)
590 # Fill in values for all pre-computed "run configs" now, which are frozen
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
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
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)
615 """Return the bot_id for this run."""
616 return self.config.GetBotId(remote_trybot=self.options.remote_trybot)
620 """Look up the RunAttributes object for this BuilderRun object."""
621 return self._ATTRS[self._attrs_id]
623 def IsToTBuild(self):
624 """Returns True if Builder is running on ToT."""
625 return self.manifest_branch == 'master'
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,
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)
643 def ConstructDashboardURL(self, stage=None):
644 """Return the dashboard URL
646 This is the direct link to buildbot logs as seen in build.chromium.org
649 stage: Link to a specific |stage|, otherwise the general buildbot log
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)
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
663 def ShouldUploadPrebuilts(self):
664 """Return True if this run should upload prebuilts."""
665 return self.options.prebuilts and self.config.prebuilts
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)
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
681 def GetVersionInfo(cls, buildroot):
682 """Helper for picking apart various version bits.
684 This method only exists so that tests can override it.
686 return manifest_version.VersionInfo.from_repo(buildroot)
688 def GetVersion(self):
689 """Calculate full R<chrome_version>-<chromeos_version> version string.
691 It is required that the sync stage be run before this method is called.
694 The version string for this run.
697 AssertionError if the sync stage has not been run first.
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.'
703 verinfo = self.GetVersionInfo(self.buildroot)
704 release_tag = self.attrs.release_tag
706 calc_version = 'R%s-%s' % (verinfo.chrome_branch, release_tag)
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(),
715 def DetermineChromeVersion(self):
716 """Determine the current Chrome version in buildroot now and return it.
718 This uses the typical portage logic to determine which version of Chrome
719 is active right now in the buildroot.
722 The new value of attrs.chrome_version (e.g. "35.0.1863.0").
724 cpv = portage_utilities.BestVisible(constants.CHROME_CP,
725 buildroot=self.buildroot)
726 return cpv.version_no_rev.partition('_')[0]
729 class _RealBuilderRun(object):
730 """Base BuilderRun class that manages self.config access.
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.
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.
742 __slots__ = _BuilderRunBase.__slots__ + (
743 '_run_base', # The _BuilderRunBase object where most functionality is.
744 '_config', # Config to use for dynamically overriding self.config.
747 def __init__(self, run_base, build_config):
748 self._run_base = run_base
749 self._config = build_config
751 # Make sure self.attrs has board-specific attributes for each board
753 for board in build_config.boards:
754 self.attrs.RegisterBoardAttrs(board, build_config.name)
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')
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
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
780 return result(*args, **kwargs)
782 run_base.config = None
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)
798 run_base.config = None
800 def GetChildren(self):
801 """Get ChildBuilderRun objects for child configs, if they exist.
804 List of ChildBuilderRun objects if self.config has child_configs. []
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))]
812 def GetUngroupedBuilderRuns(self):
813 """Same as GetChildren, but defaults to [self] if no children exist.
816 Result of self.GetChildren, if children exist, otherwise [self].
818 return self.GetChildren() or [self]
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:
825 bot_ids.append(config.name)
828 class BuilderRun(_RealBuilderRun):
829 """A standard BuilderRun for a top-level build config."""
831 def __init__(self, options, build_config, multiprocess_manager):
835 options: Command line options from this cbuildbot run.
836 build_config: Build config for this cbuildbot run.
837 multiprocess_manager: A multiprocessing.Manager.
839 run_base = _BuilderRunBase(options, multiprocess_manager)
840 super(BuilderRun, self).__init__(run_base, build_config)
843 class ChildBuilderRun(_RealBuilderRun):
844 """A BuilderRun for a "child" build config."""
846 def __init__(self, builder_run, child_index):
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.
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)