blob: 58ee4d8623ef79f9d974f97a5b43884f5fd2b7e6 [file] [log] [blame]
# Copyright 2017 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/compiled_action.gni")
import("//build/config/fuchsia/target_api_level.gni")
import("//build/json/validate_json.gni")
import("//build/python/python_action.gni")
import("//build/testing/golden_files.gni")
import("config.gni")
_types_supporting_unstable_atoms = [
"cc_source_library",
"fidl_library",
]
_types_not_requiring_compatibility = [
"bind_library",
"companion_host_tool",
"dart_library",
"data",
"documentation",
"experimental_python_e2e_test",
"ffx_tool",
"host_tool",
"package",
"version_history",
]
# Atoms of type "data" specify the real type in `value.type`. These are the
# valid values for `value.type`.
_valid_data_atom_types = [
# LINT.IfChange
"component_manifest",
"config",
# LINT.ThenChange(//build/sdk/generate_idk/__init__.py)
]
# Defines an IDK element.
#
# Outputs
#
# $target_gen_dir/$target_name.sdk
# A manifest describing what files pertain to the atom and which other atoms
# are required by this atom.
#
# $target_gen_dir/$target_name.meta.json
# A metadata file describing the atom.
# This file is included in the final SDK and used to e.g. drive the
# inclusion of the atom in a different build system.
#
# Parameters
#
# id
# Identifier of this element within SDKs.
# The identifier should represent the canonical base path of the element
# within SDKs according to the standard layout (https://fuchsia.dev/fuchsia-src/development/idk/layout.md).
# For an element at $ROOT/pkg/foo, the id should be "sdk://pkg/foo".
#
# category
# Describes the availability of the element.
# Possible values, from most restrictive to least restrictive:
# - internal : the atom is exposed in-tree to the Bazel SDK build via `@internal_sdk`.
# - compat_test : May be used to configure and run CTF tests but may not be exposed for use
# in production in the SDK or used by host tools.
# - host_tool : May be used by host tools (e.g., ffx) provided by the platform organization
# but may not be used by production code or prebuilt binaries in the SDK.
# - prebuilt : May be part of the ABI that prebuilt binaries included in the SDK use to
# interact with the platform.
# - partner : Included in the SDK for direct use of the API by out-of-tree developers.
#
# sdk_area (optional)
# The API area responsible for maintaining this SDK atom. See
# docs/contribute/governance/areas/_areas.yaml for the list of areas.
# "Unknown" is also a valid option. By default, the area will be `null` in
# the build manifest.
#
# meta
# Scope describing the element's metadata file.
# See the "Metadata scope" section for how to populate this attribute.
#
# files
# List of scopes describing the contents of this element.
# See the "File scopes" section for how to describe files.
#
# file_list
# Path to a file containing file mappings.
# Each line in the file should contain a "dest=source" mapping, similarly to
# file scopes.
#
# api (optional)
# Path to the file representing the API canonically exposed by this atom.
# This file is used to ensure modifications to the API are explicitly
# acknowledged.
# If this attribute is set, `api_contents` must be set as well.
#
# api_contents (optional)
# List of scopes for the files making up the atom's API.
# This list will be used to verify that the API has not changed locally.
# This is very roughly approximated by checking whether the files themselves
# have changed at all.
# See the "File scopes" section for how to describe files.
# Required when when `api` is set.
#
# deps (optional)
# List of GN labels for other SDK elements this element depends on at build
# time.
# These labels must point to "sdk_atom" targets.
#
# non_sdk_deps (optional)
# List of GN labels which this target needs built.
#
# Metadata scope
#
# This scope describes a metadata file to be added to the SDK element. Its
# supported attributes are:
#
# source (optional)
# Path to the metadata file.
#
# value (optional)
# Scope representing the metadata contents.
#
# NOTE: Exactly one of `source` or `value` must be set.
#
# dest (required)
# The path of the metadata file (usually `meta.json`) in the final IDK,
# relative to the IDK root.
#
# type (required)
# Type of the atom. Used to determine schema for this file
# Metadata files are hosted under //build/sdk/meta.
# If the metadata conforms to //build/sdk/meta/foo.json, the
# present attribute should have a value of "foo".
#
# stable (optional)
# Whether this sdk_atom is stabilized.
# Must be specified for types "fidl_library" and "cc_source_library" and
# otherwise unspecified.
# This is only informative. The value must match the `stable` value in the
# atom metadata specified by `source`/`value`. (That metadata is what
# controls whether the atom is marked as unstable in the final IDK.)
#
# File scopes
#
# Each scope describes a file to be added to the SDK element. The supported
# attributes are:
#
# source (required)
# Path to the original file.
# This path may be absolute or relative to the target's directory.
#
# dest (required)
# Destination path of the file relative to the IDK root.
template("sdk_atom") {
assert(defined(invoker.id), "Must define an SDK ID")
assert(defined(invoker.meta), "Must specify some metadata")
meta = invoker.meta
assert(defined(meta.type) && meta.type != "", "Must specify the atom type")
type = meta.type
assert(defined(meta.dest) && meta.dest != "",
"Must specify the atom destination in the IDK")
# Though the metadata files have a `type` key, the `meta.type` argument is not
# used for this. Verify the `meta.type` argument and underlying metadata are
# consistent when possible. The values of `type` must match if and only if
# the argument's value is not "data". For "data" atoms, an underlying type is
# specified in the metadata. Atoms that use a `meta.source` file cannot be
# verified.
if (defined(meta.value)) {
value = meta.value
assert(defined(value.type) && value.type != "",
"`type` must be specified in `value`.")
if (type == "data") {
assert(value.type != type,
"'data' atoms must specify the actual type in `meta.value`.")
is_data_type_valid = _valid_data_atom_types + [ value.type ] -
[ value.type ] != _valid_data_atom_types
assert(
is_data_type_valid,
"The `value.type` ('${value.type}') for 'data' atoms must be one of ${_valid_data_atom_types}.")
} else {
assert(value.type == type)
}
} else {
assert(type != "data", "'data' atoms must use `meta.value`.")
}
assert(defined(invoker.category), "Must define an SDK category")
category = invoker.category
_allowed_categories = [
# "internal" is deprecated; only specific legacy cases below are allowed.
"compat_test",
"host_tool",
"prebuilt",
"partner",
]
assert(
_allowed_categories + [ category ] - [ category ] !=
_allowed_categories ||
# TODO(https://fxbug.dev/372986936): Remove once all exceptions in the
# assert below have been removed.
category == "internal",
"'${target_name}' has unsupported SDK category '${category}'. Must be one of ${_allowed_categories}.")
# Atom types other than FIDL must be in the "partner" SDK category because
# they are exposed directly to developers. If you have a use case for another
# type in a different category, contact the Platform Versioning team.
assert(
category == "partner" || type == "fidl_library" ||
# TODO(https://fxbug.dev/372986936): Remove once all exceptions in the
# assert below have been removed.
category == "internal",
"Atoms of type '${type}' must be in the 'partner' SDK category ('${category}').")
target_label = get_label_info(":${target_name}", "label_no_toolchain")
if (category == "internal") {
# TODO(https://fxbug.dev/343059325): Remove once devicetree is in
# "partner" or no longer using loadable modules.
is_devicetree_visitor_loadable_module =
type == "loadable_module" &&
target_label != string_replace(target_label, "devicetree", "", 1)
# TODO(https://fxbug.dev/343059325): Remove once devicetree is in "partner".
is_devicetree =
type == "cc_prebuilt_library" &&
target_label == "//zircon/kernel/lib/devicetree:devicetree_sdk_manifest"
# TODO(https://fxbug.dev/42070500): Remove once shard is in "partner".
is_realm_builder_shard =
type == "data" &&
target_label == "//sdk/lib/sys/component:realm_builder_shard"
# TODO(https://fxbug.dev/331991540): Remove once a different
# solution for the Firmware SDK is implemented.
is_firmware_docs =
type == "documentation" && target_label == "//sdk/docs:firmware"
assert(
# `sdk_source_set()` enforces an allowlist.
type == "cc_source_library" ||
# Handle individual exceptions for types with only one "internal" instance.
is_devicetree_visitor_loadable_module || is_devicetree ||
is_realm_builder_shard || is_firmware_docs,
"`${target_label}` of type `${type}` is not allowed to use the \"internal\" SDK category. No new uses are allowed.")
not_needed([
"is_devicetree_visitor_loadable_module",
"is_realm_builder_shard",
"is_firmware_docs",
])
}
# For support unstable atoms, ensure that `meta.stable`is always
# specified and either it is true and an `api` file was specified or the
# category supports unstable atoms.
# `add_stable_true_to_meta_json` represents whether the meta.json
# generation script would have written "stable" to the file.
is_type_supporting_unstable = _types_supporting_unstable_atoms + [ type ] -
[ type ] != _types_supporting_unstable_atoms
assert(
is_type_supporting_unstable == defined(meta.stable) ||
# loadable_module does not support unstable atoms, but there are some
# in the internal only IDK.
# TODO(https://fxbug.dev/343059325): Remove once devicetree is in
# "partner" or no longer using loadable modules.
(type == "loadable_module" &&
target_label !=
string_replace(target_label, "devicetree", "", 1) &&
defined(meta.stable) && !meta.stable),
"`meta.stable` must be set if and only if the type ('${type}') is one of ${_types_supporting_unstable_atoms}.")
if (defined(meta.stable)) {
if (meta.stable) {
# TODO(https://fxbug.dev/372986936): Remove when no more uses.
assert(category != "internal",
"Atoms in SDK category '${category}' cannot be `stable`.")
is_stable = true
add_stable_true_to_meta_json = true
# For types that specify stable, ensure the `api` file was specified for stable atoms.
assert(defined(invoker.api))
} else {
# Categories other than "partner" exist only to ensure stability and thus
# should not have unstable atoms. If you have a use case for unstable
# atoms in another category, contact the Platform Versioning team.
assert(
category == "partner" ||
# TODO(https://fxbug.dev/372986936): Remove when no more uses.
category == "internal",
"`meta.stable` must be true unless the SDK category ('${category}') is 'partner'.")
is_stable = false
add_stable_true_to_meta_json = false
}
} else {
# Atom types that do not support unstable are always stable, but this is not
# reported in their `meta.json` file.
is_stable = true
add_stable_true_to_meta_json = false
}
# Handle types that are not in _types_not_requiring_compatibility. Their
# templates do not support specifying `meta.stable`, so override it here.
if (category == "internal" &&
(is_devicetree || is_realm_builder_shard || is_firmware_docs)) {
assert(is_stable)
is_stable = false
}
# TODO(https://fxbug.dev/372986936): Remove when no more uses of "internal".
assert(category != "internal" || !is_stable,
"Internal atoms must be unstable.")
is_type_not_requiring_compatibility =
_types_not_requiring_compatibility + [ type ] - [ type ] !=
_types_not_requiring_compatibility
if ((type == "cc_prebuilt_library" &&
(target_label ==
"//src/devices/bin/driver_runtime:driver_runtime_sdk_manifest" ||
target_label ==
"//third_party/Vulkan-Loader/src:libvulkan_sdk_manifest")) ||
(type == "loadable_module" &&
target_label == "//src/lib/vulkan:vulkan_layers")) {
# These few targets specify `no_headers` (or would if using a template).
# Since this list is unlikely to change much, just exempt them.
is_type_not_requiring_compatibility = true
}
assert(
defined(invoker.api) || !is_stable || is_type_not_requiring_compatibility,
"All atoms with types ('${type}') and categories ('${category}') requiring compatibility must specify an `api` file unless explicitly unstable.")
not_needed([
"is_type_not_requiring_compatibility",
"target_label",
])
_default_forward_from_invoker = [
"assert_no_deps",
"testonly",
]
gn_deps = []
if (defined(invoker.non_sdk_deps)) {
gn_deps = invoker.non_sdk_deps
}
dep_manifests = []
if (defined(invoker.deps)) {
gn_deps += invoker.deps
foreach(dep, invoker.deps) {
gen_dir = get_label_info(dep, "target_gen_dir")
name = get_label_info(dep, "name")
dep_manifests += [ "$gen_dir/$name.sdk" ]
}
}
# Some elements contain only the metadata.
if (!defined(invoker.files)) {
files = []
} else {
files = invoker.files
}
file_args = []
file_inputs = []
foreach(file, files) {
assert(defined(file.source), "File $file does not specify a source.")
assert(defined(file.dest), "File $file does not specify a destination.")
file_inputs += [ file.source ]
file_args += [
"--file",
file.dest,
rebase_path(file.source, root_build_dir),
]
}
meta_target_name = "${target_name}_meta"
# The generated file containing the metadata for the atom that will be
# included in the final IDK as its `meta.json` at `meta.dest`.
meta_file_for_idk = "$target_gen_dir/$target_name.meta.json"
_idk_atom_prebuild_info = {
atom_id = invoker.id
atom_label = get_label_info(":${target_name}", "label_no_toolchain")
atom_type = type
atom_meta = {
forward_variables_from(meta,
"*",
[
"source",
"source_prebuild_info",
])
}
atom_meta_json_file = rebase_path(meta_file_for_idk, root_build_dir)
if (defined(meta.source_prebuild_info)) {
prebuild_info = meta.source_prebuild_info
}
atom_files = []
foreach(file, files) {
atom_files += [
{
source = rebase_path(file.source, root_build_dir)
dest = file.dest
},
]
}
is_stable = is_stable
write_stable_true_to_meta_json = add_stable_true_to_meta_json
}
assert(defined(meta.source) != defined(meta.value),
"Exactly one of `meta.source` and `meta.value` must be set.")
if (defined(meta.value)) {
meta_generated_file_target = "${target_name}_meta_generated_file"
generated_file(meta_generated_file_target) {
forward_variables_from(invoker, _default_forward_from_invoker)
outputs = [ meta_file_for_idk ]
contents = meta.value
output_conversion = "json"
deps = gn_deps
}
target_providing_meta_file_for_idk = [ ":$meta_generated_file_target" ]
} else {
meta_copy_target_name = "${target_name}_meta_copy"
assert(defined(meta.source), "Meta scope needs a source or value")
if (!defined(meta.source_prebuild_info)) {
print(get_label_info(":$target_name", "label_no_toolchain") +
": No meta.source_prebuild_info argument!")
}
# Copy the file to a canonical location for access by other rules.
# TODO(https://fxbug.dev/42131074): instead, make sure that all atoms generate their metadata
# file in the right location.
copy(meta_copy_target_name) {
forward_variables_from(invoker, _default_forward_from_invoker)
sources = [ meta.source ]
outputs = [ meta_file_for_idk ]
deps = gn_deps
}
target_providing_meta_file_for_idk = [ ":$meta_copy_target_name" ]
}
# Verify that the metadata complies with the specified schema.
validate_json(meta_target_name) {
forward_variables_from(invoker, _default_forward_from_invoker)
data = meta_file_for_idk
schema = "//build/sdk/meta/${type}.json"
use_valico = true
sources = [
# This file is imported by all schemas.
"//build/sdk/meta/common.json",
]
# Add any schema referenced by the main schema for the type.
if (type == "ffx_tool") {
sources += [ "//build/sdk/meta/host_tool.json" ]
}
public_deps = target_providing_meta_file_for_idk
}
# Add the metadata file to the set of files to include in SDKs.
file_args += [
"--file",
meta.dest,
rebase_path(meta_file_for_idk, root_build_dir),
]
assert(defined(invoker.api) == defined(invoker.api_contents),
"Must set only one of 'api' and 'api_contents' together")
_verify_api = defined(invoker.api)
if (_verify_api) {
assert(invoker.api_contents != [], "api_contents cannot be empty")
generate_api_target_name = "${target_name}_generate_api"
current_api_file = "$target_gen_dir/$target_name.api"
action(generate_api_target_name) {
forward_variables_from(invoker, _default_forward_from_invoker)
script = "//build/sdk/compute_atom_api.py"
inputs = []
outputs = [ current_api_file ]
args = [
"--output",
rebase_path(current_api_file, root_build_dir),
]
deps = gn_deps
foreach(file, invoker.api_contents) {
inputs += [ file.source ]
args += [
"--file",
file.dest,
rebase_path(file.source, root_build_dir),
]
}
}
verify_api_target_name = "${target_name}_verify_api"
golden_files(verify_api_target_name) {
forward_variables_from(invoker, _default_forward_from_invoker)
comparisons = [
{
candidate = current_api_file
golden = invoker.api
},
]
warn_on_changes = warn_on_sdk_changes
deps = [ ":$generate_api_target_name" ]
}
}
# Builds a manifest representing this atom.
# This manifest is only used at build time to generate the IDK. The actual
# manifest for this atom in the IDK is `meta_file_for_idk`.
python_action(target_name) {
forward_variables_from(invoker, _default_forward_from_invoker)
manifest = "$target_gen_dir/$target_name.sdk"
areas_file = "//docs/contribute/governance/areas/_areas.yaml"
depfile = "$manifest.d"
binary_label = "//build/sdk:create_atom_manifest"
public_deps = gn_deps + [ ":$meta_target_name" ]
if (_verify_api) {
public_deps += [ ":$verify_api_target_name" ]
}
inputs = dep_manifests + file_inputs + [ areas_file ]
outputs = [ manifest ]
args = [
"--id",
invoker.id,
"--out",
rebase_path(manifest, root_build_dir),
"--depfile",
rebase_path(depfile, root_build_dir),
"--gn-label",
get_label_info(":$target_name", "label_with_toolchain"),
"--category",
category,
"--areas-file-path",
rebase_path(areas_file, root_build_dir),
"--meta",
meta.dest,
"--type",
type,
"--deps",
] + rebase_path(dep_manifests, root_build_dir) + file_args
# Record the actual stability state for all atoms. This may not match the
# presence of "stable" in `meta_file_for_idk`.
if (is_stable) {
args += [ "--stable" ]
}
if (defined(invoker.sdk_area)) {
args += [
"--area",
invoker.sdk_area,
]
}
if (defined(invoker.file_list)) {
inputs += [ invoker.file_list ]
args += [
"--file-list",
rebase_path(invoker.file_list, root_build_dir),
]
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata,
"*",
[ "idk_atom_prebuild_info" ])
}
if (category == "partner") {
# Used by idk_prebuild_manifest() template.
idk_atom_prebuild_info = [ _idk_atom_prebuild_info ]
} else {
not_needed([ "_idk_atom_prebuild_info" ])
}
}
# The manifest output contains the output directory.
no_output_dir_leaks = false
}
}
OSZAR »