blob: d9eb5979e8b55d6d0603b356930be831a3e5a833 [file] [log] [blame]
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import attr
from recipe_engine import recipe_api
from recipe_engine.config_types import Path
SDK_GCS_BUCKET = "fuchsia"
@attr.s
class ImageFilePaths(object):
"""Required files from fuchsia image to start FEMU."""
# Recipe API, required
_api = attr.ib(type=recipe_api.RecipeApi)
# Files from fuchsia image
build_args = attr.ib(type=Path, default=None)
kernel_file = attr.ib(type=Path, default=None)
system_fvm = attr.ib(type=Path, default=None)
zircona = attr.ib(type=Path, default=None)
def _exists(self, p):
return p and self._api.path.exists(p)
def _exist(self):
return all(
[
self._exists(self.build_args),
self._exists(self.kernel_file),
self._exists(self.system_fvm),
self._exists(self.zircona),
]
)
def _report_missing(self):
result = []
if not self._exists(self.build_args):
result.append(self.build_args)
if not self._exists(self.kernel_file):
result.append(self.kernel_file)
if not self._exists(self.system_fvm):
result.append(self.system_fvm)
if not self._exists(self.zircona):
result.append(self.zircona)
return result
@attr.s
class PackageFilePaths(object):
# Recipe API, required
_api = attr.ib(type=recipe_api.RecipeApi, init=True)
# Files from fuchsia packages
tar_file = attr.ib(type=Path, default=None)
amber_files = attr.ib(type=Path, default=None)
pm = attr.ib(type=Path, default=None)
def _exists(self, p):
return p and self._api.path.exists(p)
def _exist(self):
return all(
[
self._exists(self.tar_file),
self._exists(self.amber_files),
self._exists(self.pm),
]
)
def _report_missing(self):
result = []
if not self._exists(self.tar_file):
result.append(self.tar_file)
if not self._exists(self.amber_files):
result.append(self.amber_files)
if not self._exists(self.pm):
result.append(self.pm)
return result
class SDKApi(recipe_api.RecipeApi):
"""Downloads Fuchsia SDK files required to start FEMU."""
def __init__(self, *args, **kwargs):
super(SDKApi, self).__init__(*args, **kwargs)
self._image_paths = ImageFilePaths(api=self.m)
self._package_paths = PackageFilePaths(api=self.m)
self._sdk_path = None
self._version = None
def _fetch_sdk(self):
"""Downloads Fuchsia SDK from GCS and untar."""
with self.m.step.nest("ensure sdk"):
with self.m.context(infra_steps=True):
arch = self._select_arch()
# Ensure cache path has the correct permission
cache_path = self.m.buildbucket.builder_cache_path.join(
self.version, "fuchsia_sdk", self.platform_name
)
self.m.file.ensure_directory("init fuchsia_sdk cache", cache_path)
if not self.m.file.listdir(
name="check sdk cache content", source=cache_path, test_data=()
):
# Copy GN image to temp directory
sdk_file = "gn.tar.gz"
local_tmp_root = self.m.path.mkdtemp("fuchsia_sdk_tmp")
self.m.gsutil.download(
src_bucket=SDK_GCS_BUCKET,
src=self.m.path.join(
"development",
self.version,
"sdk",
self.sdk_platform_name,
sdk_file,
),
dest=local_tmp_root,
)
# Extract sdk
self.m.tar.extract(
step_name="extract sdk gz",
path=self.m.path.join(local_tmp_root, sdk_file),
directory=cache_path,
)
# Save cache path
self._sdk_path = cache_path
def _fetch_image(self):
"""Downloads Fuchsia image from GCS. Untar and store the required paths for FEMU."""
with self.m.step.nest("ensure image"):
with self.m.context(infra_steps=True):
image_to_download = self._select_image_to_download()
# Ensure cache path has the correct permission
cache_path = self.m.buildbucket.builder_cache_path.join(
self.version, "fuchsia_image", self.platform_name
)
self.m.file.ensure_directory("init fuchsia_image cache", cache_path)
if not self.m.file.listdir(
name="check image cache content", source=cache_path, test_data=()
):
# Copy image to temp directory
local_tmp_path = self.m.path.mkdtemp("fuchsia_image_tmp")
self.m.gsutil.download(
src_bucket=SDK_GCS_BUCKET,
src=self.m.path.join(
"development", self.version, "images", image_to_download
),
dest=local_tmp_path,
)
# Extract image
self.m.tar.extract(
step_name="extract image tgz",
path=self.m.path.join(local_tmp_path, image_to_download),
directory=cache_path,
)
# Assemble files required for FEMU
for p in self.m.file.listdir(
name="set image files",
source=cache_path,
test_data=(
"buildargs.gn",
"qemu-kernel.kernel",
"zircon-a.zbi",
"zircon-r.zbi",
"storage-sparse.blk",
"storage-full.blk",
),
):
base = self.m.path.basename(p)
if base == "buildargs.gn":
self._image_paths.build_args = p
elif base == "qemu-kernel.kernel":
self._image_paths.kernel_file = p
elif base == "storage-full.blk":
self._image_paths.system_fvm = p
elif base == "zircon-a.zbi":
self._image_paths.zircona = p
def _fetch_packages(self):
with self.m.step.nest("ensure packages"):
with self.m.context(infra_steps=True):
package_to_download = self._select_package_to_download()
# Ensure cache path has the correct permission
cache_path = self.m.buildbucket.builder_cache_path.join(
self.version, "fuchsia_packages", self.platform_name
)
self.m.file.ensure_directory("init fuchsia_packages cache", cache_path)
if not self.m.file.listdir(
name="check packages cache content", source=cache_path, test_data=()
):
# Copy packages to cache directory.
# TODO(yuanzhi) Copy to tmp. We need to keep this in cache because fuchsia_ctl
# used by the flutter team expects packages as .tar.gz input. However, we should
# use fuchsia device controller (FDC) that comes with VDL for FEMU based testing
# eventually.
self.m.gsutil.download(
src_bucket=SDK_GCS_BUCKET,
src=self.m.path.join(
"development", self.version, "packages", package_to_download
),
dest=cache_path,
)
# Extract package, this will produce the following subdirectories:
# |cache_path|
# |__ amber-files
# |__ keys
# |__ repository
# |__ pm
self.m.tar.extract(
step_name="extract package tar.gz",
path=self.m.path.join(cache_path, package_to_download),
directory=cache_path,
)
self._package_paths.tar_file = cache_path.join(package_to_download)
self._package_paths.amber_files = cache_path.join("amber-files")
self._package_paths.pm = cache_path.join("pm")
def authorize_zbi(
self, ssh_key_path, zbi_input_path, zbi_output_path=None, zbi_tool_path=None
):
"""Use zbi tool to extend BootFS with SSH authorization key.
Arguments:
ssh_key_path: path to public ssh key file.
zbi_input_path: path to zircon-a.zbi file.
zbi_output_path: (optional) output path to store extended zbi image file.
if None, we will replace zircon file specified in zbi_input_path
zbi_tool_path: (optional) path to the zbi binary tool.
if None, we will fetch the zbi tool from fuchsia sdk in GCS.
"""
if zbi_tool_path:
self.m.zbi.zbi_path = zbi_tool_path
if not self.m.zbi.zbi_path:
self.m.zbi.zbi_path = self.m.path.join(self.sdk_path, "tools", "zbi")
if not zbi_output_path:
zbi_output_path = zbi_input_path
self.m.zbi.copy_and_extend(
step_name="authorize zbi",
input_image=zbi_input_path,
output_image=zbi_output_path,
manifest={"data/ssh/authorized_keys": ssh_key_path},
)
def _select_package_to_download(self):
"""Maps platform parameters to Package names."""
return "qemu-{arch}.tar.gz".format(arch=self._select_arch())
def _select_image_to_download(self):
"""Maps platform parameters to Image names."""
return "qemu-{arch}.tgz".format(arch=self._select_arch())
def _select_arch(self):
"""Maps platform parameters to SDK names."""
if self.m.platform.arch is "arm" and self.m.platform.bits is 64:
return "arm64"
elif self.m.platform.arch is "intel" and self.m.platform.bits is 64:
return "x64"
raise self.m.step.StepFailure(
"Cannot find supported tools. arch %s, bit %s"
% (self.m.platform.arch, self.m.platform.bits)
)
def get_latest_version(self):
return self.m.gsutil(
"cat", "gs://%s/%s/LATEST_LINUX" % (SDK_GCS_BUCKET, "development")
)
@property
def sdk_platform_name(self):
"""Derives the sdk package platform name, resembles the CIPD platform name."""
name = ""
if self.m.platform.is_linux:
name = "linux-amd64"
elif self.m.platform.is_mac:
name = "mac-amd64"
return name
@property
def platform_name(self):
return "%s_%s_%s" % (
self.m.platform.name,
self.m.platform.arch,
self.m.platform.bits,
)
@property
def version(self):
return self._version
@version.setter
def version(self, value):
self._version = value
@property
def image_paths(self):
"""Downloads and unpacks Fuchsia image files from GCS.
Raises:
StepFailure: When cannot find image files matching host architecture.
StepFailure: When image files do not exist after download and unpack from GCS.
"""
assert self.version
self._fetch_image()
if not self._image_paths._exist():
missing = self._image_paths._report_missing()
ex = self.m.step.StepFailure(
"Image paths do not exist. {missing_paths}".format(
missing_paths=missing
)
)
ex.missing_paths = missing
raise ex
return self._image_paths
@property
def package_paths(self):
"""Downloads and unpacks Fuchsia package files from GCS.
Raises:
StepFailure: When cannot find package files matching host architecture.
StepFailure: When package files do not exist after download and unpack from GCS.
"""
assert self.version
self._fetch_packages()
if not self._package_paths._exist():
missing = self._package_paths._report_missing()
ex = self.m.step.StepFailure(
"Package paths do not exist. {missing_paths}".format(
missing_paths=missing
)
)
ex.missing_paths = missing
raise ex
return self._package_paths
@property
def sdk_path(self):
"""Downloads and unpacks Fuchsia sdk files from GCS.
Raises:
StepFailure: When cannot find sdk files matching host architecture.
"""
assert self.version
self._fetch_sdk()
return self._sdk_path