blob: e28dd5e57e3fb18f0ee97ad9f94063d782d404c4 [file] [log] [blame]
# Copyright 2017 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Build stages specific to factory builds.
Factory builds use a mix of standard stages, and custom stages
related to how build artifacts are generated and published.
"""
import datetime
import json
import logging
import os
import shutil
from chromite.cbuildbot import cbuildbot_alerts
from chromite.cbuildbot import commands
from chromite.cbuildbot.stages import generic_stages
from chromite.cbuildbot.stages import workspace_stages
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import gs
from chromite.lib import osutils
from chromite.lib import path_util
from chromite.lib import portage_util
from chromite.lib import timeout_util
from chromite.utils import gs_urls_util
from chromite.utils import pformat
class UnsafeBuildForPushImage(Exception):
"""Raised if push_image is run against a non-signable build."""
class WorkspaceArchiveBase(
workspace_stages.WorkspaceStageBase,
generic_stages.BoardSpecificBuilderStage,
generic_stages.ArchivingStageMixin,
):
"""Base class for workspace archive stages.
The expectation is that the archive stages will be creating a "branch"
upload that looks like an older style branched infrastructure build would
have generated in addition to a factory branch set of archive results.
"""
BRANCH_NAME = "branch"
@property
def branch_config(self):
"""Uniqify the name across boards."""
if self._run.options.debug:
return "%s-%s-tryjob" % (self._current_board, self.BRANCH_NAME)
else:
return "%s-%s" % (self._current_board, self.BRANCH_NAME)
@property
def branch_version(self):
"""Uniqify the name across boards."""
workspace_version_info = self.GetWorkspaceVersionInfo()
if self._run.options.debug:
build_identifier, _ = self._run.GetCIDBHandle()
build_id = build_identifier.cidb_id
return "R%s-%s-b%s" % (
workspace_version_info.chrome_branch,
workspace_version_info.VersionString(),
build_id,
)
else:
return "R%s-%s" % (
workspace_version_info.chrome_branch,
workspace_version_info.VersionString(),
)
@property
def branch_archive_url(self):
"""Uniqify the name across boards."""
return self.UniqifyArchiveUrl(config_lib.GetSiteParams().ARCHIVE_URL)
def UniqifyArchiveUrl(self, archive_url):
"""Return an archive url unique to the current board.
Args:
archive_url: The base archive URL (e.g. 'chromeos-image-archive').
Returns:
The unique archive URL.
"""
return os.path.join(
archive_url, self.branch_config, self.branch_version
)
def GetBranchArchiveUrls(self):
"""Returns upload URLs for branch artifacts based on artifacts.json."""
upload_urls = [self.branch_archive_url]
artifacts_file = portage_util.ReadOverlayFile(
"scripts/artifacts.json",
board=self._current_board,
buildroot=self._build_root,
)
if artifacts_file is not None:
artifacts_json = json.loads(artifacts_file)
extra_upload_urls = artifacts_json.get("extra_upload_urls", [])
upload_urls += [
self.UniqifyArchiveUrl(url) for url in extra_upload_urls
]
return upload_urls
def UploadBranchArtifact(self, path) -> None:
"""Upload artifacts to the branch build results."""
logging.info("UploadBranchArtifact: %s", path)
with osutils.TempDir(prefix="branch") as tempdir:
artifact_path = os.path.join(
tempdir, "%s/%s" % (self._current_board, os.path.basename(path))
)
logging.info("Rename: %s -> %s", path, artifact_path)
os.mkdir(os.path.join(tempdir, self._current_board))
shutil.copyfile(path, artifact_path)
logging.info("Main artifact from: %s", artifact_path)
self.UploadArtifact(artifact_path, archive=True)
gs_context = gs.GSContext(dry_run=self._run.options.debug_forced)
for url in self.GetBranchArchiveUrls():
logging.info("Uploading branch artifact to %s...", url)
with timeout_util.Timeout(20 * 60):
logging.info("Branch artifact from: %s", path)
gs_context.CopyInto(path, url, parallel=True, recursive=True)
def PushBoardImage(self) -> None:
"""Helper to run push_image against the branch boards artifacts."""
# This helper script is only available on internal manifests currently.
if not self._run.config["internal"]:
raise UnsafeBuildForPushImage(
"Can't use push_image on external builds."
)
logging.info(
"Use pushimage to publish signing artifacts for: %s",
self._current_board,
)
# Push build artifacts to gs://chromeos-releases for signing and
# release. This runs TOT pushimage against the build artifacts for the
# branch.
commands.PushImages(
board=self._current_board,
archive_url=self.branch_archive_url,
dryrun=self._run.options.debug
or not self._run.config["push_image"],
profile=self._run.options.profile or self._run.config["profile"],
sign_types=self._run.config["sign_types"] or [],
buildroot=self._build_root,
)
def CreateBranchMetadataJson(self) -> None:
"""Create/publish the factory build artifact for the current board."""
workspace_version_info = self.GetWorkspaceVersionInfo()
# Use the metadata for the main build, with selected fields modified.
board_metadata = self._run.attrs.metadata.GetDict()
board_metadata["boards"] = [self._current_board]
board_metadata["branch"] = self._run.config.workspace_branch
board_metadata["version_full"] = self.branch_version
board_metadata[
"version_milestone"
] = workspace_version_info.chrome_branch
board_metadata[
"version_platform"
] = workspace_version_info.VersionString()
board_metadata["version"] = {
"platform": workspace_version_info.VersionString(),
"full": self.branch_version,
"milestone": workspace_version_info.chrome_branch,
}
current_time = datetime.datetime.now()
current_time_stamp = cros_build_lib.UserDateTimeFormat(
timeval=current_time
)
# We report the build as passing, since we can't get here if isn't.
board_metadata["status"] = {
"status": "pass",
"summary": "",
"current-time": current_time_stamp,
}
with osutils.TempDir(prefix="metadata") as tempdir:
metadata_path = os.path.join(tempdir, constants.METADATA_JSON)
logging.info("Writing metadata to %s.", metadata_path)
osutils.WriteFile(
metadata_path, pformat.json(board_metadata), atomic=True
)
self.UploadBranchArtifact(metadata_path)
class FactoryArchiveStage(WorkspaceArchiveBase):
"""Generates and publishes factory specific build artifacts."""
BRANCH_NAME = "factory"
def CreateFactoryZip(self) -> None:
"""Create/publish the firmware build artifact for the current board."""
logging.info("Create factory_image.zip")
# TODO: Move this image creation logic back into WorkspaceBuildImages.
factory_install_symlink = None
if "factory_install" in self._run.config["images"]:
alias = commands.BuildFactoryInstallImage(
self._build_root,
self._current_board,
extra_env=self._portage_extra_env,
)
factory_install_symlink = self.GetImageDirSymlink(
alias, self._build_root
)
if self._run.config["factory_install_netboot"]:
commands.MakeNetboot(
self._build_root,
self._current_board,
factory_install_symlink,
)
# Build and upload factory zip if needed.
assert self._run.config["factory_toolkit"]
with osutils.TempDir(prefix="factory_zip") as zip_dir:
filename = commands.BuildFactoryZip(
self._build_root,
self._current_board,
zip_dir,
factory_install_symlink,
self.branch_version,
)
self.UploadBranchArtifact(os.path.join(zip_dir, filename))
def CreateTestImageTar(self) -> None:
"""Create and upload chromiumos_test_image.tar.xz.
This depends on the WorkspaceBuildImage stage having previously created
chromiumos_test_image.bin.
"""
with osutils.TempDir(prefix="test_image_dir") as tempdir:
tarball_path = os.path.join(tempdir, constants.TEST_IMAGE_TAR)
cros_build_lib.CreateTarball(
tarball_path,
inputs=[constants.TEST_IMAGE_BIN],
cwd=self.GetImageDirSymlink(
pointer="latest", buildroot=self._build_root
),
compression=cros_build_lib.CompressionType.XZ,
)
self.UploadBranchArtifact(tarball_path)
def CreateFactoryProjectToolkitsZip(self) -> None:
"""Create/publish the factory project toolkits for the current board."""
toolkits_src_path = os.path.join(
path_util.FromChrootPath(
commands.FACTORY_PACKAGE_CHROOT_PATH
% {"board": self._current_board},
source_path=self._build_root,
),
"project_toolkits",
commands.FACTORY_PROJECT_PACKAGE,
)
if os.path.exists(toolkits_src_path):
self.UploadBranchArtifact(toolkits_src_path)
def BuildAutotestTarballs(self) -> None:
"""Build the autotest tarballs."""
with osutils.TempDir(prefix="cbuildbot-autotest") as tempdir:
cwd = os.path.abspath(
path_util.FromChrootPath(
os.path.join(
os.path.sep,
"build",
self._current_board,
constants.AUTOTEST_BUILD_PATH,
"..",
),
source_path=self._build_root,
)
)
logging.debug(
"Running BuildAutotestTarballsForHWTest root %s cwd %s"
" target %s",
self._build_root,
cwd,
tempdir,
)
for tarball in commands.BuildAutotestTarballsForHWTest(
self._build_root, cwd, tempdir
):
self.UploadBranchArtifact(tarball)
def BuildTastTarball(self) -> None:
"""Build the tarball containing private Tast test bundles."""
with osutils.TempDir(prefix="cbuildbot-tast") as tempdir:
cwd = os.path.abspath(
path_util.FromChrootPath(
os.path.join(
os.path.sep,
"build",
self._current_board,
"build",
),
source_path=self._build_root,
)
)
logging.debug("Running commands.BuildTastBundleTarball")
tarball = commands.BuildTastBundleTarball(
self._build_root, cwd, tempdir
)
if tarball:
self.UploadBranchArtifact(tarball)
def PerformStage(self) -> None:
"""Archive and publish the factory build artifacts."""
logging.info("Factory version: %s", self.branch_version)
logging.info("Archive build as: %s", self.branch_config)
# Link branch build artifacts from build.
branch_http_url = gs_urls_util.GsUrlToHttp(
self.branch_archive_url, public=False, directory=True
)
label = "%s factory [%s]" % (self._current_board, self.branch_version)
cbuildbot_alerts.PrintBuildbotLink(label, branch_http_url)
# factory_image.zip
self.CreateFactoryZip()
self.CreateFactoryProjectToolkitsZip()
self.CreateTestImageTar()
self.CreateBranchMetadataJson()
self.PushBoardImage()
# Upload any needed HWTest artifacts.
if (
self._run.ShouldBuildAutotest()
and self._run.config.upload_hw_test_artifacts
):
self.BuildAutotestTarballs()
self.BuildTastTarball()