| # 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. |
| |
| import("//build/compiled_action.gni") |
| import("//build/info/info.gni") |
| import("//build/python/python_action.gni") |
| import("//build/sdk/product_bundle_transfer_manifest.gni") |
| import("//build/security/verifier/scrutiny_verifiers.gni") |
| import("//sdk/config.gni") |
| import("//src/developer/ffx/build/ffx_action.gni") |
| |
| # Generate a product bundle that can be used to flash, emulate, or update a |
| # product onto a Fuchsia target. |
| # |
| # Arguments |
| # partitions (required) |
| # [path] The path to the partitions config. |
| # |
| # system_a (optional) |
| # [path] The path to the images manifest that describes images intended |
| # for slot A. |
| # |
| # system_b (optional) |
| # [path] The path to the images manifest that describes images intended |
| # for slot B. |
| # |
| # system_r (optional) |
| # [path] The path to the images manifest that describes images intended |
| # for slot R. |
| # |
| # partitions_contents (optional) |
| # [list] A list of files referenced in the partitions config that will |
| # be copied into the outputs. |
| # |
| # system_a_contents (optional) |
| # [list] A list of files referenced in the system_a config that will |
| # be copied into the outputs. |
| # |
| # system_b_contents (optional) |
| # [list] A list of files referenced in the system_b config that will |
| # be copied into the outputs. |
| # |
| # system_r_contents (optional) |
| # [list] A list of files referenced in the system_r config that will |
| # be copied into the outputs. |
| # |
| # update (optional) |
| # [scope] If provided, an update package will be built. The scope has |
| # a couple required fields. |
| # |
| # version_file (required) |
| # [path] A file containing the version to put in the update package. |
| # |
| # epoch (required) |
| # [int] A 64-bit integer that sets an anti-rollback epoch for the |
| # update package. See: |
| # https://fuchsia.dev/fuchsia-src/concepts/packages/ota?hl=en#verify-epoch |
| # |
| # virtual_devices (optional; default=[]) |
| # [list] A list of virtual device spec files. |
| # |
| # default_virtual_device (optional) |
| # [string] The name of the default virtual device |
| # |
| # scrutiny_args (optional; default={}) |
| # scrutiny_recovery_args (optional; default={}) |
| # [scope] The scrutiny arguments that are forwarded to scrutiny_verifiers, |
| # which must include: |
| # assembly_image_name |
| # product_assembly_config_label |
| # route_sources_config |
| # static_pkgs_golden |
| # verify_component_resolvers_allowlist |
| # verify_routes_component_tree_config |
| # verify_routes_exceptions_allowlist |
| # verify_routes_exceptions_allowlist_product |
| # zbi_bootfs_filelist_goldens |
| # zbi_bootfs_packages_goldens |
| # zbi_kernel_cmdline_goldens |
| # |
| # skip_scrutiny (optional; default=false) |
| # skip_scrutiny_recovery (optional; default=false) |
| # [bool] Whether to skip running scrutiny verifiers on a particular slot. |
| # These should not be true if the corresponding scrutiny args are supplied. |
| # |
| # delivery_blob_type (optional) |
| # [int] The type of delivery blob the product needs. |
| # |
| template("product_bundle") { |
| assert(defined(invoker.partitions), "Need to define partitions") |
| |
| labels = { |
| hermetic_inputs = "${target_name}_hermetic_inputs" |
| product_bundle = "${target_name}_product_bundle" |
| transfer_manifest = "${target_name}_transfer_manifest" |
| scrutiny = "${target_name}_scrutiny" |
| scrutiny_recovery = "${target_name}_scrutiny_recovery" |
| } |
| |
| files = { |
| hermetic_inputs = "${target_out_dir}/${target_name}_hermetic_inputs" |
| hermetic_inputs_depfile = |
| "${target_out_dir}/${target_name}_hermetic_inputs.d" |
| outdir = "$target_out_dir/$target_name" |
| target_out_dir = target_out_dir |
| product_bundle_manifest = "${outdir}/product_bundle.json" |
| vd_dir = "${outdir}/virtual_devices" |
| transfer_manifest = "$target_out_dir/transfer.json" |
| tuf_keys = "//src/sys/pkg/repositories/devhost/keys" |
| } |
| |
| virtual_devices = [] |
| if (defined(invoker.virtual_devices)) { |
| virtual_devices += invoker.virtual_devices |
| } |
| |
| skip_scrutiny = false |
| if (defined(invoker.skip_scrutiny) && invoker.skip_scrutiny) { |
| assert(!defined(invoker.scrutiny_args), |
| "scrutiny_args should not be supplied when skip_scrutiny=true") |
| skip_scrutiny = invoker.skip_scrutiny |
| } else { |
| assert(defined(invoker.scrutiny_args), "scrutiny_args must be supplied") |
| } |
| |
| skip_scrutiny_recovery = false |
| if (defined(invoker.skip_scrutiny_recovery) && |
| invoker.skip_scrutiny_recovery) { |
| assert( |
| !defined(invoker.scrutiny_recovery_args), |
| "scrutiny_recovery_args should not be supplied when skip_scrutiny_recovery=true") |
| skip_scrutiny_recovery = invoker.skip_scrutiny_recovery |
| } else { |
| assert(defined(invoker.scrutiny_recovery_args), |
| "scrutiny_recovery_args must be supplied") |
| } |
| |
| python_action(labels.hermetic_inputs) { |
| forward_variables_from(invoker, |
| [ |
| "deps", |
| "testonly", |
| "visibility", |
| ]) |
| |
| binary_label = |
| "//build/assembly/scripts:hermetic_inputs_from_assembly_outputs" |
| |
| depfile = files.hermetic_inputs_depfile |
| inputs = [ invoker.partitions ] |
| outputs = [ files.hermetic_inputs ] |
| |
| args = [ |
| "--partitions", |
| rebase_path(invoker.partitions, root_build_dir), |
| "--include-blobs", |
| "--output", |
| rebase_path(files.hermetic_inputs, root_build_dir), |
| "--depfile", |
| rebase_path(files.hermetic_inputs_depfile, root_build_dir), |
| ] |
| |
| if (defined(invoker.system_a) || defined(invoker.system_b) || |
| defined(invoker.system_r)) { |
| args += [ "--system" ] |
| if (defined(invoker.system_a)) { |
| args += [ rebase_path(invoker.system_a, root_build_dir) ] |
| inputs += [ invoker.system_a ] |
| } |
| if (defined(invoker.system_b)) { |
| args += [ rebase_path(invoker.system_b, root_build_dir) ] |
| inputs += [ invoker.system_b ] |
| } |
| if (defined(invoker.system_r)) { |
| args += [ rebase_path(invoker.system_r, root_build_dir) ] |
| inputs += [ invoker.system_r ] |
| } |
| } |
| } |
| |
| ffx_action(labels.product_bundle) { |
| no_output_dir_leaks = false |
| forward_variables_from(invoker, |
| [ |
| "deps", |
| "testonly", |
| "visibility", |
| ]) |
| if (!defined(deps)) { |
| deps = [] |
| } |
| |
| outputs = [] |
| if (defined(invoker.partitions_contents)) { |
| foreach(file, invoker.partitions_contents) { |
| filename = get_path_info(file, "file") |
| outputs += [ "${files.outdir}/partitions/${filename}" ] |
| } |
| } |
| if (defined(invoker.system_a_contents)) { |
| foreach(file, invoker.system_a_contents) { |
| filename = get_path_info(file, "file") |
| outputs += [ "${files.outdir}/system_a/${filename}" ] |
| } |
| } |
| if (defined(invoker.system_b_contents)) { |
| foreach(file, invoker.system_b_contents) { |
| filename = get_path_info(file, "file") |
| outputs += [ "${files.outdir}/system_b/${filename}" ] |
| } |
| } |
| if (defined(invoker.system_r_contents)) { |
| foreach(file, invoker.system_r_contents) { |
| filename = get_path_info(file, "file") |
| outputs += [ "${files.outdir}/system_r/${filename}" ] |
| } |
| } |
| |
| hermetic_inputs_target = ":${labels.hermetic_inputs}" |
| hermetic_inputs_file = files.hermetic_inputs |
| hermetic_action_ignored_prefixes = [ |
| "${files.outdir}/blobs", |
| "${files.outdir}/repository", |
| ] |
| |
| # The target below is generated as a part of the `ffx_tool` action at |
| # `//src/developer/ffx/plugins/product:ffx_product_tool`. See there |
| # for more information. |
| ffx_tool = "//src/developer/ffx/plugins/product:ffx_product_tool" |
| ffx_tool_output_name = "ffx-product" |
| |
| # The sdk_id holds the sdk_version. This value can be set (e.g. for testing) |
| # with `--args sdk_id=1234` on the `fx set` call. |
| # In the future, the product bundles should be versioned independently of |
| # the sdk version. So far they have been the same value. |
| assert(sdk_id != "") |
| product_version = sdk_id |
| product_name = "${build_info_product}.${build_info_board}" |
| args = [ |
| "--config", |
| "product.experimental=true", |
| "product", |
| "create", |
| "--product-version", |
| product_version, |
| "--product-name", |
| product_name, |
| "--partitions", |
| rebase_path(invoker.partitions, root_build_dir), |
| "--out-dir", |
| rebase_path(files.outdir, root_build_dir), |
| "--tuf-keys", |
| rebase_path(files.tuf_keys, root_build_dir), |
| ] |
| |
| outputs += [ files.product_bundle_manifest ] |
| inputs = [ |
| invoker.partitions, |
| "${files.tuf_keys}/root.json", |
| "${files.tuf_keys}/snapshot.json", |
| "${files.tuf_keys}/targets.json", |
| "${files.tuf_keys}/timestamp.json", |
| ] |
| outputs += [ |
| "${files.outdir}/keys/root.json", |
| "${files.outdir}/keys/targets.json", |
| "${files.outdir}/keys/snapshot.json", |
| "${files.outdir}/keys/timestamp.json", |
| ] |
| |
| if (defined(invoker.update)) { |
| update = invoker.update |
| assert(defined(update.version_file), "Need to define update.version_file") |
| assert(defined(update.epoch), "Need to define update.epoch") |
| |
| args += [ |
| "--update-package-version-file", |
| rebase_path(update.version_file, root_build_dir), |
| "--update-package-epoch", |
| update.epoch, |
| ] |
| inputs += [ update.version_file ] |
| } |
| |
| if (defined(invoker.system_a)) { |
| args += [ |
| "--system-a", |
| rebase_path(invoker.system_a, root_build_dir), |
| ] |
| inputs += [ invoker.system_a ] |
| } |
| |
| if (defined(invoker.system_b)) { |
| args += [ |
| "--system-b", |
| rebase_path(invoker.system_b, root_build_dir), |
| ] |
| inputs += [ invoker.system_b ] |
| } |
| |
| if (defined(invoker.system_r)) { |
| args += [ |
| "--system-r", |
| rebase_path(invoker.system_r, root_build_dir), |
| ] |
| inputs += [ invoker.system_r ] |
| } |
| |
| if (virtual_devices != []) { |
| outputs += [ "${files.vd_dir}/manifest.json" ] |
| } |
| foreach(virtual_device, virtual_devices) { |
| args += [ |
| "--virtual-device", |
| rebase_path(virtual_device, root_build_dir), |
| ] |
| inputs += [ virtual_device ] |
| vd_filename = get_path_info(virtual_device, "file") |
| outputs += [ "${files.vd_dir}/${vd_filename}" ] |
| } |
| |
| if (defined(invoker.default_virtual_device)) { |
| args += [ |
| "--recommended-device", |
| invoker.default_virtual_device, |
| ] |
| } |
| |
| if (defined(invoker.delivery_blob_type) && |
| invoker.delivery_blob_type != false) { |
| args += [ |
| "--delivery-blob-type", |
| "${invoker.delivery_blob_type}", |
| ] |
| } |
| } |
| |
| product_bundle_transfer_manifest(labels.transfer_manifest) { |
| forward_variables_from(invoker, |
| [ |
| "testonly", |
| "visibility", |
| ]) |
| product_bundle_target = ":${labels.product_bundle}" |
| output_dir = files.target_out_dir |
| product_bundle_dir = files.outdir |
| |
| # The definition of metadata.product_bundles.transfer_manifest_path |
| # below depends on the following assertion. |
| assert("${output_dir}/transfer.json" == files.transfer_manifest) |
| } |
| |
| if (!skip_scrutiny) { |
| scrutiny_verifiers(labels.scrutiny) { |
| forward_variables_from(invoker, |
| [ |
| "testonly", |
| "visibility", |
| ]) |
| product_bundle = ":${labels.product_bundle}" |
| product_bundle_path = files.outdir |
| forward_variables_from(invoker.scrutiny_args, "*") |
| } |
| } |
| |
| if (!skip_scrutiny_recovery) { |
| scrutiny_verifiers(labels.scrutiny_recovery) { |
| forward_variables_from(invoker, |
| [ |
| "testonly", |
| "visibility", |
| ]) |
| product_bundle = ":${labels.product_bundle}" |
| product_bundle_path = files.outdir |
| recovery = true |
| forward_variables_from(invoker.scrutiny_recovery_args, "*") |
| } |
| } |
| |
| group(target_name) { |
| forward_variables_from(invoker, |
| [ |
| "testonly", |
| "visibility", |
| ]) |
| |
| public_deps = [ |
| ":${labels.product_bundle}", |
| ":${labels.transfer_manifest}", |
| ] |
| if (!skip_scrutiny) { |
| public_deps += [ ":${labels.scrutiny}" ] |
| } |
| if (!skip_scrutiny_recovery) { |
| public_deps += [ ":${labels.scrutiny_recovery}" ] |
| } |
| |
| # This value can be set (e.g. for testing) with `--args sdk_id=1234` on the |
| # `fx set` call. |
| # In the future, the product bundles should be versioned independently of |
| # the sdk version. So far they have been the same value. |
| product_version = sdk_id |
| assert(product_version != "") |
| |
| # A json file will be created by '//build/config/build_api_modules.gni'. |
| metadata = { |
| product_bundles = [ |
| { |
| label = get_label_info(":$target_name", "label_with_toolchain") |
| path = rebase_path(files.outdir, root_build_dir) |
| name = "${build_info_product}.${build_info_board}" |
| product_version = product_version |
| transfer_manifest_path = |
| rebase_path(files.transfer_manifest, root_build_dir) |
| transfer_manifest_url = "file://" + transfer_manifest_path |
| }, |
| ] |
| } |
| } |
| } |