| # 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) |