blob: 88560d9edb7a3510bce95f63037a06e88587e985 [file]
# Copyright 2024 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/bazel/bazel_action.gni")
import("//build/packages/prebuilt_package.gni")
# Build several Bazel targets with a single GN > Bazel boundary
# transition. This can be considerably faster than building
# them through separate bazel_build_action() targets, because
# these must always be serialized. Using a bazel_build_group()
# allows Bazel to parallelize more work at the same time,
# and avoids repeating the analysis phase on each call.
#
# However, this also means that *all* Bazel targets are re-built
# it any of a single subtarget's dependency changes.
#
# Technically, this creates N+1 targets in the GN graph, where
# N is the number of items in the `subtargets` list.
#
# The first is a bazel_action() target for the group itself
# that always builds *all* Bazel outputs of *all* subtargets,
# and copies them to a group-specific location (i.e.
# $target_out_dir/bazel_build_group_outputs/ instead of
# $target_out_dir).
#
# The remaining N targets are simple copy() or action() targets
# used to copy a subset of all group outputs to their final
# location under $target_out_dir.
#
# Arguments:
# subtargets: (optional)
# A list of scopes, each one describing the content of a given
# Bazel target to build and where to copy its outputs into the
# Ninja build directory. Each item in the list follows the
# following schema:
#
# bazel_target (required)
# Bazel target label.
#
# gn_target_name (required)
# A GN target name. A copy() or action() target will be created
# with this name to copy a subset of the group's outputs to the
# Ninja output locations specified in {copy,directory,package}_outputs
# lists.
#
# copy_outputs, directory_outputs, package_outputs:
# Same meaning as in `bazel_build_action()`, except
# that {{BAZEL_TARGET_XXX}} expansions are always
# relative to `bazel_target` for the current scope.
#
# deps:
# Usual meaning, but all deps for all subtargets are
# forwarded to the group target.
#
# metadata, visibility:
# Usual meaning, but these values are _not_ forwarded
# to the group target.
#
# driver_packages: (optional)
# A list of scopes, each one describing a driver package that is
# built by Bazel, and should be exported by this build group.
# The package manifest (and contents) will be exported to:
# target_out_dir/package_name/package_manifest.json
# Each item in the list follows the following schema:
#
# bazel_target (required)
# Bazel target label.
#
# package_name (required)
# The name of the package.
#
# gn_target_name (optional)
# The name of the `prebuilt_package()` GN target created for this
# driver package. Defaults to `package_name`.
#
# metadata: (optional)
# Metadata that is _only_ applied to the group target itself, not to
# its sub-targets.
#
# no_sdk: (optional)
# Set this to true to build Bazel artifacts that do not depend on the Fuchsia
# IDK or SDK. This makes these targets available to the GN graph early during
# the build. This flag affects all subtargets at once. Must be true if `host` is true.
# Type: boolean
# Default: false
#
# host: (optional)
# Set this to true to run a Bazel command to build host artifacts.
# This flag affects all subtargets at once. Requires `no_sdk = true`.
# Type: boolean
# Default: false
#
# testonly (optional)
# Usual GN meaning, but applies to each subtarget as well as the group.
#
# visibility (optional)
# Usual GN meaning. A value provided here applies to the group target
# and also to each subtarget that does not have its own visibility definition.
#
template("bazel_build_group") {
_bazel_targets = []
_copy_outputs = []
_deps = []
_build_group_target = target_name
_group_output_dir = "bazel_build_group_outputs"
_copy_outputs = []
_directory_outputs = []
_package_outputs = []
# Collect all the subtargets from all different types together.
_subtargets = []
_package_archives_to_expand = []
if (defined(invoker.subtargets)) {
_subtargets += invoker.subtargets
}
# Construct a subtarget entry for each of the driver subtargets specified
if (defined(invoker.driver_packages)) {
foreach(p, invoker.driver_packages) {
_subtarget = {
}
_subtarget = {
bazel_target = p.bazel_target
gn_target_name = "${p.package_name}.bazel_output"
package_outputs = [
{
package_label = bazel_target
archive = "${p.package_name}.far"
copy_debug_symbols = true
},
]
}
_subtargets += [ _subtarget ]
_prebuilt_package = {
}
_prebuilt_package = {
package_name = p.package_name
if (defined(p.gn_target_name)) {
prebuilt_package_target_name = p.gn_target_name
} else {
prebuilt_package_target_name = package_name
}
bazel_output_action = _subtarget.gn_target_name
# This metadata is used by the check_included_drivers() template.
# It includes the string BAZEL to make it clear that it's not a GN
# target, but a Bazel one.
metadata = {
fuchsia_driver_labels = [ "BAZEL:@${p.bazel_target}" ]
}
}
_package_archives_to_expand += [ _prebuilt_package ]
}
}
foreach(subtarget, _subtargets) {
_bazel_targets += [ subtarget.bazel_target ]
if (defined(subtarget.deps)) {
# Loop over the deps individually, because the += / -= pattern doesn't
# work correctly if used with a list that contains duplicates (the -=
# step will fail with a "trying to remove X, but it wasn't there error")
foreach(dep, subtarget.deps) {
_deps += [ dep ]
_deps -= [ dep ]
_deps += [ dep ]
}
}
if (defined(subtarget.copy_outputs)) {
foreach(output, subtarget.copy_outputs) {
_bazel_target = subtarget.bazel_target
if (defined(output.bazel_target)) {
_bazel_target = output.bazel_target
}
_copy_outputs += [
{
bazel_target = _bazel_target
bazel = output.bazel
ninja = "${_group_output_dir}/${output.ninja}"
},
]
}
}
if (defined(subtarget.directory_outputs)) {
foreach(output, subtarget.directory_outputs) {
_bazel_target = subtarget.bazel_target
if (defined(output.bazel_target)) {
_bazel_target = output.bazel_target
}
_directory_outputs += [
{
bazel_target = _bazel_target
bazel_dir = output.bazel_dir
ninja_dir = "${_group_output_dir}/${output.ninja_dir}"
tracked_files = output.tracked_files
copy_debug_symbols =
defined(output.copy_debug_symbols) && output.copy_debug_symbols
},
]
}
}
if (defined(subtarget.package_outputs)) {
foreach(output, subtarget.package_outputs) {
_package_outputs += [
{
package_label = output.package_label
if (defined(output.archive)) {
archive = "${_group_output_dir}/${output.archive}"
}
if (defined(output.manifest)) {
manifest = "${_group_output_dir}/${output.manifest}"
}
copy_debug_symbols =
defined(output.copy_debug_symbols) && output.copy_debug_symbols
},
]
}
}
}
bazel_action(_build_group_target) {
command = "build"
bazel_targets = _bazel_targets
deps = _deps
copy_outputs = _copy_outputs
directory_outputs = _directory_outputs
package_outputs = _package_outputs
forward_variables_from(
invoker,
"*",
[
# These bazel_action() arguments are forbidden here.
"bazel_targets",
"bazel_inputs",
"command",
"copy_outputs",
"directory_outputs",
"package_outputs",
# These arguments are handled above.
"deps",
# These arguments are specific to this template, and not
# passed to bazel_action().
"subtargets",
])
}
# Create subtarget actions
foreach(subtarget, _subtargets) {
_action_args = []
_action_outputs = []
_action_inputs = []
_hermetic_deps = true
if (defined(subtarget.directory_outputs)) {
foreach(entry, subtarget.directory_outputs) {
_src_dir = "$target_out_dir/${_group_output_dir}/${entry.ninja_dir}"
_dst_dir = "$target_out_dir/${entry.ninja_dir}"
_action_args += [
"--directory",
rebase_path(_src_dir, root_build_dir),
rebase_path(_dst_dir, root_build_dir),
] + entry.tracked_files
_action_outputs += [ "${_dst_dir}" ]
foreach(tracked_file, entry.tracked_files) {
_action_inputs += [ "${_src_dir}/${tracked_file}" ]
_action_outputs += [ "${_dst_dir}/${tracked_file}" ]
}
# This action cannot be hermetic in the presence of directory outputs.
# While it would be possible to modify the script to generate an hermetic_inputs_file,
# this would only work for unknown inputs (e.g SRC_DIR/file), but not unknown
# outputs (e.g. DST_DIR/file).
#
# TODO(digit): Change bazel_action() to use hermetic_action_ignored_prefixes.
_hermetic_deps = false
}
}
_files = []
if (defined(subtarget.copy_outputs)) {
foreach(entry, subtarget.copy_outputs) {
_files += [ entry.ninja ]
}
}
if (defined(subtarget.package_outputs)) {
foreach(entry, subtarget.package_outputs) {
if (defined(entry.archive)) {
_files += [ entry.archive ]
}
if (defined(entry.manifest)) {
_files += [ entry.manifest ]
}
}
}
if (_files != []) {
_action_args += [ "--files" ]
foreach(_file, _files) {
_input = "$target_out_dir/${_group_output_dir}/${_file}"
_output = "$target_out_dir/${_file}"
_action_args += [
rebase_path(_input, root_build_dir),
rebase_path(_output, root_build_dir),
]
_action_inputs += [ _input ]
_action_outputs += [ _output ]
}
}
action(subtarget.gn_target_name) {
deps = [ ":${_build_group_target}" ]
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
if (defined(subtarget.visibility)) {
visibility += subtarget.visibility
}
script = "//build/bazel/scripts/copy_bazel_build_group_outputs.py"
inputs = _action_inputs
outputs = _action_outputs
args = _action_args
hermetic_deps = _hermetic_deps
# Disable leak scanner since this action can copy very large product bundle directories
# (over 6 GiB) with takes dozens of seconds to verify for no practical benefit.
check_for_output_dir_leaks = false
forward_variables_from(subtarget, [ "metadata" ])
}
}
foreach(_prebuilt, _package_archives_to_expand) {
# This holds the label of the driver, so that we know it's been included.
driver_label_metadata_target =
"${_prebuilt.prebuilt_package_target_name}.driver_label_metadata"
group(driver_label_metadata_target) {
metadata = _prebuilt.metadata
}
prebuilt_package(_prebuilt.prebuilt_package_target_name) {
package_name = _prebuilt.package_name
archive = "$target_out_dir/$package_name.far"
deps = [ ":${_prebuilt.bazel_output_action}" ]
# Add the fuchsia driver label metadata such that it can be found correctly by
# check_included_drivers(), while _also_ NOT being found by assert_driver_components(),
# so that both checks work correctly.
deps += [ ":${driver_label_metadata_target}" ]
metadata = {
driver_component_barrier = []
}
}
}
}