blob: b03d4545cfca0aee99841fc84dcb2442b6f871f3 [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.
# A set of GN templates to expose Ninja-generated output files as inputs
# to bazel_action() targets. The latter run a `bazel build` command in
# the Bazel workspace setup by the platform build, which cannot access
# the Ninja output directory directly.
#
# A bazel_input_xxx() target defines a mapping from Ninja-generated outputs
# to files and Bazel filegroup() targets defined in the special repository
# named @legacy_ninja_build_outputs.
#
# For example, to expose the files generated by an action `foo` as a
# filegroup() with Bazel label @legacy_ninja_build_outputs//:foo_files, one
# can use the following definition:
#
# ```gn
# # Expose all outputs generated by :foo in Bazel.
# bazel_input_resource("foo_files") {
# deps = [ ":foo" ]
# sources = get_target_outputs(":foo")
# outputs = [ "{{source_file_part}}" ]
# }
#
# IMPORTANT: Simply defining a bazel_input_xxx() target is NOT ENOUGH, it must
# ALSO be part of the transitive dependencies listed from one of the lists
# defined in //build/bazel/legacy_ninja_build_outputs.gni
#
# There is no way to enforce that with GN at the moment.
#
# Expose a set of Ninja output files as a corresponding Bazel filegroup()
# of prebuilt input sources for the Bazel build.
#
# This template works like copy() or resource().
#
# ```gn
# # Expose all outputs generated by :foo in Bazel.
# bazel_input_resource("foo_files") {
# deps = [ ":foo" ]
# sources = get_target_outputs(":foo")
# outputs = [ "{{source_file_part}}" ]
# }
#
# ...
#
# Make sure this is reachable from the transitive dependencies listed in
# //build/bazel/legacy_ninja_build_outputs.gni, e.g.:
#
# ```
# gn_labels_for_bazel_inputs += [ "//src/foo:foo_files" ]
# ```
#
# Assuming that :foo generates the following files:
#
# out/default/gen/src/foo/foo.h
# out/default/gen/src/foo/foo.cc
#
# Then this will generate something like that @legacy_ninja_build_outputs/BUILD.bazel:
#
# ```bazel
# filegroup("foo_files") {
# srcs = [
# "foo.h",
# "foo.cc",
# ]
# )
# ```
#
# After creating the symlinks as:
#
# $BAZEL_TOPDIR/output_base/external/legacy_ninja_build_outputs/
# foo.h --> ../../../../../src/foo/foo.h
# foo.cc --> ../../../../../src/foo/foo.cc
#
# Arguments:
# name: (optional)
# Bazel filegroup name for the set of files covered by this target.
# If not specified, defaults to target_name. Note that the filegroup()
# will be part of the top-level BUILD.bazel file generated by the
# build)_inputs_workspace() dependent. This name cannot contain a
# directory separator or colon.
# Type: string
#
# sources
# Required: List of output files that become sources in the Bazel
# workspace. Technically works with Ninja source files as well, but these
# are already exposed to Bazel by default, and don't need to be
# listed in a bazel_input_resource() target. See copy() for details.
# Type: list(file)
#
# outputs
# Required: List of one runtime path. This must be a relative path (no
# leading `/`). It can use placeholders based on $sources; see copy()
# and `gn help source_expansion`.
# Type: list(path)
#
# deps
# Optional: Targets that produce $sources. Any files listed in
# $sources that are produced by the build should be produced by a
# target listed here.
# Type: list(label)
#
template("bazel_input_resource") {
_name = target_name
if (defined(invoker.name)) {
_name = invoker.name
assert(string_replace(_name, "/", "") == _name,
"name cannot contain directory separators: $_name")
assert(string_replace(_name, ":", "") == _name,
"name cannot contain colon: $_name")
}
_sources = rebase_path(invoker.sources, root_build_dir)
_dest = process_file_template(invoker.sources, invoker.outputs)
foreach(dst_path, _dest) {
assert(
rebase_path(dst_path, "//") != dst_path,
"`outputs` in bazel_input_resource() cannot start with /: ${dst_path}")
}
group(target_name) {
forward_variables_from(invoker,
"*",
[
"metadata",
"name",
"outputs",
"sources",
])
metadata = {
# Used by bazel_inputs_manifest() template.
# See its documentation for the metadata schema.
bazel_inputs = [
{
name = _name
sources = _sources
destinations = _dest
gn_label = get_label_info(":$target_name", "label_with_toolchain")
},
]
bazel_inputs_barrier = []
}
}
}
# bazel_input_resource_directory() is used to expose the content of a Ninja
# output directory as a Bazel input filegroup. Instead of passing a list of
# source files, `source_root` source point to a directory whose content will
# be symlinks into a bazel_build_inputs_workspace().
#
# IMPORTANT: This is not hermetic, and can lead to incorrect results during
# incremental builds, unless the directory's content is always cleared
# before the Ninja action that generates its content is run.
#
# For example:
#
# bazel_input_resource_directory"my-generated-resources") {
# source_dir = get_label_info(":resource-generator", "target_gen_dir")
# dest_dir = "data/resources"
# deps = [ ยจ:resource-generator" ]
# }
#
# Parameters
# name: (optional)
# Bazel filegroup name for the set of files covered by this target.
# If not specified, defaults to target_name. Note that the filegroup()
# will be part of the top-level BUILD.bazel file generated by the
# build)_inputs_workspace() dependent. This name cannot contain a
# directory separator or colon.
# Type: string
#
# source_dir
# Required: GN path to the source directory that contains all outputs
# files to be exposed as a Bazel filegroup().
# Type: string(path)
#
# dest_dir
# Required: Destination path where all sources are exposed.
# Cannot start with a "/". Use an empty string to expose files directly
# to the workspace's top-level directory.
# Type: string(path)
#
# deps
# Optional: Targets that produce $sources. Any files listed in
# $sources that are produced by the build should be produced by a
# target listed here.
# Type: list(label)
#
template("bazel_input_resource_directory") {
_name = target_name
if (defined(invoker.name)) {
_name = invoker.name
assert(string_replace(_name, "/", "") == _name,
"name cannot contain directory separators: $_name")
assert(string_replace(_name, ":", "") == _name,
"name cannot contain colon: $_name")
}
assert(defined(invoker.dest_dir), "dest_dir is required")
dest_dir = invoker.dest_dir
if (dest_dir != "") {
assert(rebase_path(dest_dir, "foo") != dest_dir,
"dest_dir cannot start with /: $dest_dir")
assert(dest_dir != "." && dest_dir != ".." &&
string_replace(dest_dir, "./", "") == dest_dir,
"dest_dir cannot contain . or .. path elements: $dest_dir")
# Add trailing directory separator.
dest_dir = string_replace(dest_dir + "//", "//", "/")
}
group(target_name) {
forward_variables_from(invoker,
[
"deps",
"testonly",
"visibility",
])
metadata = {
# Used by bazel_inputs_manifest() template.
bazel_inputs = [
{
name = _name
source_dir = rebase_path(invoker.source_dir, root_build_dir)
dest_dir = dest_dir
gn_label = get_label_info(":$target_name", "label_with_toolchain")
},
]
bazel_inputs_barrier = []
}
}
}
# Generate a manifest file describing the content of the Bazel inputs
# repository that will be used by the Bazel workspace to read Ninja outputs
# as sources / prebuilts.
#
# Args:
# output:
# Path to generated manifest file.
#
# inputs_deps:
# A list of targets, all transitive dependencies which are bazel_input_xxx()
# target will generate one entry in the manifest.
#
template("generate_bazel_inputs_manifest") {
# Generate a single manifest file that collects all bazel_input_xxx()
# resources. Each metadata entry is a scope that describes a single
# Bazel filegroup() target that will appear at the top of the
# auto-generated workspace.
#
# There are two types of entries:
#
# ## REGULAR ENTRIES
#
# These entries expose an explicit list of files, they look like:
#
# name: (required)
# Bazel filegroup name.
# Type: string
#
# destinations: (required)
# List of input files, relative to the top of the generated workspace.
# Each one will appear in the `srcs` list of the corresponding
# filegroup.
#
# sources: (required)
# List of source files for the filegroup. Must have the same
# size as the `destinations` list.
# Type: list of paths relative to the root_build_dir
#
# gn_label:
# GN label of target that defines this entry.
# Type: GN label string
#
# They should generate Bazel targets that look like:
#
# filegroup(
# name = "<name>",
# srcs = [
# <destination_1>,
# <destination_2>,
# ...
# ],
# )
#
# Where <destination_N> is the N-th entry in `destinations`, and will be
# the path to a symlink (in the repository) to the corresponding
# <sources_N> file.
#
# ## DIRECTORY ENTRIES
#
# These entries expose all files under a given output directory as
# a single filegroup() using the glob() function. IMPORTANT: For
# correctness, only use these when it is 100% sure that the content
# of the source directory is re-created properly during incremental
# builds. These look like:
#
# name: (required)
# Bazel filegroup name.
# Type: string
#
# source_dir: (required)
# A source directory path, relative to the Ninja build output
# directory, which will contain all input files for the Bazel
# filegroup().
#
# dest_dir: (required)
# A directory prefix for all input files, relative to the top of
# the generated workspace. This will be a symlink to source_dir,
# and the filegroup() will use a glob(["<dest_dir>/*"]) call to get all
# files in it.
#
# gn_label: (optional)
# GN label of target that defines this entry.
# Type: GN label string
#
# They should generate Bazel targets that look like:
#
# filegroup(
# name = "<name>",
# srcs = glob(["<dest_dir>/**]),
# )
#
# Where <dest_dir> is a repository symlink that points to source_dir.
#
generated_file(target_name) {
forward_variables_from(invoker, [ "testonly" ])
outputs = [ invoker.output ]
data_keys = [ "bazel_inputs" ]
walk_keys = [ "bazel_inputs_barrier" ]
deps = invoker.inputs_deps
output_conversion = "json"
}
}