blob: dac08a2edead71b39d24b2b9f08ff6e60a4cd007 [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/config/fuchsia/platform_version.gni")
import("//build/config/fuchsia/target_api_level.gni")
import("//build/fidl/fidl_ir.gni")
import("//build/fidl/toolchain.gni")
import("//build/json/validate_json.gni")
import("//build/sdk/plasa/plasa_fragment_fidl.gni")
import("//build/sdk/sdk_atom.gni")
import("//build/testing/fidl_api_compatibility_test.gni")
import("//build/testing/golden_files.gni")
# Declares a bindings agnostic FIDL library. See //build/fidl/fidl.gni for the
# full FIDL template which produces bindings. This template does the following:
#
# - Generates JSON IR for the library.
# - Validates the JSON IR against //tools/fidl/fidlc/schema.json.
# - Lints source files with fidl-lint.
# - Performs SDK checks and produces an sdk_atom target (if applicable).
# - Generates API summaries (if applicable).
# - Runs API compatibility tests (if applicable).
#
# Parameters
#
# * library_name
# - Required: The name of the FIDL library.
# - Type: string
#
# * fidl_ir_json
# - Required: The path to the associated FIDL IR JSON file.
# - Type: path
#
# * fidl_ir_target
# - Required: The label of the target that generates the FIDL IR JSON file.
# - Type: label
#
# * sources
# - Required: See //build/fidl/fidl.gni for a description.
# - Type: list(path)
#
# * api, available, excluded_checks, experimental_checks, experimental_flags,
# goldens_dir, non_fidl_deps, platform, sdk_category, sdk_area
# - Optional: See //build/fidl/fidl.gni for a description.
#
# * testonly, visibility, public_deps, applicable_licenses
# - Optional: Usual GN meanings.
#
template("fidl_library") {
assert(
current_toolchain == fidl_toolchain,
"This template can only be used in the FIDL toolchain $fidl_toolchain.")
assert(defined(invoker.sources), "sources is required")
assert(defined(invoker.library_name), "library_name is required")
assert(defined(invoker.fidl_ir_json), "fidl_ir_json is required")
if (defined(invoker.available)) {
assert(defined(invoker.testonly) && invoker.testonly,
"the `available` parameter is only allowed on testonly libraries")
}
main_target_name = target_name
is_sdk_included =
defined(invoker.sdk_category) && invoker.sdk_category != "excluded"
is_sdk_included_publishable =
is_sdk_included && invoker.sdk_category != "internal" &&
invoker.sdk_category != "experimental" &&
invoker.sdk_category != "partner_internal"
is_sdk_included_publishable_nontest =
is_sdk_included_publishable && invoker.sdk_category != "cts"
requires_compatibility_tests = is_sdk_included_publishable ||
(defined(invoker.sdk_category) &&
invoker.sdk_category == "partner_internal")
in_fuchsia_namespace =
invoker.library_name ==
"fuchsia." + string_replace(invoker.library_name, "fuchsia.", "", 1)
# Additional dependencies to add when this target is published in an SDK.
sdk_atom_deps = []
goldens_dir = "//sdk/history"
if (defined(invoker.goldens_dir)) {
goldens_dir = invoker.goldens_dir
}
if (!is_sdk_included) {
not_needed([ "sdk_atom_deps" ])
}
if (!requires_compatibility_tests) {
not_needed([
"goldens_dir",
"in_fuchsia_namespace",
])
}
if (!is_sdk_included_publishable_nontest) {
not_needed(invoker, [ "api" ])
}
lint_stamp_file = "$target_gen_dir/$target_name.linted"
compilation_target_name = "${target_name}_compile"
verification_target_name = "${target_name}_verify"
lint_target_name = "${target_name}_lint"
forward_variables_from(invoker,
[
"library_name",
"fidl_ir_json",
])
fidl_ir(compilation_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"available",
"experimental_flags",
"non_fidl_deps",
"platform",
"public_deps",
"sources",
"testonly",
])
visibility = [ ":*" ]
fidl_target_name = main_target_name
fidl_library_name = library_name
gen_dir = target_gen_dir
json_representation = fidl_ir_json
target_api_level = fidl_fuchsia_api_level
if (!defined(available)) {
available = [ "fuchsia:$target_api_level" ]
}
if (!defined(platform)) {
# TODO(https://fxbug.dev/325669391): Always set a default `platform`.
if (requires_compatibility_tests && in_fuchsia_namespace) {
platform = "fuchsia"
} else if (defined(invoker.testonly) && invoker.testonly) {
platform = "unversioned"
}
}
}
summary_file_json = "$target_gen_dir/$target_name.api_summary.json"
plasa_fragment_file = summary_file_json
plasa_fragment_target_name = "${target_name}_plasa"
plasa_fragment_fidl(plasa_fragment_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"testonly",
])
visibility = [
":*",
"//sdk/fidl:plasa",
]
fidl_ir = fidl_ir_json
output_fragment_file = plasa_fragment_file
dest = "fidl"
deps = [ ":$compilation_target_name" ]
}
sdk_atom_deps += [ ":$plasa_fragment_target_name" ]
action(lint_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"sources",
"testonly",
])
visibility = [ ":*" ]
script = "//build/scripts/run_and_stamp.sh"
mnemonic = "LINT"
# run_and_stamp.sh runs this tool, and touches lint_stamp_file if successful
_tool_with_no_output = "//tools/fidl/fidlc:fidl-lint"
# Construct the host toolchain version of the tool.
host_tool_label = "//tools/fidl/fidlc:fidl-lint.host"
host_tool_path = "$root_build_dir/host-tools/fidl-lint"
deps = [ host_tool_label ]
if (defined(invoker.non_fidl_deps)) {
deps += invoker.non_fidl_deps
}
# Add the executable itself as an input.
inputs = [ host_tool_path ]
outputs = [ lint_stamp_file ]
args = [ rebase_path(lint_stamp_file, root_build_dir) ]
# Don't lint FIDL libraries used to test FIDL itself.
if (filter_include([ get_label_info(target_name, "dir") ],
[
"//sdk/testing/fidl/*",
"//src/devices/tools/fidlgen_banjo/tests/fidl/*",
"//src/lib/fidl/*",
"//src/tests/benchmarks/fidl/benchmark_suite/*",
"//src/tests/fidl/*",
"//tools/fidl/fidlc/testdata/*",
]) != []) {
args += [ ":" ] # NOOP - Nothing to lint, but touch the stamp file
} else {
args += [ rebase_path(host_tool_path, root_build_dir) ]
excluded_checks = []
if (defined(invoker.excluded_checks)) {
excluded_checks = invoker.excluded_checks
}
if (excluded_checks != []) {
# Add path to fidl-lint executable, and --must-find-excluded-checks option, so fidl-lint
# will return an error if any excluded check is no longer required. Excluded checks are only
# allowed if the target files still violate those checks. After updating the FIDL files to
# resolve a lint error, remove the check-id from the list of excluded checks in the fidl()
# target to prevent the same lint errors from creeping back in, in
# the future.
args += [ "--must-find-excluded-checks" ]
foreach(excluded_check, excluded_checks) {
args += [
"-e",
excluded_check,
]
}
}
experimental_checks = []
if (defined(invoker.experimental_checks)) {
experimental_checks = invoker.experimental_checks
}
foreach(experimental_check, experimental_checks) {
args += [
"-x",
experimental_check,
]
}
foreach(source, sources) {
args += [ rebase_path(source, root_build_dir) ]
}
}
}
# Enforce compatibility checks for all FIDL libraries included in the IDK with
# platform `fuchsia`.
if (requires_compatibility_tests) {
assert(!defined(invoker.platform) || invoker.platform == "fuchsia",
"Cannot override `platform` for SDK FIDL library $library_name")
assert(!defined(invoker.available),
"Cannot override `available` for SDK FIDL library $library_name")
# The current level might be a supported level. Don't double count it.
#
# TODO: https://fxbug.dev/305961460 - Replace with more definite logic.
current_level = platform_version.in_development_api_level
policy_for_current_level = "ack_changes"
# policy_for_current_level is unused when regenerating golden files.
not_needed([ "policy_for_current_level" ])
api_levels = [ "$current_level" ]
# Set api level to the next level if generate golden files tag is set to true.
if (defined(include_next_api_level) && include_next_api_level) {
next_level = 1 + platform_version.in_development_api_level
api_levels += [ "$next_level" ]
}
foreach(supported_level, platform_version.frozen_api_levels) {
if (supported_level == current_level) {
policy_for_current_level = "no_changes"
} else {
api_levels += [ "$supported_level" ]
}
}
foreach(level, api_levels) {
fidl_ir_target_name = "${main_target_name}_compile_$level"
fidl_ir(fidl_ir_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"experimental_flags",
"non_fidl_deps",
"public_deps",
"sources",
"testonly",
])
fidl_target_name = main_target_name
fidl_library_name = library_name
# TODO(https://fxbug.dev/325669391): in_fuchsia_namespace should always
# be true here, but currently it isn't for vendor SDK libraries. Those
# libraries likely should be excluded in requires_compatibility_tests.
if (in_fuchsia_namespace) {
platform = "fuchsia"
}
available = [ "fuchsia:$level" ]
target_api_level = level
goldens_dir = goldens_dir
# Avoid clobbering existing FIDL sources.
gen_dir = "$target_gen_dir/$level"
json_representation = "$gen_dir/$target_name.fidl.json"
out_json_summary = "$gen_dir/$library_name.api_summary.json"
}
sdk_atom_deps += [ ":$fidl_ir_target_name" ]
# TODO(https://fxbug.dev/42052847): Remove in_fuchsia_namespace exception.
# It's needed because we use the goldens as action.inputs so the build
# expects them to exist, but APIs outside the "fuchsia" namespace aren't
# compatibility tested and don't have goldens.
if (defined(api_compatibility_testing) && api_compatibility_testing &&
in_fuchsia_namespace) {
test_target = "${target_name}_${level}_compatibility_test"
fidl_api_compatibility_test(test_target) {
forward_variables_from(invoker, [ "testonly" ])
target_api_level = level
current = "$target_gen_dir/$level/$library_name.api_summary.json"
if (level == "$current_level") {
golden = "$goldens_dir/$level/$library_name.api_summary.json"
policy = policy_for_current_level
} else {
golden = "$goldens_dir/$level/$library_name.api_summary.json"
policy = "no_changes"
}
deps = [ ":$fidl_ir_target_name" ]
}
sdk_atom_deps += [ ":$test_target" ]
}
}
}
validate_json(verification_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"testonly",
])
visibility = [ ":*" ]
data = fidl_ir_json
schema = "//tools/fidl/fidlc/schema.json"
deps = [ ":$compilation_target_name" ]
}
group(main_target_name) {
forward_variables_from(invoker,
[
"applicable_licenses",
"testonly",
"visibility",
])
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
# Metadata to allow us to query all FIDL IR files.
fidl_json = [ rebase_path(fidl_ir_json, root_build_dir) ]
generated_sources = fidl_json
}
public_deps = [
":$compilation_target_name",
":$lint_target_name",
]
deps = [
":$plasa_fragment_target_name",
":$verification_target_name",
]
# Ignore the FIDL libraries that are only present in private versions of
# the SDK as they don't have the same versioning constraints.
target_dir = get_label_info(main_target_name, "dir")
is_vendor_library =
string_replace(target_dir, "//vendor/", "") != target_dir
if (!is_vendor_library && defined(invoker.sdk_category) &&
invoker.sdk_category == "partner") {
deps += [ "//sdk/fidl:sdk_fidl_partner_allowlist" ]
}
# Ensures that all libraries that are in the "partner_internal" category are
# in the "sdk_partner_internal_libs_allowlist" target. This is used to
# ensure that the API compatibility tests actually run on these libraries.
if (defined(invoker.sdk_category) &&
invoker.sdk_category == "partner_internal") {
deps += [ "//sdk/fidl:sdk_partner_internal_libs_allowlist" ]
}
}
if (is_sdk_included) {
sdk_category = invoker.sdk_category
if (is_sdk_included_publishable_nontest) {
api_reference = "$library_name.api"
if (defined(invoker.api)) {
api_reference = invoker.api
}
}
# Process sources.
file_base = "fidl/$library_name"
all_files = []
sdk_sources = []
foreach(source, invoker.sources) {
relative_source = rebase_path(source, ".")
if (string_replace(relative_source, "..", "bogus") != relative_source) {
# If the source file is not within the same directory, just use the file
# name.
relative_source = get_path_info(source, "file")
}
destination = "$file_base/$relative_source"
sdk_sources += [ destination ]
all_files += [
{
source = source
dest = destination
},
]
}
# Identify metadata for dependencies.
sdk_metas = []
sdk_deps = []
if (defined(invoker.public_deps)) {
foreach(dep, invoker.public_deps) {
full_label = get_label_info(dep, "label_no_toolchain")
sdk_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" ]
}
}
# Generate the library metadata.
meta_file = "$target_gen_dir/${target_name}.sdk_meta.json"
meta_target_name = "${target_name}_meta"
action(meta_target_name) {
script = "//build/fidl/gen_sdk_meta.py"
inputs = sdk_metas
outputs = [ meta_file ]
args = [
"--out",
rebase_path(meta_file, root_build_dir),
"--name",
library_name,
"--root",
file_base,
]
args += [ "--specs" ] + rebase_path(sdk_metas, root_build_dir)
args += [ "--sources" ] + sdk_sources
deps = sdk_deps
}
sdk_atom("${target_name}_sdk") {
id = "sdk://fidl/$library_name"
category = sdk_category
if (defined(invoker.sdk_area)) {
area = invoker.sdk_area
}
if (defined(api_reference)) {
api = api_reference
api_contents = [
{
source = summary_file_json
dest = "fidl/$library_name"
},
]
}
meta = {
source = meta_file
dest = "$file_base/meta.json"
schema = "fidl_library"
}
files = all_files
metadata = {
# Metadata to allow us to query all FIDL IR and source files.
fidl_json = [ rebase_path(fidl_ir_json, root_build_dir) ]
generated_sources = fidl_json
# Metadata for FIDL SDK files.
if (defined(invoker.sdk_category)) {
sdk_fidl_json_data = [
{
name = library_name
ir = rebase_path(fidl_ir_json, root_build_dir)
category = invoker.sdk_category
},
]
}
}
non_sdk_deps = [
":$compilation_target_name",
":$meta_target_name",
] + sdk_atom_deps
deps = []
if (defined(invoker.public_deps)) {
foreach(dep, invoker.public_deps) {
label = get_label_info(dep, "label_no_toolchain")
deps += [ "${label}_sdk" ]
}
}
}
} else { # is_sdk_included
not_needed(invoker, [ "sdk_area" ])
}
}