blob: 8cf9c3cc56192978ccb300f17a0779432c624545 [file] [log] [blame]
# Copyright 2015 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing the simple builders."""
import collections
import logging
from chromite.cbuildbot.builders import generic_builders
from chromite.cbuildbot.stages import android_stages
from chromite.cbuildbot.stages import artifact_stages
from chromite.cbuildbot.stages import build_stages
from chromite.cbuildbot.stages import completion_stages
from chromite.cbuildbot.stages import report_stages
from chromite.cbuildbot.stages import scheduler_stages
from chromite.cbuildbot.stages import sync_stages
from chromite.cbuildbot.stages import test_stages
from chromite.lib import chromeos_version
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import failures_lib
from chromite.lib import parallel
from chromite.lib import results_lib
# TODO: SimpleBuilder needs to be broken up big time.
BoardConfig = collections.namedtuple("BoardConfig", ["board", "name"])
class SimpleBuilder(generic_builders.Builder):
"""Builder that performs basic vetting operations."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.sync_stage = None
def GetSyncInstance(self):
"""Sync to lkgm or TOT as necessary.
Returns:
The instance of the sync stage to run.
"""
if self._run.options.force_version:
sync_stage = self._GetStageInstance(
sync_stages.ManifestVersionedSyncStage
)
else:
sync_stage = self._GetStageInstance(sync_stages.SyncStage)
self.sync_stage = sync_stage
return sync_stage
def GetVersionInfo(self):
"""Returns the CrOS version info from the chromiumos-overlay."""
return chromeos_version.VersionInfo.from_repo(self._run.buildroot)
def _RunDebugSymbolStages(self, builder_run, board) -> None:
"""Run debug-related stages for the specified board.
Args:
builder_run: BuilderRun object for these background stages.
board: Board name.
"""
# These stages should run sequentially.
self._RunStage(
android_stages.DownloadAndroidDebugSymbolsStage,
board,
builder_run=builder_run,
)
self._RunStage(
artifact_stages.DebugSymbolsStage, board, builder_run=builder_run
)
def _RunBackgroundStagesForBoardAndMarkAsSuccessful(
self, builder_run, board
) -> None:
"""Run background board-specific stages for the specified board.
After finishing the build, mark it as successful.
Args:
builder_run: BuilderRun object for these background stages.
board: Board name.
"""
self._RunBackgroundStagesForBoard(builder_run, board)
board_runattrs = builder_run.GetBoardRunAttrs(board)
board_runattrs.SetParallel("success", True)
def _RunBackgroundStagesForBoard(self, builder_run, board) -> None:
"""Run background board-specific stages for the specified board.
Used by _RunBackgroundStagesForBoardAndMarkAsSuccessful. Callers should
use that method instead.
Args:
builder_run: BuilderRun object for these background stages.
board: Board name.
"""
config = builder_run.config
# TODO(mtennant): This is the last usage of self.archive_stages. We can
# kill it once we migrate its uses to BuilderRun so that none of the
# stages below need it as an argument.
archive_stage = self.archive_stages[BoardConfig(board, config.name)]
# While this stage list is run in parallel, the order here dictates the
# order that things will be shown in the log. So group things together
# that make sense when read in order. Also keep in mind that, since we
# gather output manually, early slow stages will prevent any output from
# later stages showing up until it finishes.
early_stage_list = [
[test_stages.UnitTestStage, board],
]
stage_list = []
stage_list += [
[artifact_stages.UploadPrebuiltsStage, board],
]
if config.run_build_configs_export:
stage_list += [[artifact_stages.BuildConfigsExportStage, board]]
# This is what adds the autotest/tast uploads to the image.
early_stage_list += [[artifact_stages.UploadTestArtifactsStage, board]]
early_stage_objs = [
self._GetStageInstance(*x, builder_run=builder_run)
for x in early_stage_list
]
stage_objs = [
self._GetStageInstance(*x, builder_run=builder_run)
for x in stage_list
]
# Build the image first before running the steps.
with self._build_image_lock:
self._RunStage(
build_stages.BuildImageStage,
board,
builder_run=builder_run,
afdo_use=True,
)
# Run the debug symbols stage before the UnitTestStage to avoid
# generating debug symbols from the altered, test symbols.
self._RunDebugSymbolStages(builder_run, board)
# Run UnitTestStage & UploadTestArtifactsStage in a separate pass before
# any of the other parallel stages to prevent races with the image
# construction in the ArchiveStage.
# http://crbug.com/1000374
self._RunParallelStages(early_stage_objs)
parallel.RunParallelSteps(
[
lambda: self._RunParallelStages(stage_objs + [archive_stage]),
]
)
def BoardsForSimpleBuilder(self, builder_run):
"""All boards for this builder."""
return builder_run.config.boards
def RunSetupBoard(self) -> None:
"""Run the SetupBoard stage for all child configs and boards."""
for builder_run in [self._run]:
for board in self.BoardsForSimpleBuilder(builder_run):
self._RunStage(
build_stages.SetupBoardStage, board, builder_run=builder_run
)
def RunEarlySyncAndSetupStages(self) -> None:
"""Runs through the early sync and board setup stages."""
# If there are slave builders, schedule them.
if self._run.config.slave_configs:
self._RunStage(
scheduler_stages.ScheduleSlavesStage, self.sync_stage
)
self._RunStage(build_stages.UprevStage)
self._RunStage(build_stages.InitSDKStage)
self._RunStage(build_stages.UpdateSDKStage)
self._RunStage(build_stages.RegenPortageCacheStage)
self.RunSetupBoard()
self._RunStage(android_stages.AndroidMetadataStage)
def RunBuildStages(self) -> None:
"""Runs through the stages to perform the build and resulting tests."""
# Prepare stages to run in background.
tasks = []
for builder_run in [self._run]:
# Prepare a local archive directory for each "run".
builder_run.GetArchive().SetupArchivePath()
for board in self.BoardsForSimpleBuilder(builder_run):
archive_stage = self._GetStageInstance(
artifact_stages.ArchiveStage,
board,
builder_run=builder_run,
chrome_version=self._run.attrs.chrome_version,
)
board_config = BoardConfig(board, builder_run.config.name)
self.archive_stages[board_config] = archive_stage
tasks.append((builder_run, board))
# Set up a process pool to run test/archive stages in the background.
# This process runs task(board) for each board added to the queue.
task_runner = self._RunBackgroundStagesForBoardAndMarkAsSuccessful
with parallel.BackgroundTaskRunner(task_runner) as queue:
for builder_run, board in tasks:
# Run BuildPackages in the foreground.
self._RunStage(
build_stages.BuildPackagesStage,
board,
update_metadata=True,
builder_run=builder_run,
afdo_use=True,
)
# Kick off our background stages.
queue.put([builder_run, board])
def _RunDefaultTypeBuild(self) -> None:
"""Runs through the stages of a non-special-type build."""
self.RunEarlySyncAndSetupStages()
self.RunBuildStages()
def RunStages(self) -> None:
"""Runs through build process."""
self._RunDefaultTypeBuild()
class DistributedBuilder(SimpleBuilder):
"""Build class that has special logic to handle distributed builds.
These builds sync using git/manifest logic in manifest_versions. In general
they use a non-distributed builder code for the bulk of the work.
"""
def __init__(self, *args, **kwargs) -> None:
"""Initializes a buildbot builder.
Extra variables:
completion_stage_class: Stage used to complete a build. Set in the
Sync stage.
"""
super().__init__(*args, **kwargs)
self.completion_stage_class = None
self.sync_stage = None
self._completion_stage = None
def GetSyncInstance(self):
"""Syncs the tree using one of the distributed sync logic paths.
Returns:
The instance of the sync stage to run.
"""
# Determine sync class to use. CQ overrides PFQ bits so should check it
# first.
if config_lib.IsCanaryType(self._run.config.build_type):
sync_stage = self._GetStageInstance(
sync_stages.ManifestVersionedSyncStage
)
self.completion_stage_class = (
completion_stages.CanaryCompletionStage
)
elif config_lib.IsPFQType(
self._run.config.build_type
) or self._run.config.build_type in (constants.FULL_TYPE,):
sync_stage = self._GetStageInstance(
sync_stages.MasterSlaveLKGMSyncStage
)
self.completion_stage_class = (
completion_stages.MasterSlaveSyncCompletionStage
)
else:
sync_stage = self._GetStageInstance(
sync_stages.ManifestVersionedSyncStage
)
self.completion_stage_class = (
completion_stages.ManifestVersionedSyncCompletionStage
)
self.sync_stage = sync_stage
return self.sync_stage
def GetCompletionInstance(self):
"""Returns completion_stage_class instance that was used for this build.
Returns:
None if the completion_stage instance was not yet created (this
occurs during Publish).
"""
return self._completion_stage
def Complete(self, was_build_successful, build_finished) -> None:
"""Completes build by publishing any required information.
Args:
was_build_successful: Whether the build succeeded.
build_finished: Whether the build completed. A build can be
successful without completing if it raises ExitEarlyException.
"""
self._completion_stage = self._GetStageInstance(
self.completion_stage_class, self.sync_stage, was_build_successful
)
completion_successful = False
try:
self._completion_stage.Run()
completion_successful = True
except failures_lib.StepFailure:
raise
finally:
self._Publish(
was_build_successful, build_finished, completion_successful
)
def _Publish(
self, was_build_successful, build_finished, completion_successful
) -> None:
"""Updates and publishes uprevs.
Args:
was_build_successful: Whether the build succeeded.
build_finished: Whether the build completed. A build can be
successful without completing if it raises ExitEarlyException.
completion_successful: Whether the compeletion_stage succeeded.
"""
if self._run.config.master:
self._RunStage(report_stages.SlaveFailureSummaryStage)
if config_lib.IsCanaryMaster(self._run) or (
self._run.config.master
and self._run.config.build_type == constants.FULL_TYPE
):
if build_finished:
self._RunStage(completion_stages.UpdateChromeosLKGMStage)
else:
logging.info(
"Skipping UpdateChromeosLKGMStage, "
"build_successful=%d completion_successful=%d "
"build_finished=%d",
was_build_successful,
completion_successful,
build_finished,
)
if self._run.config.push_overlays:
publish = (
was_build_successful
and completion_successful
and build_finished
)
# CQ and Master Chrome PFQ no longer publish uprevs. For Master
# Chrome PFQ this is because this duty is being transitioned to the
# Chrome PUpr in the PCQ world. See http://go/pupr. There is no easy
# way to disable this in ChromeOS config, so hack the check here.
self._RunStage(
completion_stages.PublishUprevChangesStage,
self.sync_stage,
publish,
)
def RunStages(self) -> None:
"""Runs simple builder logic and publishes information to overlays."""
was_build_successful = False
build_finished = False
try:
super().RunStages()
build_identifier, _ = self._run.GetCIDBHandle()
buildbucket_id = build_identifier.buildbucket_id
was_build_successful = results_lib.Results.BuildSucceededSoFar(
self.buildstore, buildbucket_id
)
build_finished = True
except failures_lib.ExitEarlyException as ex:
# If a stage throws ExitEarlyException, it's exiting with success,
# so that means we should mark ourselves as successful.
logging.info("Detected exception %s", ex)
was_build_successful = True
raise
finally:
self.Complete(was_build_successful, build_finished)