blob: befb71b8bf446957df58826b2252c606f47a41c3 [file] [log] [blame]
# Copyright 2018 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/cpp/verify_pragma_once.gni")
import("//build/cpp/verify_public_headers.gni")
import("//build/sdk/plasa/config.gni")
import("//build/sdk/plasa/plasa_fragment_cc.gni")
import("//build/sdk/sdk_atom.gni")
# A source set that can be exported to an SDK.
#
# An equivalent to the built-in source_set which adds an SDK atom declaration to
# allow the set to be included in an SDK as sources.
#
# The SDK atom target (`${target_name}_sdk) does NOT build the underlying
# `source_set` (`target_name`).
# To provide build coverage, ensure some other target in the build graph
# depends on `target_name`.
#
# Parameters
#
# category (required)
# Publication level of the library in SDKs.
# See //build/sdk/sdk_atom.gni.
#
# sdk_area (optional)
# [string] The API area responsible for maintaining this library.
# See //build/sdk/sdk_atom.gni.
#
# api (optional)
# Override path for the file representing the API of this library.
# This file is used to ensure modifications to the library's API are
# explicitly acknowledged.
# If not specified, the path will be "<sdk_name>.api".
# Not allowed when `stable` is false.
#
# sdk_name (required for non-internal libraries)
# Name of the library in the SDK.
#
# source_dir (optional)
# If set, path to the base directory of the sources.
# This is useful if the sources are generated and therefore not hosted
# directly under the directory where the GN rules are declared.
#
# include_base (optional)
# Path to the root directory for includes.
# Defaults to "include".
#
# build_as_static (optional)
# Whether the sources should be exposed as a static library.
# This is mostly used in the transition of Zircon libraries to the GN build.
# Defaults to false.
#
# non_sdk_deps (optional)
# List of dependencies that should not be reflected in SDKs.
# Mostly useful for code generation.
#
# non_sdk_public_deps (optional)
# List of dependencies that will go into `public_deps` field of `source_set`
# target.
#
# non_sdk_local_deps (optional)
# List of dependencies that will go into `deps` field of `source_set`
# target.
#
# sdk_headers_for_internal_use (optional)
# Out of the headers specified in `public` or `sources`, some headers are
# part of the SDK but not meant for direct inclusion by users, i.e. they are
# only transitively included by other public headers. They usually contain
# implementation details. Re-specify those headers here.
#
# When enumerating the platform surface area (PlaSA), these headers will
# be excluded. See /build/sdk/plasa/plasa_fragment_cc.gni.
#
# See https://fxbug.dev/42068255 for more details about this field.
#
# stable (optional)
# Whether this source library is stabilized.
# When true, an .api file is generated. When false, the atom is marked as
# unstable in the final IDK.
# Must be specified when `sdk_category` is defined and not "internal";
# otherwise, must not be specified.
#
template("sdk_source_set") {
assert(defined(invoker.category), "Must define an SDK category")
# Only categories that are included in an IDK makes sense.
valid_categories = [
# "internal" is deprecated; only specific legacy cases below are allowed.
# "compat_test" is only for ABI compatibility and thus not applicable.
# "partner_internal" is only for ABI compatibility and thus not applicable.
"partner",
]
# TODO(https://fxbug.dev/372986936): Remove once all uses have been removed.
is_internal = invoker.category == "internal"
# Atoms for the Firmware SDK, which is not a real IDK, use this template,
# but none of the supported categories are appropriate. Specifically, this
# template contains special logic related to the Bazel SDK for "internal"
# category (based on `is_internal`). Thus, allow "firmware_sdk" in this
# template then convert it to "internal" when defining the `sdk_atom()`.
# TODO(https://fxbug.dev/331991540): Remove once "firmware_sdk" is obsolete.
make_internal_atom = is_internal || invoker.category == "firmware_sdk"
assert(
valid_categories + [ invoker.category ] - [ invoker.category ] !=
valid_categories || make_internal_atom,
"'${target_name}' has unsupported SDK category '${invoker.category}'. Must be one of ${valid_categories}.")
# Internal source sets may also specify an sdk_name.
assert(
defined(invoker.sdk_name) || make_internal_atom,
"Source sets in the IDK must specify a name that is meaningful in that context.")
if (defined(invoker.sdk_name)) {
sdk_name = invoker.sdk_name
sdk_root_path = "pkg/${sdk_name}"
} else {
# if not defined by the invoker, the sdk_name is the fully qualified target name with some tweaks. For example:
# //src/lib/fxl:fxl sdk_name = //src/lib/fxl_fxl sdk_root_path= src/lib/fxl_fxl
# //src/lib/fxl:functional sdk_name = //src/lib/fxl_functional sdk_root_path= src/lib/fxl_functional
# //src/lib/fxl/files:files sdk_name = //src/lib/fxl/files_files sdk_root_path= src/lib/fxl/files_files
full_label = get_label_info(":${invoker.target_name}", "label_no_toolchain")
path = get_label_info(full_label, "dir")
path = "${path}_${invoker.target_name}"
sdk_name = path
sdk_root_path = string_replace(path, "//", "")
}
sdk_id = "sdk://${sdk_root_path}"
if (make_internal_atom) {
assert(!defined(invoker.stable) || !invoker.stable)
is_stable = false
} else {
assert(defined(invoker.stable), "Must specify stability.")
is_stable = invoker.stable
}
assert(!is_stable || invoker.category == "partner")
# Create the native target (source_set or static_library) for the GN build
main_target_name = target_name
sdk_target_name = "${target_name}_sdk"
target_type = "source_set"
if (defined(invoker.build_as_static) && invoker.build_as_static) {
target_type = "static_library"
}
target(target_type, main_target_name) {
deps = []
public_deps = []
forward_variables_from(invoker,
"*",
[
"api",
"category",
"sdk_area",
"include_base",
"metadata",
"non_sdk_deps",
"non_sdk_public_deps",
"non_sdk_local_deps",
"sdk_headers_for_internal_use",
"source_dir",
"stable",
])
if (defined(visibility)) {
visibility += [ ":${sdk_target_name}" ]
}
if (defined(invoker.non_sdk_deps)) {
deps += invoker.non_sdk_deps
public_deps += invoker.non_sdk_deps
}
if (defined(invoker.non_sdk_public_deps)) {
public_deps += invoker.non_sdk_public_deps
}
if (defined(invoker.non_sdk_local_deps)) {
deps += invoker.non_sdk_local_deps
}
# Ensure that 'sdk_source_set()' targets are included in the appropriate
# allowlist. The allowlist target's `visibility` list ensures that the
# target using this template is in the allowlist.
if (make_internal_atom) {
deps += [ "//build/sdk:internal_source_sets_allowlist" ]
} else if (is_stable) {
assert(
invoker.category == "partner",
"Create a separate allowlist when adding support for other categories.")
deps += [ "//build/sdk:partner_idk_source_sets_allowlist" ]
} else {
assert(
invoker.category == "partner",
"Create a separate allowlist when adding support for other categories.")
deps += [ "//build/sdk:partner_idk_unstable_source_sets_allowlist" ]
}
configs += [ "//build/config:sdk_extra_warnings" ]
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
}
}
# Walk over dependencies (deps and public_deps) and collect their names (dep_names), labels (all_deps), labels of corresponding
# sdk elements (sdk_deps) and location of their metadata files (sdk_metas)
sdk_metas = []
sdk_deps = []
all_deps = []
dep_names = []
if (defined(invoker.deps)) {
all_deps += invoker.deps
}
if (defined(invoker.public_deps)) {
all_deps += invoker.public_deps
}
foreach(dep, all_deps) {
dep_full_label = get_label_info(dep, "label_no_toolchain")
sdk_dep = "${dep_full_label}_sdk"
sdk_deps += [ sdk_dep ]
gen_dir = get_label_info(sdk_dep, "target_gen_dir")
name = get_label_info(sdk_dep, "name")
sdk_metas += [ "${gen_dir}/${name}.meta.json" ]
dep_names += [ get_label_info(dep, "name") ]
}
# Split invoker.sources into sources and headers.
# If invoker.public is defined, use that as the list of headers. Otherwise, *.h are headers, and the others are sources
all_headers = []
all_sources = []
if (defined(invoker.public)) {
all_headers += invoker.public
}
if (defined(invoker.sources)) {
# If public headers are not defined, pick them from `sources`.
#
# NOTE: If this is an internal SDK library, headers from `sources` are
# always made available so Bazel can find them.
if (is_internal || !defined(invoker.public)) {
foreach(source_file, invoker.sources) {
extension = get_path_info(source_file, "extension")
if (extension == "h") {
all_headers += [ source_file ]
} else {
all_sources += [ source_file ]
}
}
} else {
all_sources += invoker.sources
}
}
# Determine destinations in the SDK for headers and sources.
sdk_metadata_headers = []
sdk_metadata_sources = []
sdk_header_files = []
sdk_files = []
if (defined(invoker.include_base)) {
include_base = invoker.include_base
} else {
if (is_internal) {
# by default, we want internal SDK elements to have their include path similar when used through
# an SDK (eg Bazel SDk) or directly (GN/Ninja), so that source code doesn't need to be changed
# when switching from one to the other.
include_base = "//"
} else {
include_base = "include"
}
}
assert(include_base != "") # Work around for unused variable warning when
# all_headers is empty
include_dest = "${sdk_root_path}/include"
foreach(header, all_headers) {
relative_destination = rebase_path(header, include_base)
destination = "${include_dest}/${relative_destination}"
sdk_metadata_headers += [ destination ]
sdk_header_files += [
{
source = header
dest = destination
},
]
}
sdk_files += sdk_header_files
foreach(source, all_sources) {
sdk_metadata_sources += [ "${sdk_root_path}/${source}" ]
sdk_files += [
{
source = source
dest = "${sdk_root_path}/${source}"
},
]
}
should_verify_pragma = !is_internal
if (should_verify_pragma) {
verify_pragma_target_name = "${target_name}_sdk_pragma"
verify_pragma_once(verify_pragma_target_name) {
headers = all_headers
}
}
verify_public_headers_target = "${target_name}_verify_public_headers"
verify_public_headers(verify_public_headers_target) {
forward_variables_from(invoker, [ "testonly" ])
target_label = ":${target_name}"
headers = all_headers
}
if (generate_plasa_artifacts) {
_plasa_artifacts_target_name = "${main_target_name}_plasa"
plasa_fragment_cc(_plasa_artifacts_target_name) {
forward_variables_from(invoker,
[
"source_dir",
"testonly",
"all_headers",
"all_deps",
"sdk_headers_for_internal_use",
])
file_base = sdk_root_path
}
} else {
not_needed(invoker, [ "sdk_headers_for_internal_use" ])
}
metadata_target_name = "${target_name}_sdk_metadata"
metadata_file = "$target_gen_dir/$target_name.sdk_meta.json"
action(metadata_target_name) {
forward_variables_from(invoker, [ "testonly" ])
script = "//build/cpp/gen_sdk_sources_meta_file.py"
inputs = sdk_metas
outputs = [ metadata_file ]
args = [
"--out",
rebase_path(metadata_file, root_build_dir),
"--name",
sdk_name,
"--root",
sdk_root_path,
"--include-dir",
include_dest,
]
args += [ "--deps" ] + rebase_path(sdk_metas, root_build_dir)
args += [ "--dep_names" ] + dep_names
args += [ "--sources" ] + sdk_metadata_sources
args += [ "--headers" ] + sdk_metadata_headers
if (is_stable) {
args += [ "--stable" ]
}
# Unlike the other C++ library templates, the main target
# (`main_target_name`) is not required for the metadata and is not
# built with the atom.
deps = sdk_deps
}
sdk_atom(sdk_target_name) {
# As described at the top of this file, this atom does NOT build the
# underlying source files.
# Ensure the behavior matches this documentation.
# Both could be changed if it makes sense.
#
# When plasa is enabled, the atom's `${sdk_target_name}_meta_copy` sub-target depends on
# the `${sdk_target_name}_plasa` target, which indirectly depends on
# `main_target_name`. Rather than trying to handle the `assert_no_deps` in
# `sdk_atom()`, just skip it in when generating plasa artifacts.
if (!generate_plasa_artifacts) {
assert_no_deps = [ ":${main_target_name}" ]
}
forward_variables_from(invoker,
[
"source_dir",
"testonly",
"sdk_area",
])
category = invoker.category
if (make_internal_atom) {
category = "internal"
}
id = sdk_id
if (is_stable) {
api = "${sdk_name}.api"
if (defined(invoker.api)) {
assert(
rebase_path(invoker.api, "//") != rebase_path(api, "//"),
"The specified `api` file (`${invoker.api}`) matches the default. `api` only needs to be specified when overriding the default.")
api = invoker.api
}
api_contents = sdk_header_files
} else {
assert(
!defined(invoker.api),
"Unstable targets do not require/support modification acknowledgement.")
}
meta = {
source = metadata_file
dest = "$sdk_root_path/meta.json"
schema = "cc_source_library"
stable = is_stable
}
files = sdk_files
deps = sdk_deps
non_sdk_deps = [
":${metadata_target_name}",
":${verify_public_headers_target}",
]
if (should_verify_pragma) {
non_sdk_deps += [ ":${verify_pragma_target_name}" ]
}
if (generate_plasa_artifacts) {
non_sdk_deps += [ ":${_plasa_artifacts_target_name}" ]
}
# Explicitly add non-public dependencies, in case some of the source files
# are generated.
if (defined(invoker.non_sdk_deps)) {
non_sdk_deps += invoker.non_sdk_deps
}
if (defined(invoker.non_sdk_public_deps)) {
non_sdk_deps += invoker.non_sdk_public_deps
}
if (defined(invoker.non_sdk_local_deps)) {
non_sdk_deps += invoker.non_sdk_local_deps
}
metadata = {
# Used by the //sdk:sdk_source_set_list build API module.
sdk_source_set_sources = rebase_path(all_sources + all_headers, "//")
}
}
}
set_defaults("sdk_source_set") {
configs = default_common_binary_configs
}