blob: 29b3af017ab62528b2ee0326f5c3d4517c2968ff [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provide a class for collecting info on one builder run.
There are two public classes, BuilderRun and ChildBuilderRun, that serve
this function. The first is for most situations, the second is for "child"
configs within a builder config that has entries in "child_configs".
Almost all functionality is within the common _BuilderRunBase class. The
only thing the BuilderRun and ChildBuilderRun classes are responsible for
is overriding the self.config value in the _BuilderRunBase object whenever
it is accessed.
It is important to note that for one overall run, there will be one
BuilderRun object and zero or more ChildBuilderRun objects, but they
will all share the same _BuilderRunBase *object*. This means, for example,
that run attributes (e.g. self.attrs.release_tag) are shared between them
all, as intended.
from __future__ import print_function
import cPickle
import functools
import os
import Queue
except ImportError:
# Python-3 renamed to "queue". We still use Queue to avoid collisions
# with naming variables as "queue". Maybe we'll transition at some point.
# pylint: disable=F0401
import queue as Queue
import types
from chromite.cbuildbot import archive_lib
from chromite.cbuildbot import metadata_lib
from chromite.cbuildbot import constants
from chromite.cbuildbot import manifest_version
from chromite.cbuildbot import tree_status
from chromite.lib import cidb
from chromite.lib import portage_util
class RunAttributesError(Exception):
"""Base class for exceptions related to RunAttributes behavior."""
def __str__(self):
"""Handle stringify because base class will just spit out self.args."""
return self.msg
class ParallelAttributeError(AttributeError):
"""Custom version of AttributeError."""
def __init__(self, attr, board=None, target=None, *args):
if board or target:
self.msg = ('No such board-specific parallel run attribute %r for %s/%s' %
(attr, board, target))
self.msg = 'No such parallel run attribute %r' % attr
super(ParallelAttributeError, self).__init__(self.msg, *args)
self.args = (attr, board, target) + tuple(args)
def __str__(self):
return self.msg
class AttrSepCountError(ValueError):
"""Custom version of ValueError for when BOARD_ATTR_SEP is misused."""
def __init__(self, attr, *args):
self.msg = ('Attribute name has an unexpected number of "%s" occurrences'
' in it: %s' % (RunAttributes.BOARD_ATTR_SEP, attr))
super(AttrSepCountError, self).__init__(self.msg, *args)
self.args = (attr, ) + tuple(args)
def __str__(self):
return self.msg
class AttrNotPickleableError(RunAttributesError):
"""For when attribute value to queue is not pickleable."""
def __init__(self, attr, value, *args):
self.msg = 'Run attribute "%s" value cannot be pickled: %r' % (attr, value)
super(AttrNotPickleableError, self).__init__(self.msg, *args)
self.args = (attr, value) + tuple(args)
class AttrTimeoutError(RunAttributesError):
"""For when timeout is reached while waiting for attribute value."""
def __init__(self, attr, *args):
self.msg = 'Timed out waiting for value for run attribute "%s".' % attr
super(AttrTimeoutError, self).__init__(self.msg, *args)
self.args = (attr, ) + tuple(args)
class LockableQueue(object):
"""Multiprocessing queue with associated recursive lock.
Objects of this class function just like a regular multiprocessing Queue,
except that there is also an rlock attribute for getting a multiprocessing
RLock associated with this queue. Actual locking must still be handled by
the calling code. Example usage:
with queue.rlock:
... process the queue in some way.
def __init__(self, manager):
self._queue = manager.Queue()
self.rlock = manager.RLock()
def __getattr__(self, attr):
"""Relay everything to the underlying Queue object at self._queue."""
return getattr(self._queue, attr)
class RunAttributes(object):
"""Hold all run attributes for a particular builder run.
There are two supported flavors of run attributes: REGULAR attributes are
available only to stages that are run sequentially as part of the main (top)
process and PARALLEL attributes are available to all stages, no matter what
process they are in. REGULAR attributes are accessed directly as normal
attributes on a RunAttributes object, while PARALLEL attributes are accessed
through the {Set|Has|Get}Parallel methods. PARALLEL attributes also have the
restriction that their values must be pickle-able (in order to be sent
through multiprocessing queue).
The currently supported attributes of each kind are listed in REGULAR_ATTRS
and PARALLEL_ATTRS below. To add support for a new run attribute simply
add it to one of those sets.
A subset of PARALLEL_ATTRS is BOARD_ATTRS. These attributes only have meaning
in the context of a specific board and config target. The attributes become
available once a board/config is registered for a run, and then they can be
accessed through the {Set|Has|Get}BoardParallel methods or through the
{Get|Set|Has}Parallel methods of a BoardRunAttributes object. The latter is
To add a new BOARD attribute simply add it to the BOARD_ATTRS set below, which
will also add it to PARALLEL_ATTRS (all BOARD attributes are assumed to need
PARALLEL support).
REGULAR_ATTRS = frozenset((
'chrome_version', # Set by SyncChromeStage, if it runs.
'manifest_manager', # Set by ManifestVersionedSyncStage.
'release_tag', # Set by cbuildbot after sync stage.
'metadata', # Used by various build stages to record metadata.
# TODO(mtennant): It might be useful to have additional info for each board
# attribute: 1) a log-friendly pretty name, 2) a rough upper bound timeout
# value for consumers of the attribute to use when waiting for it.
BOARD_ATTRS = frozenset((
'breakpad_symbols_generated', # Set by DebugSymbolsStage.
'debug_tarball_generated', # Set by DebugSymbolsStage.
'images_generated', # Set by BuildImageStage.
'payloads_generated', # Set by UploadHWTestArtifacts.
'delta_payloads_generated', # Set by UploadHWTestArtifacts.
'instruction_urls_per_channel', # Set by ArchiveStage
'success', # Set by
'packages_under_test', # Set by BuildPackagesStage.
# Attributes that need to be set by stages that can run in parallel
# (i.e. in a subprocess) must be included here. All BOARD_ATTRS are
# assumed to fit into this category.
'unittest_value', # For unittests. An example of a PARALLEL attribute
# that is not also a BOARD attribute.
# This separator is used to create a unique attribute name for any
# board-specific attribute. For example:
# breakpad_symbols_generated||stumpy||stumpy-full-config
# Sanity check, make sure there is no overlap between the attr groups.
# REGULAR_ATTRS show up as attributes directly on the RunAttributes object.
__slots__ = tuple(REGULAR_ATTRS) + (
'_board_targets', # Set of registered board/target combinations.
'_manager', # The multiprocessing.Manager to use.
'_queues', # Dict of parallel attribute names to LockableQueues.
def __init__(self, multiprocess_manager):
# The __slots__ logic above confuses pylint.
# pylint: disable=assigning-non-slot
# Create queues for all non-board-specific parallel attributes now.
# Parallel board attributes must wait for the board to be registered.
self._manager = multiprocess_manager
self._queues = {}
for attr in RunAttributes.PARALLEL_ATTRS:
if attr not in RunAttributes.BOARD_ATTRS:
# pylint: disable=E1101
self._queues[attr] = LockableQueue(self._manager)
# Set of known <board>||<target> combinations.
self._board_targets = set()
def RegisterBoardAttrs(self, board, target):
"""Register a new valid board/target combination. Safe to repeat.
board: Board name to register.
target: Build config name to register.
A new BoardRunAttributes object for more convenient access to the newly
registered attributes specific to this board/target combination.
board_target = RunAttributes.BOARD_ATTR_SEP.join((board, target))
if not board_target in self._board_targets:
# Register board/target as a known board/target.
# For each board attribute that should be queue-able, create its queue
# now. Queues are kept by the uniquified run attribute name.
for attr in RunAttributes.BOARD_ATTRS:
# Every attr in BOARD_ATTRS is in PARALLEL_ATTRS, by construction.
# pylint: disable=E1101
uniquified_attr = self._GetBoardAttrName(attr, board, target)
self._queues[uniquified_attr] = LockableQueue(self._manager)
return BoardRunAttributes(self, board, target)
# TODO(mtennant): Complain if a child process attempts to set a non-parallel
# run attribute? It could be done something like this:
#def __setattr__(self, attr, value):
# """Override __setattr__ to prevent misuse of run attributes."""
# if attr in self.REGULAR_ATTRS:
# assert not self._IsChildProcess()
# super(RunAttributes, self).__setattr__(attr, value)
def _GetBoardAttrName(self, attr, board, target):
"""Translate plain |attr| to uniquified board attribute name.
attr: Plain run attribute name.
board: Board name.
target: Build config name.
The uniquified board-specific attribute name.
AssertionError if the board/target combination does not exist.
board_target = RunAttributes.BOARD_ATTR_SEP.join((board, target))
assert board_target in self._board_targets, \
'Unknown board/target combination: %s/%s' % (board, target)
# Translate to the unique attribute name for attr/board/target.
return RunAttributes.BOARD_ATTR_SEP.join((attr, board, target))
def SetBoardParallel(self, attr, value, board, target):
"""Set board-specific parallel run attribute value.
attr: Plain board run attribute name.
value: Value to set.
board: Board name.
target: Build config name.
unique_attr = self._GetBoardAttrName(attr, board, target)
self.SetParallel(unique_attr, value)
except ParallelAttributeError:
# Clarify the AttributeError.
raise ParallelAttributeError(attr, board=board, target=target)
def HasBoardParallel(self, attr, board, target):
"""Return True if board-specific parallel run attribute is known and set.
attr: Plain board run attribute name.
board: Board name.
target: Build config name.
unique_attr = self._GetBoardAttrName(attr, board, target)
return self.HasParallel(unique_attr)
def SetBoardParallelDefault(self, attr, default_value, board, target):
"""Set board-specific parallel run attribute value, if not already set.
attr: Plain board run attribute name.
default_value: Value to set.
board: Board name.
target: Build config name.
if not self.HasBoardParallel(attr, board, target):
self.SetBoardParallel(attr, default_value, board, target)
def GetBoardParallel(self, attr, board, target, timeout=0):
"""Get board-specific parallel run attribute value.
attr: Plain board run attribute name.
board: Board name.
target: Build config name.
timeout: See GetParallel for description.
The value found.
unique_attr = self._GetBoardAttrName(attr, board, target)
return self.GetParallel(unique_attr, timeout=timeout)
except ParallelAttributeError:
# Clarify the AttributeError.
raise ParallelAttributeError(attr, board=board, target=target)
def _GetQueue(self, attr, strict=False):
"""Return the queue for the given attribute, if it exists.
attr: The run attribute name.
strict: If True, then complain if queue for |attr| is not found.
The LockableQueue for this attribute, if it has one, or None
(assuming strict is False).
ParallelAttributeError if no queue for this attribute is registered,
meaning no parallel attribute by this name is known.
queue = self._queues.get(attr)
if queue is None and strict:
raise ParallelAttributeError(attr)
return queue
def SetParallel(self, attr, value):
"""Set the given parallel run attribute value.
Called to set the value of any parallel run attribute. The value is
saved onto a multiprocessing queue for that attribute.
attr: Name of the attribute.
value: Value to give the attribute. This value must be pickleable.
ParallelAttributeError if attribute is not a valid parallel attribute.
AttrNotPickleableError if value cannot be pickled, meaning it cannot
go through the queue system.
# Confirm that value can be pickled, because otherwise it will fail
# in the queue.
cPickle.dumps(value, cPickle.HIGHEST_PROTOCOL)
except cPickle.PicklingError:
raise AttrNotPickleableError(attr, value)
queue = self._GetQueue(attr, strict=True)
with queue.rlock:
# First empty the queue. Any value already on the queue is now stale.
while True:
except Queue.Empty:
def HasParallel(self, attr):
"""Return True if the given parallel run attribute is known and set.
attr: Name of the attribute.
queue = self._GetQueue(attr, strict=True)
with queue.rlock:
return not queue.empty()
except ParallelAttributeError:
return False
def SetParallelDefault(self, attr, default_value):
"""Set the given parallel run attribute only if it is not already set.
This leverages HasParallel and SetParallel in a convenient pattern.
attr: Name of the attribute.
default_value: Value to give the attribute if it is not set. This value
must be pickleable.
ParallelAttributeError if attribute is not a valid parallel attribute.
AttrNotPickleableError if value cannot be pickled, meaning it cannot
go through the queue system.
if not self.HasParallel(attr):
self.SetParallel(attr, default_value)
# TODO(mtennant): Add an option to log access, including the time to wait
# or waited. It could be enabled with an optional announce=False argument.
# See GetParallel helper on BoardSpecificBuilderStage class for ideas.
def GetParallel(self, attr, timeout=0):
"""Get value for the given parallel run attribute, optionally waiting.
If the given parallel run attr already has a value in the queue it will
return that value right away. Otherwise, it will wait for a value to
appear in the queue up to the timeout specified (timeout of None means
wait forever) before returning the value found or raising AttrTimeoutError
if a timeout was reached.
attr: The name of the run attribute.
timeout: Timeout, in seconds. A None value means wait forever,
which is probably never a good idea. A value of 0 does not wait at all.
ParallelAttributeError if attribute is not set and timeout was 0.
AttrTimeoutError if timeout is greater than 0 and timeout is reached
before a value is available on the queue.
got_value = False
queue = self._GetQueue(attr, strict=True)
# First attempt to get a value off the queue, without the lock. This
# allows a blocking get to wait for a value to appear.
value = queue.get(True, timeout)
got_value = True
except Queue.Empty:
# This means there is nothing on the queue. Let this fall through to
# the locked code block to see if another process is in the process
# of re-queuing a value. Any process doing that will have a lock.
# Now grab the queue lock and flush any other values that are on the queue.
# This should only happen if another process put a value in after our first
# queue.get above. If so, accept the updated value.
with queue.rlock:
while True:
value = queue.get(False)
got_value = True
except Queue.Empty:
if got_value:
# First re-queue the value, then return it.
return value
# Handle no value differently depending on whether timeout is 0.
if timeout == 0:
raise ParallelAttributeError(attr)
raise AttrTimeoutError(attr)
class BoardRunAttributes(object):
"""Convenience class for accessing board-specific run attributes.
Board-specific run attributes (actually board/target-specific) are saved in
the RunAttributes object but under uniquified names. A BoardRunAttributes
object provides access to these attributes using their plain names by
providing the board/target information where needed.
For example, to access the breakpad_symbols_generated board run attribute on
a regular RunAttributes object requires this:
value = attrs.GetBoardParallel('breakpad_symbols_generated', board, target)
But on a BoardRunAttributes object:
boardattrs = BoardRunAttributes(attrs, board, target)
value = boardattrs.GetParallel('breakpad_symbols_generated')
The same goes for setting values.
__slots__ = ('_attrs', '_board', '_target')
def __init__(self, attrs, board, target):
attrs: The main RunAttributes object.
board: The board name this is specific to.
target: The build config name this is specific to.
self._attrs = attrs
self._board = board
self._target = target
def SetParallel(self, attr, value, *args, **kwargs):
"""Set the value of parallel board attribute |attr| to |value|.
Relay to SetBoardParallel on self._attrs, supplying board and target.
See documentation on RunAttributes.SetBoardParallel for more details.
self._attrs.SetBoardParallel(attr, value, self._board, self._target,
*args, **kwargs)
def HasParallel(self, attr, *args, **kwargs):
"""Return True if parallel board attribute |attr| exists.
Relay to HasBoardParallel on self._attrs, supplying board and target.
See documentation on RunAttributes.HasBoardParallel for more details.
return self._attrs.HasBoardParallel(attr, self._board, self._target,
*args, **kwargs)
def SetParallelDefault(self, attr, default_value, *args, **kwargs):
"""Set the value of parallel board attribute |attr| to |value|, if not set.
Relay to SetBoardParallelDefault on self._attrs, supplying board and target.
See documentation on RunAttributes.SetBoardParallelDefault for more details.
self._attrs.SetBoardParallelDefault(attr, default_value, self._board,
self._target, *args, **kwargs)
def GetParallel(self, attr, *args, **kwargs):
"""Get the value of parallel board attribute |attr|.
Relay to GetBoardParallel on self._attrs, supplying board and target.
See documentation on RunAttributes.GetBoardParallel for more details.
return self._attrs.GetBoardParallel(attr, self._board, self._target,
*args, **kwargs)
# TODO(mtennant): Consider renaming this _BuilderRunState, then renaming
# _RealBuilderRun to _BuilderRunBase.
class _BuilderRunBase(object):
"""Class to represent one run of a builder.
This class should never be instantiated directly, but instead be
instantiated as part of a BuilderRun object.
# Class-level dict of RunAttributes objects to make it less
# problematic to send BuilderRun objects between processes through
# pickle. The 'attrs' attribute on a BuilderRun object will look
# up the RunAttributes for that particular BuilderRun here.
_ATTRS = {}
__slots__ = (
'config', # BuildConfig for this run.
'options', # The cbuildbot options object for this run.
# Run attributes set/accessed by stages during the run. To add support
# for a new run attribute add it to the RunAttributes class above.
'_attrs_id', # Object ID for looking up self.attrs.
# Some pre-computed run configuration values.
'buildnumber', # The build number for this run.
'buildroot', # The build root path for this run.
'debug', # Boolean, represents "dry run" concept, really.
'manifest_branch', # The manifest branch to build and test for this run.
# Some attributes are available as properties. In particular, attributes
# that use self.config must be determined after __init__.
# self.bot_id # Effective name of builder for this run.
# TODO(mtennant): Other candidates here include:
# trybot, buildbot, remote_trybot, chrome_root,
# test = (config build_tests AND option tests)
def __init__(self, options, multiprocess_manager):
self.options = options
# Note that self.config is filled in dynamically by either of the classes
# that are actually instantiated: BuilderRun and ChildBuilderRun. In other
# words, self.config can be counted on anywhere except in this __init__.
# The implication is that any plain attributes that are calculated from
# self.config contents must be provided as properties (or methods).
# See the _RealBuilderRun class and its __getattr__ method for details.
self.config = None
# Create the RunAttributes object for this BuilderRun and save
# the id number for it in order to look it up via attrs property.
attrs = RunAttributes(multiprocess_manager)
self._ATTRS[id(attrs)] = attrs
self._attrs_id = id(attrs)
# Fill in values for all pre-computed "run configs" now, which are frozen
# by this time.
# TODO(mtennant): Should this use os.path.abspath like builderstage does?
self.buildroot = self.options.buildroot
self.buildnumber = self.options.buildnumber
self.manifest_branch = self.options.branch
# For remote_trybot runs, options.debug is implied, but we want true dryrun
# mode only if --debug was actually specified (i.e. options.debug_forced).
# TODO(mtennant): Get rid of confusing debug and debug_forced, if at all
# possible. Also, eventually use "dry_run" and "verbose" options instead to
# represent two distinct concepts.
self.debug = self.options.debug
if self.options.remote_trybot:
self.debug = self.options.debug_forced
# The __slots__ logic above confuses pylint.
# pylint: disable=assigning-non-slot
# Certain run attributes have sensible defaults which can be set here.
# This allows all code to safely assume that the run attribute exists.
attrs.chrome_version = None
attrs.metadata = metadata_lib.CBuildbotMetadata(
def bot_id(self):
"""Return the bot_id for this run."""
return self.config.GetBotId(remote_trybot=self.options.remote_trybot)
def attrs(self):
"""Look up the RunAttributes object for this BuilderRun object."""
return self._ATTRS[self._attrs_id]
def IsToTBuild(self):
"""Returns True if Builder is running on ToT."""
return self.manifest_branch == 'master'
def GetArchive(self):
"""Create an Archive object for this BuilderRun object."""
# The Archive class is very lightweight, and is read-only, so it
# is ok to generate a new one on demand. This also avoids worrying
# about whether it can go through pickle.
# Almost everything the Archive class does requires GetVersion(),
# which means it cannot be used until the version has been settled on.
# However, because it does have some use before then we provide
# the GetVersion function itself to be called when needed later.
return archive_lib.Archive(self.bot_id, self.GetVersion, self.options,
def GetBoardRunAttrs(self, board):
"""Create a BoardRunAttributes object for this run and given |board|."""
return BoardRunAttributes(self.attrs, board,
def GetWaterfall(self):
"""Gets the waterfall of the current build."""
# Metadata dictionary may not have been written at this time (it
# should be written in the BuildStartStage), fall back to get the
# environment variable in that case.
return (self.attrs.metadata.GetDict().get('buildbot-master-name') or
os.environ.get('BUILDBOT_MASTERNAME', ''))
def ConstructDashboardURL(self, stage=None):
"""Return the dashboard URL
This is the direct link to buildbot logs as seen in
stage: Link to a specific |stage|, otherwise the general buildbot log
The fully formed URL
return tree_status.ConstructDashboardURL(
self.options.buildnumber, stage=stage)
def ShouldBuildAutotest(self):
"""Return True if this run should build autotest and artifacts."""
return self.config.build_tests and self.options.tests
def ShouldUploadPrebuilts(self):
"""Return True if this run should upload prebuilts."""
return self.options.prebuilts and self.config.prebuilts
def GetCIDBHandle(self):
"""Get the build_id and cidb handle, if available.
A (build_id, CIDBConnection) tuple if cidb is set up and a build_id is
known in metadata. Otherwise, (None, None).
build_id = self.attrs.metadata.GetValue('build_id')
except KeyError:
return (None, None)
if not cidb.CIDBConnectionFactory.IsCIDBSetup():
return (None, None)
cidb_handle = cidb.CIDBConnectionFactory.GetCIDBConnectionForBuilder()
if cidb_handle:
return (build_id, cidb_handle)
return (None, None)
def ShouldReexecAfterSync(self):
"""Return True if this run should re-exec itself after sync stage."""
if self.options.postsync_reexec and self.config.postsync_reexec:
# Return True if this source is not in designated buildroot.
abs_buildroot = os.path.abspath(self.buildroot)
return not os.path.abspath(__file__).startswith(abs_buildroot)
return False
def ShouldPatchAfterSync(self):
"""Return True if this run should patch changes after sync stage."""
return self.options.postsync_patch and self.config.postsync_patch
def GetVersionInfo(cls, buildroot):
"""Helper for picking apart various version bits.
This method only exists so that tests can override it.
return manifest_version.VersionInfo.from_repo(buildroot)
def GetVersion(self):
"""Calculate full R<chrome_version>-<chromeos_version> version string.
It is required that the sync stage be run before this method is called.
The version string for this run.
AssertionError if the sync stage has not been run first.
# This method should never be called before the sync stage has run, or
# it would return a confusing value.
assert hasattr(self.attrs, 'release_tag'), 'Sync stage must run first.'
verinfo = self.GetVersionInfo(self.buildroot)
release_tag = self.attrs.release_tag
if release_tag:
calc_version = 'R%s-%s' % (verinfo.chrome_branch, release_tag)
# Non-versioned builds need the build number to uniquify the image.
calc_version = 'R%s-%s-b%s' % (verinfo.chrome_branch,
return calc_version
def DetermineChromeVersion(self):
"""Determine the current Chrome version in buildroot now and return it.
This uses the typical portage logic to determine which version of Chrome
is active right now in the buildroot.
The new value of attrs.chrome_version (e.g. "35.0.1863.0").
cpv = portage_util.BestVisible(constants.CHROME_CP,
return cpv.version_no_rev.partition('_')[0]
class _RealBuilderRun(object):
"""Base BuilderRun class that manages self.config access.
For any builder run, sometimes the build config is the top-level config and
sometimes it is a "child" config. In either case, the config to use should
override self.config for all cases. This class provides a mechanism for
overriding self.config access generally.
Also, methods that do more than access state for a BuilderRun should
live here. In particular, any method that uses 'self' as an object
directly should be here rather than _BuilderRunBase.
__slots__ = _BuilderRunBase.__slots__ + (
'_run_base', # The _BuilderRunBase object where most functionality is.
'_config', # Config to use for dynamically overriding self.config.
def __init__(self, run_base, build_config):
self._run_base = run_base
self._config = build_config
# Make sure self.attrs has board-specific attributes for each board
# in build_config.
for board in build_config.boards:
def __getattr__(self, attr):
# Remember, __getattr__ only called if attribute was not found normally.
# In normal usage, the __init__ guarantees that self._run_base and
# self._config will be present. However, the unpickle process bypasses
# __init__, and this object must be pickle-able. That is why we access
# self._run_base and self._config through __getattribute__ here, otherwise
# unpickling results in infinite recursion.
# TODO(mtennant): Revisit this if pickling support is changed to go through
# the __init__ method, such as by supplying __reduce__ method.
run_base = self.__getattribute__('_run_base')
config = self.__getattribute__('_config')
# run_base.config should always be None except when accessed through
# this routine. Override the value here, then undo later.
run_base.config = config
result = getattr(run_base, attr)
if isinstance(result, types.MethodType):
# Make sure run_base.config is also managed when the method is called.
def FuncWrapper(*args, **kwargs):
run_base.config = config
return result(*args, **kwargs)
run_base.config = None
# TODO(mtennant): Find a way to make the following actually work. It
# makes pickling more complicated, unfortunately.
# Cache this function wrapper to re-use next time without going through
# __getattr__ again. This ensures that the same wrapper object is used
# each time, which is nice for identity and equality checks. Subtle
# gotcha that we accept: if the function itself on run_base is replaced
# then this will continue to provide the behavior of the previous one.
#setattr(self, attr, FuncWrapper)
return FuncWrapper
return result
run_base.config = None
def GetChildren(self):
"""Get ChildBuilderRun objects for child configs, if they exist.
List of ChildBuilderRun objects if self.config has child_configs. []
# If there are child configs, construct a list of ChildBuilderRun objects
# for those child configs and return that.
return [ChildBuilderRun(self, ix)
for ix in range(len(self.config.child_configs))]
def GetUngroupedBuilderRuns(self):
"""Same as GetChildren, but defaults to [self] if no children exist.
Result of self.GetChildren, if children exist, otherwise [self].
return self.GetChildren() or [self]
def GetBuilderIds(self):
"""Return a list of builder names for this config and the child configs."""
bot_ids = []
for config in self.config.child_configs:
return bot_ids
class BuilderRun(_RealBuilderRun):
"""A standard BuilderRun for a top-level build config."""
def __init__(self, options, build_config, multiprocess_manager):
options: Command line options from this cbuildbot run.
build_config: Build config for this cbuildbot run.
multiprocess_manager: A multiprocessing.Manager.
run_base = _BuilderRunBase(options, multiprocess_manager)
super(BuilderRun, self).__init__(run_base, build_config)
class ChildBuilderRun(_RealBuilderRun):
"""A BuilderRun for a "child" build config."""
def __init__(self, builder_run, child_index):
builder_run: BuilderRun for the parent (main) cbuildbot run. Extract
the _BuilderRunBase from it to make sure the same base is used for
both the main cbuildbot run and any child runs.
child_index: The child index of this child run, used to index into
the main run's config.child_configs.
# pylint: disable=W0212
run_base = builder_run._run_base
config = builder_run.config.child_configs[child_index]
super(ChildBuilderRun, self).__init__(run_base, config)