blob: b4b653ca25c3a469b65691d5a101b230d608fae0 [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 the Chrome stages."""
import glob
import logging
import multiprocessing
import os
import shutil
from chromite.cbuildbot import cbuildbot_alerts
from chromite.cbuildbot import commands
from chromite.cbuildbot import manifest_version
from chromite.cbuildbot.stages import artifact_stages
from chromite.cbuildbot.stages import generic_stages
from chromite.cbuildbot.stages import sync_stages
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import failures_lib
from chromite.lib import goma_lib
from chromite.lib import osutils
from chromite.lib import parallel
from chromite.lib import path_util
from chromite.lib import portage_util
from chromite.lib import results_lib
MASK_CHANGES_ERROR_SNIPPET = "The following mask changes are necessary"
CHROMEPIN_MASK_PATH = os.path.join(
constants.SOURCE_ROOT,
constants.CHROMIUMOS_OVERLAY_DIR,
"profiles",
"default",
"linux",
"package.mask",
"chromepin",
)
class SyncChromeStage(
generic_stages.BuilderStage, generic_stages.ArchivingStageMixin
):
"""Stage that syncs Chrome sources if needed."""
option_name = "managed_chrome"
category = constants.PRODUCT_CHROME_STAGE
def __init__(self, builder_run, buildstore, **kwargs):
super().__init__(builder_run, buildstore, **kwargs)
# PerformStage() will fill this out for us.
# TODO(mtennant): Replace with a run param.
self.chrome_version = None
def HandleSkip(self):
"""Set run.attrs.chrome_version to chrome version in buildroot now."""
self._run.attrs.chrome_version = self._run.DetermineChromeVersion()
logging.debug(
"Existing chrome version is %s.", self._run.attrs.chrome_version
)
self._WriteChromeVersionToMetadata()
super().HandleSkip()
def _GetChromeVersionFromMetadata(self):
"""Return the Chrome version from metadata; None if is does not exist."""
version_dict = self._run.attrs.metadata.GetDict().get("version")
return None if not version_dict else version_dict.get("chrome")
@failures_lib.SetFailureType(failures_lib.InfrastructureFailure)
def PerformStage(self):
chrome_atom_to_build = None
if self._chrome_rev:
if (
self._chrome_rev == constants.CHROME_REV_SPEC
and self._run.options.chrome_version
):
self.chrome_version = self._run.options.chrome_version
logging.info(
"Using chrome version from options.chrome_version: %s",
self.chrome_version,
)
else:
self.chrome_version = self._GetChromeVersionFromMetadata()
if self.chrome_version:
logging.info(
"Using chrome version from the metadata dictionary: %s",
self.chrome_version,
)
# Perform chrome uprev.
try:
chrome_atom_to_build = commands.MarkChromeAsStable(
self._build_root,
self._run.manifest_branch,
self._chrome_rev,
self._boards,
chrome_version=self.chrome_version,
)
except commands.ChromeIsPinnedUprevError as e:
# If uprev failed due to a chrome pin, record that failure (so that the
# build ultimately fails) but try again without the pin, to allow the
# slave to test the newer chrome anyway).
chrome_atom_to_build = e.new_chrome_atom
if chrome_atom_to_build:
results_lib.Results.Record(self.name, e)
cbuildbot_alerts.PrintBuildbotStepFailure()
logging.error(
"Chrome is pinned. Unpinning chrome and continuing "
"build for chrome atom %s. This stage will be marked "
"as failed to prevent an uprev.",
chrome_atom_to_build,
)
logging.info(
"Deleting pin file at %s and proceeding.",
CHROMEPIN_MASK_PATH,
)
osutils.SafeUnlink(CHROMEPIN_MASK_PATH)
else:
raise
kwargs = {}
if self._chrome_rev == constants.CHROME_REV_SPEC:
kwargs["revision"] = self.chrome_version
cbuildbot_alerts.PrintBuildbotStepText(
"revision %s" % kwargs["revision"]
)
else:
if not self.chrome_version:
self.chrome_version = self._run.DetermineChromeVersion()
kwargs["tag"] = self.chrome_version
cbuildbot_alerts.PrintBuildbotStepText("tag %s" % kwargs["tag"])
useflags = self._run.config.useflags
git_cache_dir = (
self._run.options.chrome_preload_dir
or self._run.options.git_cache_dir
)
commands.SyncChrome(
self._build_root,
self._run.options.chrome_root,
useflags,
git_cache_dir=git_cache_dir,
**kwargs,
)
def _WriteChromeVersionToMetadata(self):
"""Write chrome version to metadata and upload partial json file."""
self._run.attrs.metadata.UpdateKeyDictWithDict(
"version", {"chrome": self._run.attrs.chrome_version}
)
self.UploadMetadata(filename=constants.PARTIAL_METADATA_JSON)
def Finish(self):
"""Provide chrome_version to the rest of the run."""
# Even if the stage failed, a None value for chrome_version still
# means something. In other words, this stage tried to run.
self._run.attrs.chrome_version = self.chrome_version
self._WriteChromeVersionToMetadata()
super().Finish()
class SimpleChromeArtifactsStage(
generic_stages.BoardSpecificBuilderStage, generic_stages.ArchivingStageMixin
):
"""Archive Simple Chrome artifacts."""
option_name = "chrome_sdk"
config_name = "chrome_sdk"
category = constants.PRODUCT_CHROME_STAGE
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._upload_queue = multiprocessing.Queue()
self._pkg_dir = os.path.join(
self._build_root,
constants.DEFAULT_CHROOT_DIR,
"build",
self._current_board,
portage_util.VDB_PATH,
)
def _BuildAndArchiveChromeSysroot(self):
"""Generate and upload sysroot for building Chrome."""
assert self.archive_path.startswith(self._build_root)
extra_env = {}
if self._run.config.useflags:
extra_env["USE"] = " ".join(self._run.config.useflags)
in_chroot_path = path_util.ToChrootPath(self.archive_path)
cmd = [
"cros_generate_sysroot",
"--out-dir",
in_chroot_path,
"--board",
self._current_board,
"--deps-only",
"--package",
constants.CHROME_CP,
]
cros_build_lib.run(
cmd, cwd=self._build_root, enter_chroot=True, extra_env=extra_env
)
self._upload_queue.put([constants.CHROME_SYSROOT_TAR])
def _ArchiveChromeEbuildEnv(self):
"""Generate and upload Chrome ebuild environment."""
files = glob.glob(
os.path.join(self._pkg_dir, constants.CHROME_CP) + "-*"
)
if not files:
raise artifact_stages.NothingToArchiveException(
"Failed to find package %s" % constants.CHROME_CP
)
if len(files) > 1:
cbuildbot_alerts.PrintBuildbotStepWarnings()
logging.warning(
"Expected one package for %s, found %d",
constants.CHROME_CP,
len(files),
)
chrome_dir = sorted(files)[-1]
env_bzip = os.path.join(chrome_dir, "environment.bz2")
with osutils.TempDir(prefix="chrome-sdk-stage") as tempdir:
# Convert from bzip2 to tar format.
bzip2 = cros_build_lib.FindCompressor(
cros_build_lib.CompressionType.BZIP2
)
cros_build_lib.run(
[bzip2, "-d", env_bzip, "-c"],
stdout=os.path.join(tempdir, constants.CHROME_ENV_FILE),
)
env_tar = os.path.join(self.archive_path, constants.CHROME_ENV_TAR)
cros_build_lib.CreateTarball(env_tar, tempdir)
self._upload_queue.put([os.path.basename(env_tar)])
def _GenerateAndUploadMetadata(self):
self.UploadMetadata(
upload_queue=self._upload_queue,
filename=constants.PARTIAL_METADATA_JSON,
)
def PerformStage(self):
steps = [
self._BuildAndArchiveChromeSysroot,
self._ArchiveChromeEbuildEnv,
self._GenerateAndUploadMetadata,
]
with self.ArtifactUploader(self._upload_queue, archive=False):
parallel.RunParallelSteps(steps)
if (
self._run.config.chrome_sdk_build_chrome
and config_lib.IsCanaryMaster(self._run)
):
test_stage = TestSimpleChromeWorkflowStage(
self._run, self.buildstore, self._current_board
)
test_stage.Run()
class TestSimpleChromeWorkflowStage(
generic_stages.BoardSpecificBuilderStage, generic_stages.ArchivingStageMixin
):
"""Run through the simple chrome workflow."""
category = constants.PRODUCT_CHROME_STAGE
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._run.options.chrome_root:
self.chrome_src = os.path.join(self._run.options.chrome_root, "src")
board_dir = "out_%s" % self._current_board
self.out_board_dir = os.path.join(
self.chrome_src, board_dir, "Release"
)
def _VerifyChromeDeployed(self, tempdir):
"""Check to make sure deploy_chrome ran correctly."""
if not os.path.exists(os.path.join(tempdir, "chrome")):
raise AssertionError("deploy_chrome did not run successfully!")
def _VerifySDKEnvironment(self):
"""Make sure the SDK environment is set up properly."""
# If the environment wasn't set up, then the output directory wouldn't be
# created after 'gn gen'.
# TODO: Make this check actually look at the environment.
if not os.path.exists(self.out_board_dir):
raise AssertionError("%s not created!" % self.out_board_dir)
# Log args.gn for debugging.
logging.info(
"ARGS.GN=\n%s",
osutils.ReadFile(os.path.join(self.out_board_dir, "args.gn")),
)
def _ShouldEnableGoma(self):
# Enable goma if 1) Chrome actually needs to be built, 2) goma is available
# and 3) config says goma should be used to build Chrome.
return (
self._run.options.managed_chrome
and self._run.options.goma_dir
and self._run.config.chrome_sdk_goma
)
def _BuildChrome(self, sdk_cmd, goma):
"""Use the generated SDK to build Chrome."""
# Validate fetching of the SDK and setting everything up.
sdk_cmd.Run(["true"])
sdk_cmd.Run(["gclient", "runhooks"])
# Generate args.gn and ninja files.
gn_cmd = os.path.join(self.chrome_src, "buildtools", "linux64", "gn")
gn_gen_cmd = '%s gen "%s" --args="$GN_ARGS"' % (
gn_cmd,
self.out_board_dir,
)
sdk_cmd.Run(["bash", "-c", gn_gen_cmd])
self._VerifySDKEnvironment()
if goma:
# If goma is enabled, start goma compiler_proxy here, and record
# several information just before building Chrome is started.
goma.Start()
extra_env = goma.GetExtraEnv()
ninja_env_path = os.path.join(goma.goma_log_dir, "ninja_env")
sdk_cmd.Run(
["env", "--null"],
run_args={"extra_env": extra_env, "stdout": ninja_env_path},
)
osutils.WriteFile(
os.path.join(goma.goma_log_dir, "ninja_cwd"), sdk_cmd.cwd
)
osutils.WriteFile(
os.path.join(goma.goma_log_dir, "ninja_command"),
cros_build_lib.CmdToStr(sdk_cmd.GetNinjaCommand()),
)
else:
extra_env = None
result = None
try:
# Build chromium.
result = sdk_cmd.Ninja(run_args={"extra_env": extra_env})
finally:
# In teardown, if goma is enabled, stop the goma compiler proxy,
# and record/copy some information to log directory, which will be
# uploaded to the goma's server in a later stage.
if goma:
goma.Stop()
ninja_log_path = os.path.join(
self.chrome_src, sdk_cmd.GetNinjaLogPath()
)
if os.path.exists(ninja_log_path):
shutil.copy2(
ninja_log_path,
os.path.join(goma.goma_log_dir, "ninja_log"),
)
if result:
osutils.WriteFile(
os.path.join(goma.goma_log_dir, "ninja_exit"),
str(result.returncode),
)
def _TestDeploy(self, sdk_cmd):
"""Test SDK deployment."""
with osutils.TempDir(prefix="chrome-sdk-stage") as tempdir:
# Use the TOT deploy_chrome.
script_path = os.path.join(
self._build_root, constants.CHROMITE_BIN_SUBDIR, "deploy_chrome"
)
sdk_cmd.Run(
[
script_path,
"--build-dir",
self.out_board_dir,
"--staging-only",
"--staging-dir",
tempdir,
]
)
self._VerifyChromeDeployed(tempdir)
def _VMTest(self, sdk_cmd):
"""Run cros_run_test."""
image_path = os.path.join(
self.GetImageDirSymlink(), constants.TEST_IMAGE_BIN
)
# Run VM test for boards where we've built a VM.
if image_path and os.path.exists(image_path):
sdk_cmd.VMTest(image_path)
def PerformStage(self):
with osutils.TempDir(prefix="chrome-sdk-cache") as tempdir:
cache_dir = os.path.join(tempdir, "cache")
extra_args = [
"--cwd",
self.chrome_src,
"--sdk-path",
self.archive_path,
]
# Do not automatically run 'gn gen', that will be done in _BuildChrome.
extra_args.extend(["--nogn-gen"])
if self._ShouldEnableGoma():
# TODO(crbug.com/751010): Revisit to enable DepsCache for
# non-chrome-pfq bots, too.
use_goma_deps_cache = self._run.config.name.endswith(
"chrome-pfq"
)
goma = goma_lib.Goma(
self._run.options.goma_dir,
self._run.options.goma_client_json,
stage_name=self.StageNamePrefix()
if use_goma_deps_cache
else None,
chromeos_goma_dir=self._run.options.chromeos_goma_dir,
)
extra_args.extend(
["--nostart-goma", "--gomadir", str(goma.linux_goma_dir)]
)
self._run.attrs.metadata.UpdateWithDict(
{"goma_tmp_dir_for_simple_chrome": str(goma.goma_tmp_dir)}
)
else:
goma = None
if constants.USE_CHROME_INTERNAL in self._run.config.useflags:
extra_args.extend(["--internal"])
sdk_cmd = commands.ChromeSDK(
self._build_root,
self._current_board,
chrome_src=self.chrome_src,
goma=bool(goma),
extra_args=extra_args,
cache_dir=cache_dir,
)
self._BuildChrome(sdk_cmd, goma)
self._TestDeploy(sdk_cmd)
self._VMTest(sdk_cmd)
class ChromeLKGMSyncStage(sync_stages.SyncStage):
"""Stage that syncs to the last known good manifest for Chrome."""
output_manifest_sha1 = False
category = constants.PRODUCT_CHROME_STAGE
def GetNextManifest(self):
"""Override: Gets the LKGM from the Chrome tree."""
chrome_lkgm = commands.GetChromeLKGM(self._run.options.chrome_version)
# We need a full buildspecs manager here as we need an initialized manifest
# manager with paths to the spec.
# TODO(mtennant): Consider registering as manifest_manager run param, for
# consistency, but be careful that consumers do not get confused.
# Currently only the "manifest_manager" from ManifestVersionedSync (and
# subclasses) is used later in the flow.
manifest_manager = manifest_version.BuildSpecsManager(
source_repo=self.repo,
manifest_repo=self._GetManifestVersionsRepoUrl(),
build_names=self._run.GetBuilderIds(),
incr_type="build",
force=False,
branch=self._run.manifest_branch,
buildstore=self.buildstore,
)
manifest_manager.BootstrapFromVersion(chrome_lkgm)
return manifest_manager.GetLocalManifest(chrome_lkgm)