blob: bfe8c7e864ba0ddba1da9fde1ac92fa3a7cfa43e [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.
"""Module containing SDK stages."""
from __future__ import print_function
import glob
import json
import os
from chromite.cbuildbot import commands
from chromite.cbuildbot import constants
from chromite.cbuildbot.stages import generic_stages
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 retry_util
from chromite.lib import toolchain
# Version of the Manifest file being generated for SDK artifacts. Should be
# incremented for major format changes.
# 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).
# Names of various packaged artifacts.
SDK_TARBALL_NAME = 'built-sdk.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',
def CreateTarball(source_root, tarball_path, exclude_paths=None):
"""Packs |source_root| into |tarball_path|.
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 = ['--exclude=%s/*' % path for path in exclude_paths]
# Options for maximum compression.
extra_env = {'XZ_OPT': '-e9'}
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]
class SDKBuildToolchainsStage(generic_stages.BuilderStage):
"""Stage that builds all the cross-compilers we care about"""
def PerformStage(self):
chroot_location = os.path.join(self._build_root,
# Build the toolchains first. Since we're building & installing the
# compilers, need to run as root.
self.CrosSetupToolchains(['--nousepkg'], sudo=True)
# Create toolchain packages.
def CrosSetupToolchains(self, cmd_args, **kwargs):
"""Wrapper around cros_setup_toolchains to simplify things."""
['cros_setup_toolchains'] + list(cmd_args),
chromite_cmd=True, enter_chroot=True, **kwargs)
def CreateRedistributableToolchains(self, chroot_location):
"""Create the toolchain packages"""
# 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
'--output-dir', os.path.join('/', constants.SDK_TOOLCHAINS_OUTPUT),
], sudo=True)
class SDKPackageStage(generic_stages.BuilderStage):
"""Stage that performs preparing and packaging SDK files"""
def __init__(self, builder_run, version=None, **kwargs):
self.sdk_version = version
super(SDKPackageStage, self).__init__(builder_run, **kwargs)
def PerformStage(self):
tarball_location = os.path.join(self._build_root, SDK_TARBALL_NAME)
chroot_location = os.path.join(self._build_root,
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)
# Create a package manifest for the tarball.
self.CreateManifestFromSDK(board_location, manifest_location)
def CreateManifestFromSDK(self, sdk_path, dest_manifest):
"""Creates a manifest from a given source chroot.
sdk_path: Path to the root of the SDK to describe.
dest_manifest: Path to the manifest that should be generated.
"""'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(buildroot, sdk_tarball, buildbot_uri_log, version,
"""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.
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,
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,
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)
retry_util.RetryException(perf_uploader.PerfUploadingError, 3,
perf_values, platform_name, test_name,
def SendPerfValues(self, sdk_tarball):
"""Generate & upload perf data for the build"""
self._SendPerfValues(self._build_root, sdk_tarball,
self.ConstructDashboardURL(), self.sdk_version,
class SDKPackageToolchainOverlaysStage(generic_stages.BuilderStage):
"""Stage that creates and packages per-board toolchain overlays."""
def __init__(self, builder_run, version=None, **kwargs):
self.sdk_version = version
super(SDKPackageToolchainOverlaysStage, self).__init__(builder_run,
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,
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(
# 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():
toolchains = set(toolchain.GetToolchainsForBoard(board).iterkeys())
except portage_util.MissingOverlayException:
# The board overlay may not exist, e.g. on external builders.
toolchains_str = '-'.join(sorted(toolchains))
if not toolchains.issubset(sdk_toolchains) or toolchains_str in generated:
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,
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,
# 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.SudoRunCommand(['chown', 'root:root', overlay_dir])
overlay_tarball_template % {'toolchains': toolchains_str})
class SDKTestStage(generic_stages.BuilderStage):
"""Stage that performs testing an SDK created in a previous stage"""
option_name = 'tests'
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]
[], cwd=self._build_root, enter_chroot=True, chroot_args=chroot_args,
# 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,
new_pkgdir = os.path.join(self._build_root, new_chroot_dir, pkgdir)
osutils.SafeMakedirs(new_pkgdir, sudo=True)
['cp', '-r'] + glob.glob(os.path.join(old_pkgdir, 'cross-*')) +
# 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,
# Build all the boards with the new sdk.
for board in self._boards:
cmd = ['./setup_board', '--board', board, '--skip_chroot_upgrade']
cmd, cwd=self._build_root, enter_chroot=True,
chroot_args=new_chroot_args, extra_env=self._portage_extra_env)
cmd = ['./build_packages', '--board', board, '--nousepkg',
cros_build_lib.RunCommand(cmd, cwd=self._build_root, enter_chroot=True,