| # -*- coding: utf-8 -*- |
| # 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. |
| |
| """Module containing SDK stages.""" |
| |
| from __future__ import print_function |
| |
| import glob |
| import json |
| import os |
| |
| from chromite.cbuildbot import commands |
| from chromite.cbuildbot import prebuilts |
| from chromite.cbuildbot.stages import generic_stages |
| from chromite.lib import constants |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import osutils |
| from chromite.lib import perf_uploader |
| from chromite.lib import portage_util |
| from chromite.lib import toolchain |
| from chromite.scripts import upload_prebuilts |
| |
| # Version of the Manifest file being generated for SDK artifacts. Should be |
| # incremented for major format changes. |
| PACKAGE_MANIFEST_VERSION = '1' |
| |
| # Paths excluded when packaging SDK artifacts. These are relative to the target |
| # build root where SDK packages are being installed (e.g. /build/amd64-host). |
| PACKAGE_EXCLUDED_PATHS = ('usr/lib/debug', 'usr/lib64/debug', |
| constants.AUTOTEST_BUILD_PATH, 'packages', 'tmp') |
| |
| # Names of various packaged artifacts. |
| SDK_TARBALL_NAME = 'built-sdk.tar.xz' |
| TOOLCHAINS_OVERLAY_TARBALL_TEMPLATE = \ |
| 'built-sdk-overlay-toolchains-%(toolchains)s.tar.xz' |
| |
| |
| def SdkPerfPath(buildroot): |
| """Return the path to the perf file for sdk stages.""" |
| return os.path.join(buildroot, constants.DEFAULT_CHROOT_DIR, 'tmp', |
| 'cros-sdk.perf') |
| |
| |
| def CreateTarball(source_root, tarball_path, exclude_paths=None): |
| """Packs |source_root| into |tarball_path|. |
| |
| Args: |
| source_root: Path to the directory we want to package. |
| tarball_path: Path of the tarball that should be created. |
| exclude_paths: Subdirectories to exclude. |
| """ |
| # TODO(zbehan): We cannot use xz from the chroot unless it's |
| # statically linked. |
| extra_args = None |
| if exclude_paths is not None: |
| extra_args = ['--anchored'] |
| extra_args.extend('--exclude=./%s/*' % x for x in exclude_paths) |
| # Options for maximum compression. |
| extra_env = {'XZ_OPT': '-e9'} |
| cros_build_lib.CreateTarball( |
| tarball_path, |
| source_root, |
| sudo=True, |
| extra_args=extra_args, |
| debug_level=logging.INFO, |
| extra_env=extra_env) |
| # Make sure the regular user has the permission to read. |
| cmd = ['chmod', 'a+r', tarball_path] |
| cros_build_lib.sudo_run(cmd) |
| |
| |
| class SDKBuildToolchainsStage(generic_stages.BuilderStage, |
| generic_stages.ArchivingStageMixin): |
| """Stage that builds all the cross-compilers we care about""" |
| |
| category = constants.PRODUCT_TOOLCHAIN_STAGE |
| |
| def PerformStage(self): |
| chroot_location = os.path.join(self._build_root, |
| constants.DEFAULT_CHROOT_DIR) |
| |
| # Build the toolchains first. Since we're building & installing the |
| # compilers, need to run as root. |
| self.CrosSetupToolchains(['--nousepkg'], sudo=True, |
| extra_env=self._portage_extra_env) |
| |
| # Create toolchain packages. |
| self.CreateRedistributableToolchains(chroot_location) |
| toolchain_path = os.path.join(chroot_location, |
| constants.SDK_TOOLCHAINS_OUTPUT) |
| for files in os.listdir(toolchain_path): |
| self.UploadArtifact( |
| os.path.join(toolchain_path, files), strict=True, archive=True) |
| |
| def CrosSetupToolchains(self, cmd_args, **kwargs): |
| """Wrapper around cros_setup_toolchains to simplify things.""" |
| commands.RunBuildScript( |
| self._build_root, ['cros_setup_toolchains'] + list(cmd_args), |
| chromite_cmd=True, |
| enter_chroot=True, |
| **kwargs) |
| |
| def CreateRedistributableToolchains(self, chroot_location): |
| """Create the toolchain packages""" |
| osutils.RmDir( |
| os.path.join(chroot_location, constants.SDK_TOOLCHAINS_OUTPUT), |
| ignore_missing=True) |
| |
| # We need to run this as root because the tool creates hard links to root |
| # owned files and our bots enable security features which disallow that. |
| # Specifically, these features cause problems: |
| # /proc/sys/kernel/yama/protected_nonaccess_hardlinks |
| # /proc/sys/fs/protected_hardlinks |
| self.CrosSetupToolchains([ |
| # TODO(crbug.com/917193): Enable debugging for now. |
| '--debug', |
| '--create-packages', |
| '--output-dir', |
| os.path.join('/', constants.SDK_TOOLCHAINS_OUTPUT), |
| ], |
| sudo=True) |
| |
| |
| class SDKPackageStage(generic_stages.BuilderStage, |
| generic_stages.ArchivingStageMixin): |
| """Stage that performs preparing and packaging SDK files""" |
| |
| category = constants.PRODUCT_TOOLCHAIN_STAGE |
| |
| def __init__(self, builder_run, buildstore, version=None, **kwargs): |
| self.sdk_version = version |
| super(SDKPackageStage, self).__init__(builder_run, buildstore, **kwargs) |
| |
| def PerformStage(self): |
| tarball_location = os.path.join(self._build_root, SDK_TARBALL_NAME) |
| chroot_location = os.path.join(self._build_root, |
| constants.DEFAULT_CHROOT_DIR) |
| board_location = os.path.join(chroot_location, 'build/amd64-host') |
| manifest_location = tarball_location + '.Manifest' |
| |
| # Create a tarball of the latest SDK. |
| CreateTarball(board_location, tarball_location, |
| exclude_paths=PACKAGE_EXCLUDED_PATHS) |
| self.UploadArtifact(tarball_location, strict=True, archive=True) |
| |
| # Create a package manifest for the tarball. |
| self.CreateManifestFromSDK(board_location, manifest_location) |
| |
| self.SendPerfValues(tarball_location) |
| |
| def CreateManifestFromSDK(self, sdk_path, dest_manifest): |
| """Creates a manifest from a given source chroot. |
| |
| Args: |
| sdk_path: Path to the root of the SDK to describe. |
| dest_manifest: Path to the manifest that should be generated. |
| """ |
| logging.info('Generating manifest for new sdk') |
| package_data = {} |
| for key, version in portage_util.ListInstalledPackages(sdk_path): |
| package_data.setdefault(key, []).append((version, {})) |
| self._WriteManifest(package_data, dest_manifest) |
| |
| def _WriteManifest(self, data, manifest): |
| """Encode manifest into a json file.""" |
| json_input = dict(version=PACKAGE_MANIFEST_VERSION, packages=data) |
| osutils.WriteFile(manifest, json.dumps(json_input)) |
| |
| def _SendPerfValues(self, buildroot, sdk_tarball, buildbot_uri_log, version, |
| platform_name): |
| """Generate & upload perf data for the build""" |
| perf_path = SdkPerfPath(buildroot) |
| test_name = 'sdk' |
| units = 'bytes' |
| |
| # Make sure the file doesn't contain previous data. |
| osutils.SafeUnlink(perf_path) |
| |
| common_kwargs = { |
| 'higher_is_better': False, |
| 'graph': 'cros-sdk-size', |
| 'stdio_uri': buildbot_uri_log, |
| } |
| |
| sdk_size = os.path.getsize(sdk_tarball) |
| perf_uploader.OutputPerfValue(perf_path, 'base', sdk_size, units, |
| **common_kwargs) |
| |
| for tarball in glob.glob( |
| os.path.join(buildroot, constants.DEFAULT_CHROOT_DIR, |
| constants.SDK_TOOLCHAINS_OUTPUT, '*.tar.*')): |
| name = os.path.basename(tarball).rsplit('.', 2)[0] |
| size = os.path.getsize(tarball) |
| perf_uploader.OutputPerfValue(perf_path, name, size, units, |
| **common_kwargs) |
| perf_uploader.OutputPerfValue(perf_path, 'base_plus_%s' % name, |
| sdk_size + size, units, **common_kwargs) |
| |
| # Due to limitations in the perf dashboard, we have to create an integer |
| # based on the current timestamp. This field only accepts integers, and |
| # the perf dashboard accepts this or CrOS+Chrome official versions. |
| revision = int(version.replace('.', '')) |
| perf_values = perf_uploader.LoadPerfValues(perf_path) |
| self._UploadPerfValues( |
| perf_values, platform_name, test_name, revision=revision) |
| |
| def SendPerfValues(self, sdk_tarball): |
| """Generate & upload perf data for the build""" |
| self._SendPerfValues(self._build_root, sdk_tarball, |
| self.ConstructDashboardURL(), self.sdk_version, |
| self._run.bot_id) |
| |
| |
| class SDKPackageToolchainOverlaysStage(generic_stages.BuilderStage): |
| """Stage that creates and packages per-board toolchain overlays.""" |
| |
| category = constants.PRODUCT_TOOLCHAIN_STAGE |
| |
| def __init__(self, builder_run, buildstore, version=None, **kwargs): |
| self.sdk_version = version |
| super(SDKPackageToolchainOverlaysStage, self).__init__( |
| builder_run, buildstore, **kwargs) |
| |
| def PerformStage(self): |
| chroot_dir = os.path.join(self._build_root, constants.DEFAULT_CHROOT_DIR) |
| sdk_dir = os.path.join(chroot_dir, 'build/amd64-host') |
| tmp_dir = os.path.join(chroot_dir, 'tmp') |
| osutils.SafeMakedirs(tmp_dir, mode=0o777, sudo=True) |
| overlay_output_dir = os.path.join(chroot_dir, constants.SDK_OVERLAYS_OUTPUT) |
| osutils.RmDir(overlay_output_dir, ignore_missing=True, sudo=True) |
| osutils.SafeMakedirs(overlay_output_dir, mode=0o777, sudo=True) |
| overlay_tarball_template = os.path.join( |
| overlay_output_dir, TOOLCHAINS_OVERLAY_TARBALL_TEMPLATE) |
| |
| # Generate an overlay tarball for each unique toolchain combination. We |
| # restrict ourselves to (a) board configs that are available to the builder |
| # (naturally), and (b) toolchains that are part of the 'sdk' set. |
| sdk_toolchains = set(toolchain.GetToolchainsForBoard('sdk')) |
| generated = set() |
| for board in self._run.site_config.GetBoards(): |
| try: |
| toolchains = set(toolchain.GetToolchainsForBoard(board).keys()) |
| except portage_util.MissingOverlayError: |
| # The board overlay may not exist, e.g. on external builders. |
| continue |
| |
| toolchains_str = '-'.join(sorted(toolchains)) |
| if not toolchains.issubset(sdk_toolchains) or toolchains_str in generated: |
| continue |
| |
| with osutils.TempDir( |
| prefix='toolchains-overlay-%s.' % toolchains_str, |
| base_dir=tmp_dir, |
| sudo_rm=True) as overlay_dir: |
| # NOTE: We let MountOverlayContext remove the mount point created by |
| # the TempDir context below, because it has built-in retries for rmdir |
| # EBUSY errors that are due to unmount lag. |
| with osutils.TempDir( |
| prefix='amd64-host-%s.' % toolchains_str, |
| base_dir=tmp_dir, |
| delete=False) as merged_dir: |
| with osutils.MountOverlayContext( |
| sdk_dir, overlay_dir, merged_dir, cleanup=True): |
| sysroot = merged_dir[len(chroot_dir):] |
| cmd = [ |
| 'cros_setup_toolchains', '--targets=boards', |
| '--include-boards=%s' % board, |
| '--sysroot=%s' % sysroot |
| ] |
| commands.RunBuildScript( |
| self._build_root, |
| cmd, |
| chromite_cmd=True, |
| enter_chroot=True, |
| sudo=True, |
| extra_env=self._portage_extra_env) |
| |
| # NOTE: Make sure that the overlay directory is owned root:root and has |
| # 0o755 perms; apparently, these things are preserved through |
| # tarring/untarring and might cause havoc if overlooked. |
| os.chmod(overlay_dir, 0o755) |
| cros_build_lib.sudo_run(['chown', 'root:root', overlay_dir]) |
| CreateTarball(overlay_dir, |
| overlay_tarball_template % {'toolchains': toolchains_str}) |
| |
| generated.add(toolchains_str) |
| |
| |
| class SDKTestStage(generic_stages.BuilderStage): |
| """Stage that performs testing an SDK created in a previous stage""" |
| |
| option_name = 'tests' |
| category = constants.PRODUCT_TOOLCHAIN_STAGE |
| |
| def PerformStage(self): |
| new_chroot_dir = 'new-sdk-chroot' |
| tarball_location = os.path.join(self._build_root, SDK_TARBALL_NAME) |
| new_chroot_args = ['--chroot', new_chroot_dir] |
| if self._run.options.chrome_root: |
| new_chroot_args += ['--chrome_root', self._run.options.chrome_root] |
| |
| # Build a new SDK using the provided tarball. |
| chroot_args = new_chroot_args + [ |
| '--download', '--replace', '--nousepkg', '--url', |
| 'file://' + tarball_location |
| ] |
| cros_build_lib.run(['true'], |
| cwd=self._build_root, |
| enter_chroot=True, |
| chroot_args=chroot_args, |
| extra_env=self._portage_extra_env) |
| |
| # Inject the toolchain binpkgs from the previous sdk build. On end user |
| # systems, they'd be fetched from the binpkg mirror, but we don't have one |
| # set up for this local build. |
| pkgdir = os.path.join('var', 'lib', 'portage', 'pkgs') |
| old_pkgdir = os.path.join(self._build_root, constants.DEFAULT_CHROOT_DIR, |
| pkgdir) |
| new_pkgdir = os.path.join(self._build_root, new_chroot_dir, pkgdir) |
| osutils.SafeMakedirs(new_pkgdir, sudo=True) |
| cros_build_lib.sudo_run( |
| ['cp', '-r'] + glob.glob(os.path.join(old_pkgdir, '*')) + [new_pkgdir]) |
| |
| # Now install those toolchains in the new chroot. We skip the chroot |
| # upgrade below which means we need to install the toolchain manually. |
| cmd = [ |
| 'cros_setup_toolchains', '--targets=boards', |
| '--include-boards=%s' % ','.join(self._boards) |
| ] |
| commands.RunBuildScript( |
| self._build_root, |
| cmd, |
| chromite_cmd=True, |
| enter_chroot=True, |
| sudo=True, |
| chroot_args=new_chroot_args, |
| extra_env=self._portage_extra_env) |
| |
| # Build all the boards with the new sdk. |
| for board in self._boards: |
| logging.PrintBuildbotStepText(board) |
| commands.SetupBoard( |
| self._build_root, |
| board, |
| usepkg=True, |
| chroot_upgrade=False, |
| extra_env=self._portage_extra_env, |
| chroot_args=new_chroot_args) |
| commands.Build( |
| self._build_root, |
| board, |
| build_autotest=True, |
| usepkg=False, |
| extra_env=self._portage_extra_env, |
| chroot_args=new_chroot_args, |
| disable_revdep_logic=True) |
| |
| |
| class SDKUprevStage(generic_stages.BuilderStage): |
| """Stage that uprevs SDK version.""" |
| |
| category = constants.PRODUCT_TOOLCHAIN_STAGE |
| |
| def __init__(self, builder_run, buildstore, version=None, **kwargs): |
| super(SDKUprevStage, self).__init__(builder_run, buildstore, **kwargs) |
| self._version = version |
| |
| def PerformStage(self): |
| logging.PrintBuildbotStepText(self._version) |
| |
| if self._run.config.prebuilts == constants.PUBLIC: |
| binhost_conf_dir = constants.PUBLIC_BINHOST_CONF_DIR |
| else: |
| binhost_conf_dir = constants.PRIVATE_BINHOST_CONF_DIR |
| sdk_conf = os.path.join(self._build_root, binhost_conf_dir, 'host', |
| 'sdk_version.conf') |
| |
| tc_path_format = prebuilts.GetToolchainSdkUploadFormat( |
| self._version, |
| prebuilts.GetToolchainSdkPaths(self._build_root)[0][1]) |
| sdk_settings = { |
| 'SDK_LATEST_VERSION': self._version, |
| 'TC_PATH': tc_path_format % { |
| 'version': self._version |
| }, |
| } |
| if self._run.options.publish: |
| upload_prebuilts.RevGitFile( |
| sdk_conf, sdk_settings, dryrun=self._run.options.debug) |
| else: |
| logging.info( |
| 'Not updating sdk_version.conf because publishing was disabled.') |