| # -*- coding: utf-8 -*- |
| # Copyright 2017 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. |
| |
| """Build stages related to a secondary workspace directory. |
| |
| A workspace is a compelete ChromeOS checkout and may contain it's own chroot, |
| .cache directory, etc. Conceptually, cbuildbot_launch creates a workspace for |
| the intitial ChromeOS build, but these stages are for creating a secondary |
| build. |
| |
| This might be useful if a build needs to work with more than one branch at a |
| time, or make changes to ChromeOS code without changing the code it is currently |
| running. |
| |
| A secondary workspace may not be inside an existing ChromeOS repo checkout. |
| Also, the initial sync will usually take about 40 minutes, so performance should |
| be considered carefully. |
| """ |
| |
| from __future__ import print_function |
| |
| import os |
| import re |
| |
| from chromite.cbuildbot import cbuildbot_run |
| from chromite.cbuildbot import commands |
| from chromite.cbuildbot import manifest_version |
| from chromite.cbuildbot import trybot_patch_pool |
| from chromite.cbuildbot.stages import artifact_stages |
| from chromite.cbuildbot.stages import generic_stages |
| from chromite.lib import config_lib |
| from chromite.lib import constants |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import cros_sdk_lib |
| from chromite.lib import failures_lib |
| from chromite.lib import gs |
| from chromite.lib import osutils |
| from chromite.lib import path_util |
| from chromite.lib import portage_util |
| from chromite.lib import request_build |
| from chromite.lib import retry_util |
| from chromite.lib import timeout_util |
| from chromite.lib.parser import package_info |
| |
| |
| BUILD_PACKAGES_PREBUILTS = '10774.0.0' |
| BUILD_PACKAGES_WITH_DEBUG_SYMBOLS = '6302.0.0' |
| CROS_RUN_UNITTESTS = '6773.0.0' |
| BUILD_IMAGE_BUILDER_PATH = '8183.0.0' |
| BUILD_IMAGE_ECLEAN_FLAG = '8318.0.0' |
| ANDROID_BREAKPAD = '9667.0.0' |
| SETUP_BOARD_PORT_COMPLETE = '11802.0.0' |
| |
| |
| class InvalidWorkspace(failures_lib.StepFailure): |
| """Raised when a workspace isn't usable.""" |
| |
| |
| def ChrootArgs(options): |
| """cros_sdk command line arguments. |
| |
| To ensure consistent arguments passed into cros_sdk for workspace stages, |
| compute them here. |
| |
| Args: |
| options: self._run.options |
| |
| Returns: |
| List of command line arguments, normally passed into run as chroot_args. |
| """ |
| chroot_args = ['--cache-dir', options.cache_dir] |
| if options.chrome_root: |
| chroot_args += ['--chrome_root', options.chrome_root] |
| |
| return chroot_args |
| |
| |
| class WorkspaceStageBase(generic_stages.BuilderStage): |
| """Base class for Workspace stages.""" |
| def __init__(self, builder_run, buildstore, build_root, **kwargs): |
| """Initializer. |
| |
| Properties for subclasses: |
| self._build_root to access the workspace directory, |
| self._orig_root to access the original buildroot. |
| |
| Args: |
| builder_run: BuilderRun object. |
| buildstore: BuildStore instance to make DB calls with. |
| build_root: Fully qualified path to use as a string. |
| """ |
| super(WorkspaceStageBase, self).__init__( |
| builder_run, buildstore, build_root=build_root, |
| **kwargs) |
| |
| self._orig_root = builder_run.buildroot |
| |
| def GetWorkspaceRepo(self): |
| """Fetch a repo object for the workspace. |
| |
| Returns: |
| repository.RepoRepository instance for the workspace. |
| """ |
| # TODO: Properly select the manifest. Currently hard coded to internal |
| # branch checkouts. |
| manifest_url = config_lib.GetSiteParams().MANIFEST_INT_URL |
| |
| # Workspace repos use the workspace URL / branch. |
| return self.GetRepoRepository( |
| manifest_repo_url=manifest_url, |
| branch=self._run.config.workspace_branch) |
| |
| def GetWorkspaceVersionInfo(self): |
| """Fetch a VersionInfo for the workspace. |
| |
| Only valid after the workspace has been synced. |
| |
| Returns: |
| manifest-version.VersionInfo object based on the workspace checkout. |
| """ |
| return manifest_version.VersionInfo.from_repo(self._build_root) |
| |
| def AfterLimit(self, limit): |
| """Is worksapce version newer than cutoff limit? |
| |
| Args: |
| limit: String version of format '123.0.0' |
| |
| Returns: |
| bool: True if workspace has newer version than limit. |
| """ |
| version_info = self.GetWorkspaceVersionInfo() |
| return version_info > manifest_version.VersionInfo(limit) |
| |
| # Standardize manifest_versions paths for workspaces. |
| |
| @property |
| def int_manifest_versions_path(self): |
| """Path to use for internal manifest_versions.""" |
| return os.path.join( |
| self._orig_root, |
| config_lib.GetSiteParams().INTERNAL_MANIFEST_VERSIONS_PATH) |
| |
| @property |
| def ext_manifest_versions_path(self): |
| """Path to use for external manifest_versions.""" |
| return os.path.join( |
| self._orig_root, |
| config_lib.GetSiteParams().EXTERNAL_MANIFEST_VERSIONS_PATH) |
| |
| def GetWorkspaceReleaseTag(self): |
| workspace_version_info = self.GetWorkspaceVersionInfo() |
| |
| if self._run.options.debug: |
| build_identifier, _ = self._run.GetCIDBHandle() |
| build_id = build_identifier.cidb_id |
| return 'R%s-%s-b%s' % ( |
| workspace_version_info.chrome_branch, |
| workspace_version_info.VersionString(), |
| build_id) |
| else: |
| return 'R%s-%s' % ( |
| workspace_version_info.chrome_branch, |
| workspace_version_info.VersionString()) |
| |
| |
| class SyncStage(WorkspaceStageBase): |
| """Perform a repo sync.""" |
| |
| category = constants.CI_INFRA_STAGE |
| |
| def __init__(self, builder_run, buildstore, build_root, |
| external=False, |
| branch=None, |
| version=None, |
| patch_pool=None, |
| copy_repo=None, |
| **kwargs): |
| """Initializer. |
| |
| Args: |
| builder_run: BuilderRun object. |
| buildstore: BuildStore instance to make DB calls with. |
| build_root: Path to sync into. |
| external: Boolean telling if this an internal or external checkout. |
| branch: Branch to sync, with default to master. |
| version: Version number to sync too. |
| patch_pool: None or a list of lib.patch.GerritPatch objects. |
| copy_repo: None, or the copy of a repo to seed the sync from. |
| """ |
| super(SyncStage, self).__init__( |
| builder_run, buildstore, build_root=build_root, **kwargs) |
| |
| self.external = external |
| self.branch = branch |
| self.version = version |
| self.patch_pool = patch_pool |
| self.copy_repo = copy_repo |
| |
| def PerformStage(self): |
| """Sync stuff!""" |
| logging.info('SubWorkspaceSync') |
| |
| cmd = [ |
| os.path.join(constants.CHROMITE_DIR, 'scripts', 'repo_sync_manifest'), |
| '--repo-root', self._build_root, |
| '--manifest-versions-int', self.int_manifest_versions_path, |
| '--manifest-versions-ext', self.ext_manifest_versions_path, |
| ] |
| |
| if self.external: |
| cmd += ['--external'] |
| |
| if self.branch and not self.version: |
| cmd += ['--branch', self.branch] |
| |
| if self.version: |
| logging.PrintBuildbotStepText('Version: %s' % self.version) |
| cmd += ['--version', self.version] |
| |
| if self.patch_pool: |
| patch_options = [] |
| for patch in self.patch_pool: |
| logging.PrintBuildbotLink(str(patch), patch.url) |
| patch_options += ['--gerrit-patches', patch.gerrit_number_str] |
| |
| cmd += patch_options |
| |
| if self.copy_repo: |
| cmd += ['--copy-repo', self.copy_repo] |
| |
| assert not (self.version and self.patch_pool), ( |
| 'Can\'t cherry-pick "%s" into an official version "%s."' % |
| (patch_options, self.version)) |
| |
| cros_build_lib.run(cmd) |
| |
| |
| class WorkspaceSyncStage(WorkspaceStageBase): |
| """Checkout both infra and workspace repos.""" |
| |
| category = constants.CI_INFRA_STAGE |
| |
| def PerformStage(self): |
| """Sync all the stuff!""" |
| # Select changes to cherry-pick into the build, and filter them into |
| # chromite versus branch changes. |
| patch_pool = trybot_patch_pool.TrybotPatchPool.FromOptions( |
| gerrit_patches=self._run.options.gerrit_patches) |
| |
| infra_pool = patch_pool.FilterFn(trybot_patch_pool.ChromiteFilter) |
| branch_pool = patch_pool.FilterFn(trybot_patch_pool.ChromiteFilter, |
| negate=True) |
| |
| SyncStage( |
| self._run, |
| self.buildstore, |
| build_root=self._orig_root, |
| external=True, |
| branch='master', |
| patch_pool=infra_pool, |
| suffix=' [Infra]').Run() |
| |
| branch = self._run.config.workspace_branch |
| |
| SyncStage( |
| self._run, |
| self.buildstore, |
| build_root=self._build_root, |
| external=not self._run.config.internal, |
| branch=branch, |
| version=self._run.options.force_version, |
| patch_pool=branch_pool, |
| copy_repo=self._orig_root, |
| suffix=' [%s]' % branch).Run() |
| |
| |
| class WorkspaceSyncChromeStage(WorkspaceStageBase): |
| """Stage that syncs Chrome sources if needed.""" |
| category = constants.PRODUCT_CHROME_STAGE |
| |
| # 6 hours in seconds should be long enough to fetch Chrome. I hope. |
| SYNC_CHROME_TIMEOUT = 6 * 60 * 60 |
| |
| def DetermineChromeVersion(self): |
| cpv = portage_util.PortageqBestVisible(constants.CHROME_CP, |
| cwd=self._build_root) |
| return cpv.version_no_rev.partition('_')[0] |
| |
| |
| @failures_lib.SetFailureType(failures_lib.InfrastructureFailure) |
| def PerformStage(self): |
| chrome_version = self.DetermineChromeVersion() |
| |
| logging.PrintBuildbotStepText('tag %s' % chrome_version) |
| |
| sync_chrome = os.path.join( |
| self._orig_root, 'chromite', 'bin', 'sync_chrome') |
| |
| # Branched gclient can use git-cache incompatibly, so use a temp one. |
| with osutils.TempDir(prefix='dummy') as git_cache: |
| # --reset tells sync_chrome to blow away local changes and to feel |
| # free to delete any directories that get in the way of syncing. This |
| # is needed for unattended operation. |
| # --gclient is not specified here, sync_chrome will locate the one |
| # on the $PATH. |
| cmd = [sync_chrome, |
| '--reset', |
| '--tag', chrome_version, |
| '--git_cache_dir', git_cache] |
| |
| if constants.USE_CHROME_INTERNAL in self._run.config.useflags: |
| cmd += ['--internal'] |
| |
| cmd += [self._run.options.chrome_root] |
| with timeout_util.Timeout(self.SYNC_CHROME_TIMEOUT): |
| retry_util.RunCommandWithRetries( |
| constants.SYNC_RETRIES, cmd, cwd=self._build_root) |
| |
| |
| class WorkspaceUprevAndPublishStage(WorkspaceStageBase): |
| """Uprev ebuilds, and immediately publish them. |
| |
| This stage updates ebuilds to top of branch with no verification, or prebuilt |
| generation. This is generally intended only for branch builds. |
| """ |
| config = 'push_overlays' |
| |
| def __init__(self, builder_run, buildstore, boards=None, **kwargs): |
| super(WorkspaceUprevAndPublishStage, self).__init__(builder_run, |
| buildstore, |
| **kwargs) |
| if boards is not None: |
| self._boards = boards |
| |
| def PerformStage(self): |
| """Perform the uprev and push.""" |
| commands.UprevPackages(self._orig_root, self._boards, |
| overlay_type=self._run.config.overlays, |
| workspace=self._build_root) |
| |
| logging.info('Pushing.') |
| commands.UprevPush(self._orig_root, |
| overlay_type=self._run.config.push_overlays, |
| dryrun=self._run.options.debug, |
| workspace=self._build_root) |
| |
| |
| class WorkspacePublishBuildspecStage(WorkspaceStageBase): |
| """Increment the ChromeOS version, and publish a buildspec.""" |
| |
| def PerformStage(self): |
| """Increment ChromeOS version, and publish buildpec.""" |
| repo = self.GetWorkspaceRepo() |
| |
| # TODO: Add 'patch' support somehow, |
| if repo.branch == 'master': |
| incr_type = 'build' |
| else: |
| incr_type = 'branch' |
| |
| build_spec_path = manifest_version.GenerateAndPublishOfficialBuildSpec( |
| repo, |
| incr_type, |
| manifest_versions_int=self.int_manifest_versions_path, |
| manifest_versions_ext=self.ext_manifest_versions_path, |
| dryrun=self._run.options.debug) |
| |
| if self._run.options.debug: |
| msg = 'DEBUG: Would have defined: %s' % build_spec_path |
| else: |
| msg = 'Defined: %s' % build_spec_path |
| |
| logging.PrintBuildbotStepText(msg) |
| |
| |
| class WorkspaceScheduleChildrenStage(WorkspaceStageBase): |
| """Schedule child builds for this buildspec.""" |
| |
| def PerformStage(self): |
| """Schedule child builds for this buildspec.""" |
| # build_identifier, _ = self._run.GetCIDBHandle() |
| # build_id = build_identifier.cidb_id |
| # master_buildbucket_id = self._run.options.buildbucket_id |
| version_info = self.GetWorkspaceVersionInfo() |
| |
| extra_args = [ |
| '--buildbot', |
| '--version', version_info.VersionString(), |
| ] |
| |
| if self._run.options.debug: |
| extra_args.append('--debug') |
| |
| for child_name in self._run.config.slave_configs: |
| child = request_build.RequestBuild( |
| build_config=child_name, |
| # See crbug.com/940969. These id's get children killed during |
| # multiple quick builds. |
| # master_cidb_id=build_id, |
| # master_buildbucket_id=master_buildbucket_id, |
| extra_args=extra_args, |
| ) |
| result = child.Submit(dryrun=self._run.options.debug) |
| |
| logging.info( |
| 'Build_name %s buildbucket_id %s created_timestamp %s', |
| result.build_config, result.buildbucket_id, result.created_ts) |
| logging.PrintBuildbotLink(result.build_config, result.url) |
| |
| |
| class WorkspaceInitSDKStage(WorkspaceStageBase): |
| """Stage that is responsible for initializing the SDK.""" |
| |
| category = constants.CI_INFRA_STAGE |
| |
| def PerformStage(self): |
| chroot_path = os.path.join(self._build_root, |
| constants.DEFAULT_CHROOT_DIR) |
| |
| # Worksapce chroots are always wiped by cleanup stage, no need to update. |
| cmd = ['cros_sdk', '--create'] + ChrootArgs(self._run.options) |
| |
| commands.RunBuildScript(self._build_root, cmd, chromite_cmd=True, |
| extra_env=self._portage_extra_env) |
| |
| post_ver = cros_sdk_lib.GetChrootVersion(chroot_path) |
| logging.PrintBuildbotStepText(post_ver) |
| |
| |
| class WorkspaceUpdateSDKStage(generic_stages.BuilderStage): |
| """Stage that is responsible for updating the chroot.""" |
| |
| option_name = 'build' |
| category = constants.CI_INFRA_STAGE |
| |
| def PerformStage(self): |
| """Do the work of updating the chroot.""" |
| usepkg_toolchain = ( |
| self._run.config.usepkg_toolchain and not self._latest_toolchain) |
| |
| commands.UpdateChroot( |
| self._build_root, |
| usepkg=usepkg_toolchain, |
| extra_env=self._portage_extra_env, |
| chroot_args=['--cache-dir', self._run.options.cache_dir]) |
| |
| |
| class WorkspaceSetupBoardStage(generic_stages.BoardSpecificBuilderStage, |
| WorkspaceStageBase): |
| """Stage that is responsible for building host pkgs and setting up a board.""" |
| category = constants.CI_INFRA_STAGE |
| |
| def PerformStage(self): |
| usepkg = self._run.config.usepkg_build_packages |
| func = (commands.SetupBoard if self.AfterLimit(SETUP_BOARD_PORT_COMPLETE) |
| else commands.LegacySetupBoard) |
| func( |
| self._build_root, board=self._current_board, usepkg=usepkg, |
| force=self._run.config.board_replace, |
| profile=self._run.options.profile or self._run.config.profile, |
| chroot_upgrade=True, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=self._portage_extra_env) |
| |
| |
| class WorkspaceBuildPackagesStage(generic_stages.BoardSpecificBuilderStage, |
| WorkspaceStageBase): |
| """Build Chromium OS packages.""" |
| |
| category = constants.PRODUCT_OS_STAGE |
| |
| def PerformStage(self): |
| usepkg = False |
| if self.AfterLimit(BUILD_PACKAGES_PREBUILTS): |
| usepkg = self._run.config.usepkg_build_packages |
| |
| packages = self.GetListOfPackagesToBuild() |
| |
| cmd = ['./build_packages', '--board=%s' % self._current_board, |
| '--accept_licenses=@CHROMEOS', '--skip_chroot_upgrade'] |
| |
| if not self._run.options.tests: |
| cmd.append('--nowithautotest') |
| |
| if self.AfterLimit(BUILD_PACKAGES_WITH_DEBUG_SYMBOLS): |
| cmd.append('--withdebugsymbols') |
| |
| if not usepkg: |
| cmd.extend(commands.LOCAL_BUILD_FLAGS) |
| |
| if self._run.config.nobuildretry: |
| cmd.append('--nobuildretry') |
| |
| cmd.extend(packages) |
| |
| commands.RunBuildScript( |
| self._build_root, cmd, |
| enter_chroot=True, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=self._portage_extra_env) |
| |
| |
| class WorkspaceUnitTestStage(generic_stages.BoardSpecificBuilderStage, |
| WorkspaceStageBase): |
| """Run unit tests.""" |
| option_name = 'tests' |
| config_name = 'unittests' |
| category = constants.PRODUCT_OS_STAGE |
| |
| # If the unit tests take longer than 90 minutes, abort. They usually take |
| # thirty minutes to run, but they can take twice as long if the machine is |
| # under load (e.g. in canary groups). |
| # |
| # If the processes hang, parallel_emerge will print a status report after 60 |
| # minutes, so we picked 90 minutes because it gives us a little buffer time. |
| UNIT_TEST_TIMEOUT = 90 * 60 |
| |
| def WaitUntilReady(self): |
| """Decide if we should run the unittest stage.""" |
| # See crbug.com/937328. |
| if not self.AfterLimit(CROS_RUN_UNITTESTS): |
| logging.PrintBuildbotStepWarnings() |
| logging.warning('cros_run_unit_tests does not exist on this branch.') |
| return False |
| |
| return True |
| |
| def PerformStage(self): |
| extra_env = {} |
| if self._run.config.useflags: |
| extra_env['USE'] = ' '.join(self._run.config.useflags) |
| r = ' Reached UnitTestStage timeout.' |
| with timeout_util.Timeout(self.UNIT_TEST_TIMEOUT, reason_message=r): |
| try: |
| commands.RunUnitTests( |
| self._build_root, |
| self._current_board, |
| blacklist=self._run.config.unittest_blacklist, |
| build_stage=self._run.config.build_packages, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=extra_env) |
| except failures_lib.BuildScriptFailure: |
| logging.PrintBuildbotStepWarnings() |
| logging.warning('Unittests failed. Ignored crbug.com/936123.') |
| |
| |
| class WorkspaceBuildImageStage(generic_stages.BoardSpecificBuilderStage, |
| WorkspaceStageBase): |
| """Build standard Chromium OS images.""" |
| |
| option_name = 'build' |
| config_name = 'images' |
| category = constants.PRODUCT_OS_STAGE |
| |
| def PerformStage(self): |
| |
| # Collect build_image arguments. |
| version = self.GetWorkspaceReleaseTag() |
| rootfs_verification = self._run.config.rootfs_verification |
| disk_layout = self._run.config.disk_layout |
| builder_path = '/'.join([self._bot_id, version]) |
| |
| # We only build base, dev, and test images from this stage. |
| images_can_build = set(['base', 'dev', 'test']) |
| images_to_build = set(self._run.config.images).intersection( |
| images_can_build) |
| assert images_to_build |
| |
| # Build up command line. |
| cmd = ['./build_image', |
| '--board', self._current_board, |
| '--replace', |
| '--version', version] |
| |
| if self.AfterLimit(BUILD_IMAGE_ECLEAN_FLAG): |
| cmd += ['--noeclean'] |
| |
| if not rootfs_verification: |
| cmd += ['--noenable_rootfs_verification'] |
| |
| if disk_layout: |
| cmd += ['--disk_layout', disk_layout] |
| |
| if self.AfterLimit(BUILD_IMAGE_BUILDER_PATH): |
| cmd += ['--builder_path', builder_path] |
| |
| cmd += sorted(images_to_build) |
| |
| # Run command. |
| commands.RunBuildScript( |
| self._build_root, |
| cmd, |
| enter_chroot=True, |
| extra_env=self._portage_extra_env, |
| chroot_args=ChrootArgs(self._run.options)) |
| |
| |
| class WorkspaceDebugSymbolsStage(WorkspaceStageBase, |
| generic_stages.BoardSpecificBuilderStage, |
| generic_stages.ArchivingStageMixin): |
| """Handles generation & upload of debug symbols.""" |
| |
| config_name = 'debug_symbols' |
| category = constants.PRODUCT_OS_STAGE |
| |
| @failures_lib.SetFailureType(failures_lib.InfrastructureFailure) |
| def PerformStage(self): |
| """Generate debug symbols and upload debug.tgz.""" |
| buildroot = self._build_root |
| board = self._current_board |
| |
| # Generate breakpad symbols of Chrome OS binaries. |
| commands.GenerateBreakpadSymbols( |
| buildroot, board, |
| self._run.options.debug_forced, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=self._portage_extra_env) |
| |
| # Download android symbols (if this build has them), and Generate |
| # breakpad symbols of Android binaries. This must be done after |
| # GenerateBreakpadSymbols because it clobbers the output |
| # directory. |
| symbols_file = self.DownloadAndroidSymbols() |
| |
| if symbols_file: |
| try: |
| commands.GenerateAndroidBreakpadSymbols( |
| buildroot, board, symbols_file, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=self._portage_extra_env) |
| except failures_lib.BuildScriptFailure: |
| # Android breakpad symbol preparation is expected to work in |
| # modern branches. |
| if self.AfterLimit(ANDROID_BREAKPAD): |
| raise |
| |
| # For older branches, we only process them on a best effort basis. |
| logging.PrintBuildbotStepWarnings() |
| logging.warning('Preparing Android symbols failed, ignoring..') |
| |
| # Upload them. |
| self.UploadDebugTarball() |
| |
| # Upload debug/breakpad tarball. |
| self.UploadDebugBreakpadTarball() |
| |
| # Upload them to crash server. |
| if self._run.config.upload_symbols: |
| self.UploadSymbols(buildroot, board) |
| |
| def UploadDebugTarball(self): |
| """Generate and upload the debug tarball.""" |
| filename = commands.GenerateDebugTarball( |
| buildroot=self._build_root, |
| board=self._current_board, |
| archive_path=self.archive_path, |
| gdb_symbols=self._run.config.archive_build_debug, |
| archive_name='debug.tgz', |
| chroot_compression=False) |
| self.UploadArtifact(filename, archive=False) |
| |
| def UploadDebugBreakpadTarball(self): |
| """Generate and upload the debug tarball with only breakpad files.""" |
| filename = commands.GenerateDebugTarball( |
| buildroot=self._build_root, |
| board=self._current_board, |
| archive_path=self.archive_path, |
| gdb_symbols=False, |
| archive_name='debug_breakpad.tar.xz', |
| chroot_compression=False) |
| self.UploadArtifact(filename, archive=False) |
| |
| def UploadSymbols(self, buildroot, board): |
| """Upload generated debug symbols.""" |
| failed_name = 'failed_upload_symbols.list' |
| failed_list = os.path.join(self.archive_path, failed_name) |
| |
| if self._run.options.debug: |
| # For debug builds, limit ourselves to just uploading 1 symbol. |
| # This way trybots and such still exercise this code. |
| cnt = 1 |
| official = False |
| else: |
| cnt = None |
| official = self._run.config.chromeos_official |
| |
| upload_passed = True |
| try: |
| commands.UploadSymbols( |
| buildroot, board, official, cnt, failed_list, |
| chroot_args=ChrootArgs(self._run.options), |
| extra_env=self._portage_extra_env) |
| except failures_lib.BuildScriptFailure: |
| upload_passed = False |
| |
| if os.path.exists(failed_list): |
| self.UploadArtifact(failed_name, archive=False) |
| |
| logging.notice('To upload the missing symbols from this build, run:') |
| for url in self._GetUploadUrls(filename=failed_name): |
| logging.notice('upload_symbols --failed-list %s %s', |
| os.path.join(url, failed_name), |
| os.path.join(url, 'debug_breakpad.tar.xz')) |
| |
| # Delay throwing the exception until after we uploaded the list. |
| if not upload_passed: |
| raise artifact_stages.DebugSymbolsUploadException( |
| 'Failed to upload all symbols.') |
| |
| def DetermineAndroidPackage(self): |
| """Returns the active Android container package in use by the board. |
| |
| Workspace version of cbuildbot_run.DetermineAndroidPackage(). |
| |
| Returns: |
| String identifier for a package, or None |
| """ |
| packages = portage_util.GetPackageDependencies( |
| self._current_board, 'virtual/target-os', |
| buildroot=self._build_root) |
| |
| android_packages = {p for p in packages |
| if p.startswith('chromeos-base/android-container-') or |
| p.startswith('chromeos-base/android-vm-')} |
| |
| assert len(android_packages) <= 1 |
| |
| if android_packages: |
| return next(iter(android_packages)) |
| else: |
| return None |
| |
| def DetermineAndroidBranch(self, package): |
| """Returns the Android branch in use by the active container ebuild. |
| |
| Workspace version of cbuildbot_run.DetermineAndroidBranch(). |
| |
| Args: |
| package: String name of Android package to get branch of. |
| |
| Returns: |
| String with the android container branch name. |
| """ |
| ebuild_path = portage_util.FindEbuildForBoardPackage( |
| package, self._current_board, buildroot=self._build_root) |
| host_ebuild_path = path_util.FromChrootPath(ebuild_path, |
| source_path=self._build_root) |
| # We assume all targets pull from the same branch and that we always |
| # have at least one of the following targets. |
| targets = constants.ANDROID_ALL_BUILD_TARGETS |
| ebuild_content = osutils.SourceEnvironment(host_ebuild_path, targets) |
| logging.info('Got ebuild env: %s', ebuild_content) |
| for target in targets: |
| if target in ebuild_content: |
| branch = re.search(r'(.*?)-linux-', ebuild_content[target]) |
| if branch is not None: |
| return branch.group(1) |
| raise cbuildbot_run.NoAndroidBranchError( |
| 'Android branch could not be determined for %s (ebuild empty?)' % |
| ebuild_path) |
| |
| def DetermineAndroidVersion(self, package): |
| """Determine the current Android version in buildroot now and return it. |
| |
| This uses the typical portage logic to determine which version of Android |
| is active right now in the buildroot. |
| |
| Workspace version of cbuildbot_run.DetermineAndroidVersion(). |
| |
| Args: |
| package: String name of Android package to get version of. |
| |
| Returns: |
| The Android build ID of the container for the boards. |
| """ |
| cpv = package_info.SplitCPV(package) |
| return cpv.version_no_rev |
| |
| def DetermineAndroidABI(self): |
| """Returns the Android ABI in use by the active container ebuild. |
| |
| Workspace version of cbuildbot_run.DetermineAndroidABI(). |
| |
| Args: |
| package: String name of Android package to get ABI version of. |
| |
| Returns: |
| string defining ABI of the container. |
| """ |
| use_flags = portage_util.GetInstalledPackageUseFlags( |
| 'sys-devel/arc-build', self._current_board, |
| buildroot=self._build_root) |
| if 'abi_x86_64' in use_flags.get('sys-devel/arc-build', []): |
| return 'x86_64' |
| elif 'abi_x86_32' in use_flags.get('sys-devel/arc-build', []): |
| return 'x86' |
| else: |
| # ARM only supports 32-bit so it does not have abi_x86_{32,64} set. But it |
| # is also the last possible ABI, so returning by default. |
| return 'arm' |
| |
| def DetermineAndroidVariant(self, package): |
| """Returns the Android variant in use by the active container ebuild.""" |
| |
| all_use_flags = portage_util.GetInstalledPackageUseFlags( |
| package, self._current_board, buildroot=self._build_root) |
| for use_flags in all_use_flags.values(): |
| for use_flag in use_flags: |
| if 'cheets_userdebug' in use_flag or 'cheets_sdk_userdebug' in use_flag: |
| return 'userdebug' |
| elif 'cheets_user' in use_flag or 'cheets_sdk_user' in use_flag: |
| # TODO(b/120999609): bertha builds always download userdebug builds |
| # at the moment because user builds are broken. Remove this clause |
| # when resolved. |
| if self.DetermineAndroidTarget(package) == 'bertha': |
| return 'userdebug' |
| return 'user' |
| |
| # We iterated through all the flags and could not find user or userdebug. |
| # This should not be possible given that this code is only ran by |
| # builders, which will never use local images. |
| raise cbuildbot_run.NoAndroidVariantError( |
| 'Android Variant cannot be determined for the packge: %s' % |
| package) |
| |
| def DetermineAndroidTarget(self, package): |
| if package.startswith('chromeos-base/android-vm-'): |
| return 'bertha' |
| if package.startswith('chromeos-base/android-container-'): |
| return 'cheets' |
| |
| raise cbuildbot_run.NoAndroidTargetError( |
| 'Android Target cannot be determined for the package: %s' % |
| package) |
| |
| def DownloadAndroidSymbols(self): |
| """Helper to download android container symbols, as needed. |
| |
| Determines which, if any, Android container this build includes, and |
| downloads it's symbols. |
| |
| Returns: |
| path to downloaded symbols file, or None if not downloaded. |
| """ |
| android_package = self.DetermineAndroidPackage() |
| if not android_package: |
| logging.info('Android is not enabled on this board. Skipping symbols.') |
| return None |
| |
| android_build_branch = self.DetermineAndroidBranch(android_package) |
| android_version = self.DetermineAndroidVersion(android_package) |
| arch = self.DetermineAndroidABI() |
| variant = self.DetermineAndroidVariant(android_package) |
| android_target = self.DetermineAndroidTarget(android_package) |
| # For user builds, there are no suffix. |
| # For userdebug builds, there is an explicit '_userdebug' suffix. |
| suffix = '' |
| if variant != 'user': |
| suffix = '_' + variant |
| |
| logging.info( |
| 'Downloading symbols of Android %s (%s)...', |
| android_version, android_build_branch) |
| |
| symbols_file_url = constants.ANDROID_SYMBOLS_URL_TEMPLATE % { |
| 'branch': android_build_branch, |
| 'target': android_target, |
| 'arch': arch, |
| 'version': android_version, |
| 'variant': variant, |
| 'suffix': suffix} |
| |
| # Should be based on self.archive_path, but we need a path inside |
| # the workspace chroot, not infra chroot. |
| symbols_file = os.path.join(self._build_root, |
| 'buildbot_archive', |
| constants.ANDROID_SYMBOLS_FILE) |
| gs_context = gs.GSContext() |
| gs_context.Copy(symbols_file_url, symbols_file) |
| |
| return symbols_file |