blob: 75b9ba83506f912b776deb815bc0196c2aad35a3 [file] [log] [blame]
# Copyright 2013 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 SDK stages."""
import glob
import json
import logging
import os
import re
from chromite.cbuildbot import cbuildbot_alerts
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 osutils
from chromite.lib import perf_uploader
from chromite.lib import portage_util
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"
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().__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"
# Cleanup etc/make.conf.board_setup for use in SDK.
self.CleanupMakeConfBoardSetup(board_location)
# 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,
)
def CleanupMakeConfBoardSetup(self, board_location):
"""Cleanup etc/make.conf.board_setup to be usable in the SDK"""
board_setup = os.path.join(board_location, "etc/make.conf.board_setup")
lines = osutils.ReadFile(board_setup).splitlines()
to_remove = re.compile(r"^(ROOT|PKG_CONFIG)=")
lines = [x for x in lines if not to_remove.match(x)]
data = "\n".join(lines) + "\n"
if "/build/" in data:
logging.error("%s content:\n%s", board_setup, data)
raise ValueError("/build/ paths must be cleaned from make.conf")
osutils.WriteFile(board_setup, data, sudo=True)
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:
cbuildbot_alerts.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().__init__(builder_run, buildstore, **kwargs)
self._version = version
def PerformStage(self):
cbuildbot_alerts.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."
)