blob: 06a8263d08bbf28691ba57f70336aa3b81bfe537 [file] [log] [blame]
# Copyright 2022 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.
"""Rule for creating product bundle for flashing Fuchsia images to target devices."""
load(
":providers.bzl",
"FuchsiaAssemblyConfigInfo",
"FuchsiaProductBundleInfo",
"FuchsiaProductImageInfo",
"FuchsiaScrutinyConfigInfo",
)
def _scrutiny_validation(
ctx,
ffx_tool,
pb_out_dir,
scrutiny_config,
is_recovery = False):
deps = []
ffx_invocation = [
ffx_tool.path,
"scrutiny",
"verify",
]
if is_recovery:
ffx_invocation += ["--recovery"]
deps += [_verify_bootfs_filelist(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.bootfs_files,
scrutiny_config.bootfs_packages,
)]
deps += [_verify_kernel_cmdline(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.kernel_cmdline,
)]
deps += _verify_route_sources(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.routes_config_golden,
)
deps += _verify_component_resolver_allowlist(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.component_resolver_allowlist,
)
deps += _verify_routes(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.component_route_exceptions,
scrutiny_config.component_tree_config,
)
deps += _verify_base_packages(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.base_packages,
)
deps += _verify_structured_config(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
scrutiny_config.structured_config_policy,
)
deps += _extract_structured_config(
ctx,
ffx_tool,
pb_out_dir,
is_recovery,
)
return deps
def _verify_bootfs_filelist(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
zbi_bootfs_filelist_goldens,
zbi_bootfs_packages_goldens):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_bootfs.stamp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"bootfs",
"--product-bundle",
pb_out_dir.path,
]
for golden_file in zbi_bootfs_filelist_goldens:
_ffx_invocation += [
"--golden",
golden_file.path,
]
for golden_package in zbi_bootfs_packages_goldens:
_ffx_invocation += [
"--golden-packages",
golden_package.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir] + zbi_bootfs_filelist_goldens + zbi_bootfs_packages_goldens
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file],
command = "\n".join(script_lines),
progress_message = "Verify Bootfs file list for %s" % ctx.label.name,
)
return stamp_file
def _verify_kernel_cmdline(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
zbi_kernel_cmdline_goldens):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_kernel.stamp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"kernel-cmdline",
"--product-bundle",
pb_out_dir.path,
]
for golden_file in zbi_kernel_cmdline_goldens:
_ffx_invocation += [
"--golden",
golden_file.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir] + zbi_kernel_cmdline_goldens
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file],
command = "\n".join(script_lines),
progress_message = "Verify Kernel Cmdline for %s" % ctx.label.name,
)
return stamp_file
def _verify_route_sources(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
routes_config_golden):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_route.stamp")
tmp_dir = ctx.actions.declare_directory(ctx.label.name + "_route.tmp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"--tmp-dir",
tmp_dir.path,
"route-sources",
"--product-bundle",
pb_out_dir.path,
"--config",
routes_config_golden.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir, routes_config_golden]
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file, tmp_dir],
command = "\n".join(script_lines),
progress_message = "Verify Route sources for %s" % ctx.label.name,
)
return [stamp_file, tmp_dir]
def _verify_component_resolver_allowlist(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
component_resolver_allowlist):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_component_resolver.stamp")
tmp_dir = ctx.actions.declare_directory(ctx.label.name + "_component_resolver.tmp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"--tmp-dir",
tmp_dir.path,
"component-resolvers",
"--product-bundle",
pb_out_dir.path,
"--allowlist",
component_resolver_allowlist.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir, component_resolver_allowlist]
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file, tmp_dir],
command = "\n".join(script_lines),
progress_message = "Verify Component Resolver for %s" % ctx.label.name,
)
return [stamp_file, tmp_dir]
def _verify_routes(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
allow_lists,
component_tree_config):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_routes.stamp")
tmp_dir = ctx.actions.declare_directory(ctx.label.name + "_routes.tmp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"--tmp-dir",
tmp_dir.path,
"routes",
"--product-bundle",
pb_out_dir.path,
]
if component_tree_config:
_ffx_invocation += [
"--component-tree-config",
component_tree_config.path,
]
for allow_list in allow_lists:
_ffx_invocation += [
"--allowlist",
allow_list.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir, component_tree_config] + allow_lists
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file, tmp_dir],
command = "\n".join(script_lines),
progress_message = "Verify Routes for %s" % ctx.label.name,
)
return [stamp_file, tmp_dir]
def _verify_base_packages(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
base_packages):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_static_pkgs.stamp")
tmp_dir = ctx.actions.declare_directory(ctx.label.name + "_static_pkgs.tmp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"--tmp-dir",
tmp_dir.path,
"static-pkgs",
"--product-bundle",
pb_out_dir.path,
"--golden",
base_packages.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir, base_packages]
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file, tmp_dir],
command = "\n".join(script_lines),
progress_message = "Verify Static pkgs for %s" % ctx.label.name,
)
return [stamp_file, tmp_dir]
def _verify_structured_config(
ctx,
ffx_invocation,
ffx_tool,
pb_out_dir,
structured_config_policy):
stamp_file = ctx.actions.declare_file(ctx.label.name + "_structured_config.stamp")
tmp_dir = ctx.actions.declare_directory(ctx.label.name + "_structured_config.tmp")
_ffx_invocation = []
_ffx_invocation.extend(ffx_invocation)
_ffx_invocation += [
"--stamp",
stamp_file.path,
"--tmp-dir",
tmp_dir.path,
"structured-config",
"--product-bundle",
pb_out_dir.path,
"--policy",
structured_config_policy.path,
]
script_lines = [
"set -e",
" ".join(_ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir, structured_config_policy]
ctx.actions.run_shell(
inputs = inputs,
outputs = [stamp_file, tmp_dir],
command = "\n".join(script_lines),
progress_message = "Verify structured config for %s" % ctx.label.name,
)
return [stamp_file, tmp_dir]
def _extract_structured_config(ctx, ffx_tool, pb_out_dir, is_recovery):
structured_config = ctx.actions.declare_file(ctx.label.name + "_structured_config")
depfile = ctx.actions.declare_file(ctx.label.name + "_depfile")
ffx_invocation = [
ffx_tool.path,
"scrutiny",
"extract",
"structured-config",
"--product-bundle",
pb_out_dir.path,
"--output",
structured_config.path,
# These args are currently required, but the outputs are ignored.
"--build-path",
pb_out_dir.path,
"--depfile",
depfile.path,
]
if is_recovery:
ffx_invocation += ["--recovery"]
script_lines = [
"set -e",
" ".join(ffx_invocation),
]
inputs = [ffx_tool, pb_out_dir]
ctx.actions.run_shell(
inputs = inputs,
outputs = [structured_config, depfile],
command = "\n".join(script_lines),
progress_message = "Extract structured config for %s" % ctx.label.name,
)
return [structured_config, depfile]
def _fuchsia_product_bundle_impl(ctx):
partitions_configuration = ctx.attr.partitions_config[FuchsiaAssemblyConfigInfo].config
system_a_out = ctx.attr.product_image[FuchsiaProductImageInfo].images_out
ffx_tool = ctx.toolchains["@rules_fuchsia//fuchsia:toolchain"].ffx
pb_out_dir = ctx.actions.declare_directory(ctx.label.name + "_out")
# Gather all the arguments to pass to ffx.
ffx_invocation = [
"$ORIG_DIR/$FFX",
"--config \"product.experimental=true\"",
"product",
"create",
"--partitions $ORIG_DIR/$PARTITIONS_PATH",
"--system-a $ORIG_DIR/$SYSTEM_A_MANIFEST",
"--out-dir $ORIG_DIR/$OUTDIR",
]
# Gather the environment variables needed in the script.
env = {
"FFX": ffx_tool.path,
"OUTDIR": pb_out_dir.path,
"PARTITIONS_PATH": partitions_configuration.path,
"SYSTEM_A_MANIFEST": system_a_out.path + "/images.json",
"ARTIFACTS_BASE_PATH": ctx.attr.artifacts_base_path,
}
# Gather all the inputs.
inputs = [partitions_configuration] + ctx.files.product_image
# If recovery is supplied, add it to the product bundle.
if ctx.attr.recovery != None:
system_r_out = ctx.attr.recovery[FuchsiaProductImageInfo].images_out
ffx_invocation.append("--system-r $ORIG_DIR/$SYSTEM_R_MANIFEST")
env["SYSTEM_R_MANIFEST"] = system_r_out.path + "/images.json"
inputs.extend(ctx.files.recovery)
# If update info is supplied, add it to the product bundle.
if ctx.file.update_version_file != None:
if ctx.file.repository_keys == None:
fail("Repository keys must be supplied in order to build an update package")
ffx_invocation.extend([
"--update-package-version-file $ORIG_DIR/$UPDATE_VERSION_FILE",
"--update-package-epoch $UPDATE_EPOCH",
])
env["UPDATE_VERSION_FILE"] = ctx.file.update_version_file.path
env["UPDATE_EPOCH"] = ctx.attr.update_epoch
inputs.append(ctx.file.update_version_file)
# If a repository is supplied, add it to the product bundle.
if ctx.file.repository_keys != None:
ffx_invocation.append("--tuf-keys $ORIG_DIR/$REPOKEYS")
env["REPOKEYS"] = ctx.file.repository_keys.path
inputs.append(ctx.file.repository_keys)
script_lines = [
"set -e",
"ORIG_DIR=$(pwd)",
"cd $ARTIFACTS_BASE_PATH",
" ".join(ffx_invocation),
]
if ctx.file.repository_keys != None:
script_lines.append("cp -r $ORIG_DIR/$REPOKEYS $ORIG_DIR/$OUTDIR")
script = "\n".join(script_lines)
ctx.actions.run_shell(
inputs = inputs,
outputs = [pb_out_dir],
command = script,
env = env,
progress_message = "Creating product bundle for %s" % ctx.label.name,
)
deps = [pb_out_dir]
# Scrutiny Validation
if ctx.attr.product_image_scrutiny_config:
product_image_scrutiny_config = ctx.attr.product_image_scrutiny_config[FuchsiaScrutinyConfigInfo]
deps += _scrutiny_validation(ctx, ffx_tool, pb_out_dir, product_image_scrutiny_config)
if ctx.attr.recovery_scrutiny_config:
recovery_scrutiny_config = ctx.attr.recovery_scrutiny_config[FuchsiaScrutinyConfigInfo]
deps += _scrutiny_validation(ctx, ffx_tool, pb_out_dir, recovery_scrutiny_config, True)
return [DefaultInfo(files = depset(direct = deps)), FuchsiaProductBundleInfo(
product_bundle = pb_out_dir,
is_remote = False,
)]
fuchsia_product_bundle = rule(
doc = """Creates pb for flashing Fuchsia images to target devices.""",
implementation = _fuchsia_product_bundle_impl,
toolchains = ["@rules_fuchsia//fuchsia:toolchain"],
attrs = {
"partitions_config": attr.label(
doc = "Partitions config to use, can be a fuchsia_partitions_configuration target, or a JSON file",
allow_single_file = [".json"],
mandatory = True,
),
"product_image": attr.label(
doc = "fuchsia_product_image target to put in slot A",
providers = [FuchsiaProductImageInfo],
mandatory = True,
),
"recovery": attr.label(
doc = "fuchsia_product_image target to put in slot R",
providers = [FuchsiaProductImageInfo],
),
"artifacts_base_path": attr.string(
doc = "Artifacts base directories that items in config files are relative to.",
default = ".",
),
"repository_keys": attr.label(
doc = "Directory of repository keys",
allow_single_file = True,
default = None,
),
"update_version_file": attr.label(
doc = "version file needed to create update package",
allow_single_file = True,
default = None,
),
"update_epoch": attr.string(
doc = "epoch needed to create update package",
default = "1",
),
"product_image_scrutiny_config": attr.label(
doc = "Scrutiny config for slot A",
providers = [FuchsiaScrutinyConfigInfo],
),
"recovery_scrutiny_config": attr.label(
doc = "Scrutiny config for recovery",
providers = [FuchsiaScrutinyConfigInfo],
),
},
)