blob: 9eb0fb15a364a7afc51845ad2d6a87fd062e1982 [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.
import("//build/bazel/bazel_inputs.gni")
import("//build/bazel/bazel_workspace.gni")
import("//build/bazel/legacy_ninja_build_outputs.gni")
import("//build/bazel/logging.gni")
import("//build/bazel/remote_services.gni")
import("//build/config/compiler.gni")
import("//build/config/fuchsia_cxx_version.gni")
import("//build/licenses/generated_licenses_spdx.gni")
declare_args() {
# Suppress Bazel non-error output
# This is now disabled by default since bazel_action()
# uses the //:console pool to print its output directly
# to stdout/stderr during the Ninja build.
bazel_quiet = false
}
# Wrap a Bazel command with a GN action.
# Bazel is invoked in a workspace setup by `//build/bazel:bazel_workspace`.
#
# The caller should pass a list of Bazel target patterns for the
# `bazel build` command, as well as a list Bazel output files relative
# to the workspace's root that will be copied into the
# GN target's target_out_dir by default.
#
# All inputs seen by the Bazel command must be defined in the
# @legacy_ninja_build_outputs Bazel repository, which is populated by
# bazel_input_xxx() targets that must appear as dependencies of the
# //build/bazel:legacy_ninja_build_outputs target.
#
# The bazel-generated files will be copied (possibly hard-linked) into the
# `target_out_dir` matching the GN action for this template call (since
# GN requires that an action's output file be in either target_out_dir
# or target_out_dir).
#
# This also takes a list of Bazel target patterns, since Bazel does not
# support building specific files, only targets. For now it is up to the
# caller to know exactly which files are generated by Bazel for a given
# set of target patterns.
#
# Args:
# command: (required)
# The Bazel command, e.g. `build`, `run`, `test`...
# See: `bazel help`.
# Type: string.
#
# bazel_inputs: (optional)
# List of labels to bazel_input_xxx() targets that define
# the set of Ninja-generated inputs that the Bazel build command will
# use. These must be in the dependency tree of
# `//build/bazel:legacy_ninja_build_outputs`. Note that this is only used to
# ensure that said inputs are properly generated by Ninja before this
# action's command is run. The command will be able to see any input
# files exported by the @legacy_ninja_build_outputs Bazel repository,
# as there is no way to restrict them for now.
# Type: list of GN labels.
#
# bazel_targets:
# A list of Bazel target patterns for the `bazel build` command.
# This list cannot be empty since there is no concept of 'default'
# targets in Bazel. Note target sets (i.e. `...`) are not supported.
#
# Relative target names (e.g. ":foo" or "foo:bar") are supported and
# will be resolved relative to the current directory.
#
# Type: list of Bazel target patterns.
#
# copy_outputs: (optional)
# A list of scopes, where each item describes a Bazel output and a
# corresponding Ninja output location (where the Bazel build artifact
# will be copied).
#
# Each scope uses the following format:
#
# {
# bazel = "<bazel_path>"
# ninja = "<ninja_path>"
# }
#
# See technical note below for details about <bazel_path> and <ninja_path>
#
# Type: list of scopes (cannot be empty)
#
# allow_directory_in_outputs (optional):
# If true, directories are allowed in `copy_outputs`, which will allow
# users to send directories as a whole from Bazel to GN.
#
# NOTE: Using directories as outputs can usually lead to incremental build
# issues in GN/Ninja, because timestamps on directories may not correctly
# represent freshness of its contents. See directory_timestamp_outputs below.
#
# Type: boolean
# Default: false
#
# directory_timestamp_outputs: (optional)
#
# When allow_directory_in_outputs is set, a list of one ore more output
# files inside the output directories. Paths are relative to target_out_dir.
# This is useful to provide Ninja with a file timestamp it can rely on
# (directory timestamps are not usable, and not managed in a consistent way
# across platforms).
#
# IMPORTANT: For incremental build correctness, these timestamps must always
# reflect the freshness of the whole directory content. For example:
#
# bazel_action("foo_product_bundle") {
# command = "build"
# ...
# copy_outputs = [
# {
# bazel = "{{BAZEL_TARGET_OUT_DIR}}/{{BAZEL_TARGET_NAME}}_out"
# ninja = "product_bundle"
# }
# ]
# directory_timestamp_outputs = [
# "product_bundle/product_bundle.json",
# ]
# }
#
# Type: list of paths relative to target_out_dir.
# Default: empty
#
# build_id_directories: (optional)
# When build_id_directories is set the action will copy all of the debug
# symbols in the build-id into the root .build-id directory.
#
# The arguments use the same scopes as those used in copy_outputs which
# means they can use the same string templates.
#
# The tool will look for build directories that have a 2 character name and
# then copy all of the contents. Thus the structure of the directory should
# look like:
# - .stamp (the stamp file for copy_outputs)
# - ab/
# - xyz.debug
# - 12345
# - oe/
# - abcd
#
# IMPORTANT: For incremental build correctness, the build-id directory must
# contain a file name `.stamp` which represent the state of the entire
# build-id directory. The name of this file can be changed via the
# build_id_stamp_name variable if a different value is used.
#
# # Specify where the build ids are
# build_id_directories = [
# {
# bazel = "{{BAZEL_TARGET_OUT_DIR}}/{{BAZEL_TARGET_NAME}}_build_id_dir"
# ninja = "${bazel_build_target}_build_id_dir"
# }
# ]
#
# Type: list of scopes.
# Default: empty
#
# build_id_stamp_name: (optional)
# An optional name to use for the stamp file to look for when tracking
# a build_id_directories entry.
# Type: string
# Default: .stamp
#
# extra_bazel_args: (optional)
# A list extra command line arguments to pass to Bazel.
# Type: list of string.
#
# filter_bazel_info_logs: (optional)
# When true (the default value), INFO level logs are filtered from Bazel.
# INFO level logs from Bazel are usually not interesting during Ninja
# invocations.
# Type: boolean
# Default: true
#
# remote_build: (optional)
# Set this to true to override the global default `enable_bazel_remote_rbe`
# setting which controls whether the underlying bazel invocation
# will build remotely using RBE.
# Type: boolean
# Default: `enable_bazel_remote_rbe` (global)
#
# output_groups: (optional)
# A list of values to pass to the --output_groups flag when calling bazel.
# This value will follow the conventions defined by bazel which can be found
# at https://bazel.build/reference/command-line-reference#flag--output_groups.
# Passing ["foo"] will only build the "foo" group and will omit the default
# set of files. If you want the union of the default set and the "foo" set
# then pass ["+foo"].
# The output_groups flag affects the execution phase and so should only be
# added to commands that execute a build.
# Type: list of string.
#
# include_host_tools_licenses: (optional)
# Set this to true to include the licenses of host tool dependencies in
# @gn_targets//:all_licenses.spdx.json.
# Type: boolean
# Default: false
#
# ignore_license_collection_errors: (optional)
# Set this to true to ignore errors when collecting license requirements
# from dependencies.
# Type: boolean
# Default: false
#
# deps:
# metadata:
# no_output_dir_leaks:
# testonly:
# visibility:
# Usual GN meaning.
#
#
# TECHNICAL NOTE ON BAZEL OUTPUTS:
#
# Bazel places its build artifacts under directories that are specific to
# the Bazel build configuration. In practice, these will be different based
# on the current GN toolchain instance where the bazel_action() target
# is defined.
#
# Because the exact Bazel config directories are hard to predict, Bazel
# creates (or updates) a symlink named `bazel-bin` in the main workspace
# directory. This symlink points to the correct config-specific directory,
# and build artifacts are then reachable as
# "$BAZEL_WORKSPACE/bazel-bin/<bazel_target_package>/<output_files>"
#
# However, every time a bazel_action() action is run, the value of the
# bazel-bin symlink target *may* change, so these bazel output paths cannot
# be considered stable or valid once the action has completed.
#
# Hence Bazel build outputs must be copied to stable locations in the
# Ninja build directory, by the script invoked by bazel_action().
#
# The `copy_outputs` argument is used to list all Bazel outputs and their
# corresponding Ninja output path. It is a list of scopes whose format
# is:
#
# {
# bazel = "<bazel_path>"
# ninja = "<ninja_path>"
# }
#
# Where <bazel_path> is a Bazel output path, relative to the Bazel workspace
# that looks like "bazel-bin/<bazel_target_package>/<output_file>", and
# where <ninja_path> is an output path relative to the `target_out_dir`
# of the current target.
#
# Moreover, the <bazel_path> value supports these special substitution
# expressions:
#
# {{BAZEL_TARGET_NAME}}
# Expands to the target name of the first `bazel_targets` label
#
# {{BAZEL_TARGET_PACKAGE}}
# Expands to the package name of the first `bazel_targets` label
# without a // prefix!
#
# {{BAZEL_TARGET_OUT_DIR}}
# Expands to "bazel-bin/{{BAZEL_TARGET_PACKAGE}}"
#
# {{BAZEL_TARGET_OUT_PATH}}
# Expands to {{BAZEL_TARGET_OUT_DIR}}/{{BAZEL_TARGET_NAME}}.
#
template("bazel_action") {
assert(defined(invoker.command), "command must be defined!")
assert(defined(invoker.bazel_targets), "bazel_targets must be defined!")
assert(invoker.bazel_targets != [], "bazel_targets cannot be empty!")
assert(invoker.copy_outputs != [], "`copy_outputs` cannot be empty")
_needs_dir_copy = false
_bazel_output_list = []
_ninja_output_list = []
_build_id_dirs = []
_copy_ouputs = invoker.copy_outputs
_directory_timestamp_outputs = []
if (defined(invoker.directory_timestamp_outputs)) {
_needs_dir_copy = true
_directory_timestamp_outputs += invoker.directory_timestamp_outputs
}
# Set up our build id directories
_build_id_stamp_name = ".stamp"
if (defined(invoker.build_id_stamp_name)) {
_build_id_stamp_name = invoker.build_id_stamp_name
}
if (defined(invoker.build_id_directories)) {
_needs_dir_copy = true
foreach(entry, invoker.build_id_directories) {
assert(
"$entry" != entry,
"`build_id_directories` items must be scopes, string found: \"$entry\"")
assert(defined(entry.bazel),
"`build_id_directories` scope requires bazel key: $entry")
assert(defined(entry.ninja),
"`build_id_directories` scope requires ninja key: $entry")
_build_id_dirs += [ "${target_out_dir}/${entry.ninja}" ]
_copy_ouputs += [ entry ]
_directory_timestamp_outputs +=
[ "${entry.ninja}/${_build_id_stamp_name}" ]
}
} else {
not_needed([ "_build_id_stamp_name" ])
}
foreach(entry, _copy_ouputs) {
assert("$entry" != entry,
"`copy_outputs` items must be scopes, string found: \"$entry\"")
assert(defined(entry.bazel),
"`copy_outputs` scope requires bazel key: $entry")
_bazel_output_list += [ entry.bazel ]
assert(defined(entry.ninja),
"`copy_outputs` scope requires ninja key: $entry")
_ninja_output_list += [ entry.ninja ]
}
_allow_directory_in_outputs = false
if (defined(invoker.allow_directory_in_outputs)) {
_allow_directory_in_outputs = invoker.allow_directory_in_outputs
}
_num_bazel_outputs = 0
_bazel_outputs = []
# Compute the value of the TARGET_OUT_XXX shortcuts.
_bazel_targets = invoker.bazel_targets
_bazel_target = get_label_info(_bazel_targets[0], "name")
_bazel_target_package =
rebase_path(get_label_info(_bazel_targets[0], "dir"), "//")
_bazel_target_out_dir = "bazel-bin/" + _bazel_target_package
not_needed([
"_bazel_target",
"_bazel_target_package",
"_bazel_target_out_dir",
])
_bazel_outputs = []
foreach(path, _bazel_output_list) {
assert(rebase_path(path, "//") != path,
"bazel output path must be relative: $path")
assert(string_replace("XX$path", "XX-", "") == "XX$path",
"bazel output path cannot begin with dash: $path")
if (string_replace(path, "{{BAZEL_TARGET_", "") != path) {
path = string_replace(path, "{{BAZEL_TARGET_NAME}}", _bazel_target)
path = string_replace(path,
"{{BAZEL_TARGET_PACKAGE}}",
_bazel_target_package)
path = string_replace(path,
"{{BAZEL_TARGET_OUT_DIR}}",
_bazel_target_out_dir)
path = string_replace(path,
"{{BAZEL_TARGET_OUT_PATH}}",
"${_bazel_target_out_dir}/${_bazel_target}")
}
# In case of typo with a substitution (e.g. writing {{BAZEL_TARGET_O_DIR}})
# GN will complain with an error but will not display the problematic
# so use an assert to provide a user-friendly message.
assert(string_replace(path, "{{", "") == path,
"Unsupported substitution in `copy_outputs` path: $path")
_bazel_outputs += [ path ]
_num_bazel_outputs += 1
}
_ninja_outputs = _ninja_output_list
_outputs = []
foreach(path, _ninja_outputs) {
_outputs += [ "${target_out_dir}/${path}" ]
}
# Write the log of build events to a target-specific file.
# See https://bazel.build/remote/bep for details.
# For now, produce in JSON output since we lack a tool to convert from
# binary to other formats in the tree.
_bazel_build_events_log_json =
"$target_out_dir/$target_name.bazel_events.log.json"
_bazel_inputs = []
if (defined(invoker.bazel_inputs)) {
_bazel_inputs = invoker.bazel_inputs
}
_bazel_build_files = []
_bazel_targets = []
foreach(label, invoker.bazel_targets) {
dir = get_label_info(label, "dir")
name = get_label_info(label, "name")
assert(get_path_info(dir, "file") != "...",
"`...` is not supported in bazel_targets")
_bazel_build_files += [ "${dir}/BUILD.bazel" ]
_bazel_targets += [ "${dir}:${name}" ]
}
# A group to depend on all bazel_input_xxx() dependencies.
_bazel_inputs_group_target = "$target_name.bazel_inputs"
group(_bazel_inputs_group_target) {
forward_variables_from(invoker, [ "testonly" ])
deps = _bazel_inputs
if (defined(invoker.deps)) {
deps += invoker.deps
}
visibility = [ ":*" ]
}
# Generate a manifest file that references all transitive bazel_input_xxx()
# dependencies from _bazel_inputs, which will be passed to bazel_action.py
# for verification (and print a human-friendly message on error).
#
# NOTE: Place the manifest file under $target_out_dir because $target_gen_dir
# is cleared explicitly by the //build/bazel/tests/build_action/run_tests.py
# script. It also prevents these file to appear as extraneous differences.
_ninja_inputs_manifest = "$target_out_dir/${target_name}.ninja_inputs.json"
_ninja_inputs_target = "${target_name}.ninja_inputs"
generate_bazel_inputs_manifest(_ninja_inputs_target) {
forward_variables_from(invoker, [ "testonly" ])
output = _ninja_inputs_manifest
inputs_deps = [ ":${_bazel_inputs_group_target}" ]
}
# Generate a manifest file that references all transitive bazel_input_file()
# and bazel_input_directory() dependencies from 'deps' or 'bazel_inputs'.
#
# These are passed to bazel_action.py to populate the @gn_targets external
# repository with symlinks and filegroups().
#
_gn_targets_repository_manifest =
"$target_gen_dir/${target_name}.gn_targets_manifest.json"
_gn_targets_repository_target = "${target_name}.gn_targets"
generate_gn_targets_repository_manifest(_gn_targets_repository_target) {
forward_variables_from(invoker, [ "testonly" ])
output = _gn_targets_repository_manifest
deps = [ ":${_bazel_inputs_group_target}" ]
}
# Generate an SPDX file that contains license information for all
# dependencies of this bazel_action() GN target, this will be exposed
# through the special @gn_targets//all_licenses_spdx_json, which should
# be used in product-specific build rules instead of
# @legacy_ninja_build_outputs//:all_bazel_inputs_licenses_spdx_json
_all_licenses_spdx_target = "${target_name}.all_licenses_spdx"
_all_licenses_spdx_file =
"$target_out_dir/${target_name}.all_licenses.spdx.json"
generated_licenses_spdx(_all_licenses_spdx_target) {
forward_variables_from(invoker, [ "testonly" ])
target = ":${_bazel_inputs_group_target}"
output = _all_licenses_spdx_file
spdx_root_package_name = "Fuchsia"
debug_hints = true
include_host_tools = defined(invoker.include_host_tools_licenses) &&
invoker.include_host_tools_licenses
ignore_collection_errors =
defined(invoker.ignore_license_collection_errors) &&
invoker.ignore_license_collection_errors
visibility = [ ":*" ]
}
action(target_name) {
forward_variables_from(invoker,
[
"deps",
"no_output_dir_leaks",
"testonly",
"visibility",
])
script = "//build/bazel/scripts/bazel_action.py"
# NOTE: Do not add bazel_inputs_manifest to the list of inputs
# because *nothing* should depend on the target that generates
# it. Moreoever, it is testonly=true, which would prevent
# non-testonly bazel_action() from being added to the GN graph.
# This is "safe" because the file is generated at GN gen time.
inputs = [ bazel_launcher ] + _bazel_build_files
inputs += [ _ninja_inputs_manifest ]
depfile = "$target_gen_dir/$target_name.d"
outputs = _outputs
if (_needs_dir_copy) {
assert(
_allow_directory_in_outputs,
"directory_timestamp_outputs is not useful when there are no directory copies (allow_directory_in_outputs=false)")
foreach(path, _directory_timestamp_outputs) {
outputs += [ target_out_dir + "/" + path ]
}
} else {
not_needed([ "_directory_timestamp_outputs" ])
}
_path_mapping = "${target_out_dir}/${target_name}.path_mapping"
outputs += [ _path_mapping ]
args = [
"--bazel-launcher",
rebase_path(bazel_launcher, root_build_dir),
"--workspace-dir",
rebase_path(bazel_workspace_dir, root_build_dir),
"--command",
invoker.command,
"--gn-target-label",
get_label_info(":$target_name", "label_no_toolchain"),
"--legacy-inputs-manifest",
rebase_path(bazel_inputs_manifest, root_build_dir),
"--ninja-inputs-manifest",
rebase_path(_ninja_inputs_manifest, root_build_dir),
"--gn-targets-repository-manifest",
rebase_path(_gn_targets_repository_manifest, root_build_dir),
"--gn-targets-all-licenses-spdx",
rebase_path(_all_licenses_spdx_file, root_build_dir),
"--bazel-outputs",
] + _bazel_outputs + [ "--bazel-targets" ] + _bazel_targets +
[ "--ninja-outputs" ] + rebase_path(_outputs, root_build_dir) +
[ "--build-id-dirs" ] + rebase_path(_build_id_dirs, root_build_dir) +
[
"--depfile",
rebase_path(depfile, root_build_dir),
"--path-mapping",
rebase_path(_path_mapping, root_build_dir),
]
if (_allow_directory_in_outputs) {
args += [ "--allow-directory-in-outputs" ]
}
if (invoker.command == "build") {
outputs += [ _bazel_build_events_log_json ]
args += [
"--bazel-build-events-log-json",
rebase_path(_bazel_build_events_log_json, root_build_dir),
]
}
args += [ "--" ]
# Ensure that the default platform is set correctly when this build action
# is invoked from a Fuchsia or host toolchain. For other ones. Other toolchains
# will inherit the 'default' platforms which may be set to
# //build/bazel/platforms:common in the future, and does not target any
# specific system intentionally.
if (is_host || is_fuchsia) {
args +=
[ "--platforms=//build/bazel/platforms:${current_os}_${current_cpu}" ]
}
_filter_bazel_info_logs = true
if (defined(invoker.filter_bazel_info_logs)) {
_filter_bazel_info_logs = invoker.filter_bazel_info_logs
}
if (bazel_quiet) {
args += [ "--config=quiet" ]
not_needed([ "_filter_bazel_info_logs" ])
} else {
if (_filter_bazel_info_logs) {
# Disable INFO lines and printing results, this makes Bazel output much
# less chatty when invoked from Ninja. https://fxbug.dev/42077198
args += [ "--ui_event_filters=-info" ]
}
# Note that --show_result=0 is not supported by `bazel query`.
if (invoker.command != "query") {
args += [ "--show_result=0" ]
}
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
if (invoker.command == "build") {
# Used by //:bazel_build_events_logs Build API module.
bazel_build_events_log = [
{
gn_label = get_label_info(":$target_name", "label_no_toolchain")
build_events_log =
rebase_path(_bazel_build_events_log_json, root_build_dir)
format = "json"
},
]
}
}
if (bazel_execution_logs) {
args += [ "--config=exec_log" ]
}
_remote_build = enable_bazel_remote_rbe
if (defined(invoker.remote_build)) {
_remote_build = invoker.remote_build
}
if (_remote_build) {
args += [ "--config=remote" ]
}
if (bazel_upload_build_events != "") {
args += [ "--config=$bazel_upload_build_events" ]
}
if (defined(invoker.extra_bazel_args)) {
args += invoker.extra_bazel_args
}
# Below are extra args to Bazel
# Forward GN sanitizer configuration to Bazel
if (is_hwasan) {
args += [ "--features=hwasan" ]
} else if (is_asan) {
args += [ "--features=asan" ]
}
if (is_lsan) {
args += [ "--features=lsan" ]
}
if (is_msan) {
args += [ "--features=msan" ]
}
if (is_tsan) {
args += [ "--features=tsan" ]
}
if (is_ubsan) {
args += [ "--features=ubsan" ]
}
# Only clang toolchain has coverage support in Bazel at the moment.
if (is_clang_coverage) {
args += [ "--features=coverage" ]
}
# Forward GN C/C++ compiler optimization configuration to Bazel
if (optimize == "none" || optimize == "default" ||
optimize == "sanitizer" || optimize == "coverage") {
args += [ "--compilation_mode=fastbuild" ]
} else if (optimize == "debug") {
args += [ "--compilation_mode=dbg" ]
} else if (optimize == "size" || optimize == "speed" ||
optimize == "profile" || optimize == "size_lto") {
args += [ "--compilation_mode=opt" ]
} else {
assert("Unsupported mode optimize = $optimize")
}
# Pass in the output groups if defined
if (defined(invoker.output_groups)) {
foreach(output_group, invoker.output_groups) {
# output groups can be a comma separated list or accumulated from
# multiple --output_groups flags. The logic is easier if we just pass
# multiple flags.
args += [ "--output_groups=${output_group}" ]
}
}
# If the c++ version is 20, we overwrite the copts to use
# c++20 in Bazel too.
if (fuchsia_cxx_version == 20) {
args += [
"--cxxopt=-std=c++20",
"--cxxopt=-Wno-deprecated-this-capture",
"--cxxopt=-Wc++20-compat",
"--cxxopt=-Wno-error=c++20-compat",
]
}
if (!defined(deps)) {
deps = []
}
deps += [
":${_all_licenses_spdx_target}",
":${_gn_targets_repository_target}",
":${_ninja_inputs_target}",
"//build/bazel:generate_main_workspace($default_toolchain)",
]
# Using the console pool prevents concurrent bazel build actions to run
# (otherwise the `bazel-bin` symlink target would become unpredictable
# resulting in erroneous outputs or errors), as well as allow Bazel to
# print its progress on smart terminals when `fx build` is invoked
# from one, which is handy to follow long-lasting commands.
pool = "//:console($default_toolchain)"
# This action definitely cannot be hermetic. Callers should add
# themselves to the //build:non_hermetic_deps visibility list.
hermetic_deps = false
# The build events log contains absolute paths written by Bazel!
no_output_dir_leaks = false
}
}