blob: 0a0acce0634c2a03267c1d6465fbdbe1949bb40f [file] [log] [blame]
# Copyright 2021 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/components/fuchsia_component.gni")
import("//build/cpp/library_headers.gni")
import("//build/fidl/fidl.gni")
import("//build/rust/rustc_library.gni")
import("//tools/configc/build/config.gni")
# Defines a configuration value file for a Fuchsia component.
#
# A config value file is produced from a component manifest that contains a schema
# and a JSON5 file with concrete configuration values.
#
# For example, if a component manifest defines the `enable_foo` flag:
#
# ```
# // ./meta/my_component.cml
# {
# // ...
# config: {
# enable_foo: { type: "bool" }
# }
# }
# ```
#
# The definition file will need to contain an entry for it and any other fields
# in its manifest:
#
# ```
# // ./config/my_component.json5
# {
# enable_foo: true
# }
# ```
#
# Building the config value file requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
# component = "my_component"
# manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
# cm_label = ":my_component_manifest"
# deps = [ ... ]
# }
#
# fuchsia_structured_config_values("my_component_config") {
# cm_label = ":my_component_manifest"
# values = "config/my_component.json5"
# }
# ```
#
# Finally, the package must include the value file alongside the manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_package("my_package") {
# deps = [
# ":my_component",
# ":my_component_config",
# ]
# }
# ```
#
# Parameters
#
# cm_label (required)
# The label of the fuchsia_component_manifest target for which the file will be generated.
# Type: GN label, e.g. `:my_component_manifest`
#
# values_source -or- values (required)
# file: The JSON5 file containing the concrete values for the generated file.
# values: A GN scope containing literal values for the generated file.
# TODO(https://fxbug.dev/42169169) document this format properly.
# Type: path or scope
#
# component_name (optional)
# The basename of the component manifest within the package's meta/ dir. If not provided,
# derived from the `cm_label` parameter's outputs. Must be specified in order to invoke
# this template in a different GN module from where `cm_label` is defined.
#
# cvf_output_name (optional)
# The name of the cvf file that is being produced. Defaults to the value of component_name.
#
# data_deps (optional)
# deps (optional)
# testonly (optional)
# Standard GN meaning.
template("fuchsia_structured_config_values") {
if (current_toolchain == default_toolchain) {
assert(
defined(invoker.cm_label),
"must provide a component manifest label with a configuration declaration")
_source_defined = defined(invoker.values_source)
_values_defined = defined(invoker.values)
assert(
(_source_defined || _values_defined) &&
!(_source_defined && _values_defined),
"must provide either `values_source` (path to JSON5 file) or `values` (GN scope with literal values)")
if (_values_defined) {
_generated_values_label = "${target_name}_generated_values"
_value_file_deps = [ ":$_generated_values_label" ]
_value_file = "$target_gen_dir/${target_name}_values_from_literal.json"
generated_file(_generated_values_label) {
output_conversion = "json"
contents = invoker.values
outputs = [ _value_file ]
}
} else {
_value_file_deps = []
_value_file = invoker.values_source
}
if (defined(invoker.component_name)) {
# we have the component name which means we can infer the output location ourselves
component_name = invoker.component_name
_cm_out_dir = get_label_info(invoker.cm_label, "target_out_dir")
_cm_target_name = get_label_info(invoker.cm_label, "name")
compiled_manifest = "$_cm_out_dir/cml/$_cm_target_name/$component_name.cm"
} else {
# make sure invoker.cm_label is in the same module, i.e. starts with ":"
segments = string_split(invoker.cm_label, ":")
assert(
segments[0] == "",
"component_name must be provided if cm_label is in a different module")
# now we can call get_target_outputs without creating unactionable error messages
component_outputs = get_target_outputs(invoker.cm_label)
compiled_manifest = component_outputs[0]
component_name = get_path_info(compiled_manifest, "name")
}
cvf_output_name = component_name
if (defined(invoker.cvf_output_name)) {
cvf_output_name = invoker.cvf_output_name
}
# compile the value file
resource_target = "${target_name}"
cvf_target = "${target_name}_cvf"
cvf(cvf_target) {
forward_variables_from(invoker, [ "testonly" ])
cm = compiled_manifest
value_file = _value_file
if (!defined(deps)) {
deps = []
}
deps += [ "${invoker.cm_label}" ] + _value_file_deps
visibility = [ ":${resource_target}" ]
}
# package the value file
resource(resource_target) {
forward_variables_from(invoker,
[
"data_deps",
"deps",
"testonly",
"visibility",
])
sources = get_target_outputs(":${cvf_target}")
if (!defined(deps)) {
deps = []
}
deps += [ ":${cvf_target}" ]
# NOTE: must be kept in sync with path in fuchsia_component.gni
outputs = [ "meta/$cvf_output_name.cvf" ]
if (defined(visibility)) {
# The group we create below for the non-default toolchains needs to depend on this target.
# We can't explicitly list all of the toolchain suffixes that might dep on this, because not
# all of them have variable shortcuts defined.
visibility += [ ":${resource_target}" ]
}
# Avoid picking up transitive dependencies in the cm_label.
metadata = {
distribution_entries_barrier = []
}
}
} else {
# need to have a nop target that can be depended upon by host toolchain targets
group(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
deps = [ ":$target_name($default_toolchain)" ]
}
not_needed(invoker, "*")
}
}
# Defines a configuration value manifest that provides configuration values.
#
# This accepts a component manifest that `uses` configuration capabilities. It
# produces a component manifest that defines each of these configuration capabilities
# and exposes those capabilities to its parent.
#
# A config value manifest is produced from a component manifest that contains a schema
# and a JSON5 file with concrete configuration values.
#
# For example, if a component manifest use the `fuchsia.component.EnableFoo` flag:
#
# ```
# // ./meta/my_component.cml
# {
# // ...
# use: [
# {
# config: "fuchsia.component.EnableFoo",
# key: "enable_foo",
# type: "bool",
# },
# ]
# }
# ```
#
# The definition file will need to contain an entry for it and any other fields
# in its manifest:
#
# ```
# // ./config/my_component.json5
# {
# "enable_foo": true
# }
# ```
#
# Building the config value file requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
# component = "my_component"
# manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
# cm_label = ":my_component_manifest"
# deps = [ ... ]
# }
#
# fuchsia_structured_config_values2("my_component_config") {
# cm_label = ":my_component_manifest"
# values = "config/my_component.json5"
# }
# ```
#
# You can link your component and its config together in a parent component.
# ```
# // ./meta/root.cml
# {
# children: [
# { name: "my_component", url: "#meta/my_component.cm", },
# { name: "my_config", url: "#meta/my_component_config.cm", },
# ],
# offer: [
# { from: "#my_config", to: "#my_component", config: "fuchsia.config.MyConfig",}
# ],
# }
# ```
#
# Finally, the package must include the value file alongside the manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_package("my_package") {
# deps = [
# ":my_component",
# ":my_component_config",
# ]
# }
# ```
#
# Parameters
#
# cm_label (required)
# The label of the fuchsia_component_manifest target for which the file will be generated.
# Type: GN label, e.g. `:my_component_manifest`
#
# values_source -or- values (required)
# file: The JSON5 file containing the concrete values for the generated file.
# values: A GN scope containing literal values for the generated file.
# TODO(https://fxbug.dev/42169169) document this format properly.
# Type: path or scope
#
# component_name (optional)
# The basename of the component manifest within the package's meta/ dir. If not provided,
# derived from the `cm_label` parameter's outputs. Must be specified in order to invoke
# this template in a different GN module from where `cm_label` is defined.
#
# output_component_name (optional)
# The name of the component that is being produced. Defaults to "${component_name}_config".
#
# data_deps (optional)
# deps (optional)
# testonly (optional)
# Standard GN meaning.
template("fuchsia_structured_config_values2") {
if (current_toolchain == default_toolchain) {
assert(
defined(invoker.cm_label),
"must provide a component manifest label with a configuration declaration")
_source_defined = defined(invoker.values_source)
_values_defined = defined(invoker.values)
assert(
(_source_defined || _values_defined) &&
!(_source_defined && _values_defined),
"must provide either `values_source` (path to JSON5 file) or `values` (GN scope with literal values)")
if (_values_defined) {
_generated_values_label = "${target_name}_generated_values"
_value_file_deps = [ ":$_generated_values_label" ]
_value_file = "$target_gen_dir/${target_name}_values_from_literal.json"
generated_file(_generated_values_label) {
output_conversion = "json"
contents = invoker.values
outputs = [ _value_file ]
}
} else {
_value_file_deps = []
_value_file = invoker.values_source
}
if (defined(invoker.component_name)) {
# we have the component name which means we can infer the output location ourselves
component_name = invoker.component_name
_cm_out_dir = get_label_info(invoker.cm_label, "target_out_dir")
_cm_target_name = get_label_info(invoker.cm_label, "name")
compiled_manifest = "$_cm_out_dir/cml/$_cm_target_name/$component_name.cm"
} else {
# make sure invoker.cm_label is in the same module, i.e. starts with ":"
segments = string_split(invoker.cm_label, ":")
assert(
segments[0] == "",
"component_name must be provided if cm_label is in a different module")
# now we can call get_target_outputs without creating unactionable error messages
component_outputs = get_target_outputs(invoker.cm_label)
compiled_manifest = component_outputs[0]
component_name = get_path_info(compiled_manifest, "name")
}
output_component_name = component_name + "_config"
if (defined(invoker.output_component_name)) {
output_component_name = invoker.output_component_name
}
# compile the value file
resource_target = "${target_name}"
cvf_target = "${target_name}_cvf"
component_value_manifest(cvf_target) {
forward_variables_from(invoker, [ "testonly" ])
cm = compiled_manifest
value_file = _value_file
if (!defined(deps)) {
deps = []
}
deps += [ "${invoker.cm_label}" ] + _value_file_deps
visibility = [ ":${resource_target}" ]
}
# package the value file
resource(resource_target) {
forward_variables_from(invoker,
[
"data_deps",
"deps",
"testonly",
"visibility",
])
sources = get_target_outputs(":${cvf_target}")
if (!defined(deps)) {
deps = []
}
deps += [ ":${cvf_target}" ]
outputs = [ "meta/$output_component_name.cm" ]
if (defined(visibility)) {
# The group we create below for the non-default toolchains needs to depend on this target.
# We can't explicitly list all of the toolchain suffixes that might dep on this, because not
# all of them have variable shortcuts defined.
visibility += [ ":${resource_target}" ]
}
# Avoid picking up transitive dependencies in the cm_label.
metadata = {
distribution_entries_barrier = []
}
}
} else {
# need to have a nop target that can be depended upon by host toolchain targets
group(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
deps = [ ":$target_name($default_toolchain)" ]
}
not_needed(invoker, "*")
}
}
# Defines a Rust configuration client library for a Fuchsia ELF component.
#
# A config client library is produced from a component manifest that contains a schema.
#
# For example, if a component manifest defines the `enable_foo` flag:
#
# ```
# // ./meta/my_component.cml
# {
# // ...
# config: {
# enable_foo: { type: "bool" }
# }
# }
# ```
#
# Building the config client library requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
# component = "my_component"
# manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
# cm_label = ":my_component_manifest"
# deps = [ ... ]
# }
#
# fuchsia_structured_config_rust_lib("my_component_config_lib") {
# cm_label = ":my_component_manifest"
# }
# ```
#
# Finally, a rust binary can import this library for use
#
# ```
# # ./BUILD.gn
# rustc_binary("my_binary") {
# ...
# deps = [
# ":my_component_config_lib",
# ...
# ]
# }
# ```
#
# And in my_binary source, it can be used like this
#
# ```
# use my_component_config_lib:get_config;
#
# fn main() {
# let config = get_config();
# println!("Is foo enabled = {}", config.enable_foo);
# }
# ```
#
# Parameters
#
# cm_label (required)
# The label of the fuchsia_component_manifest target for which the file will be generated.
# Type: GN label, e.g. `:my_component_manifest`
#
# name (optional)
# Name for the generated Rust library. If not specified, the target name is used.
# Type: string
#
# testonly (optional)
# Standard GN meaning.
template("fuchsia_structured_config_rust_lib") {
if (!defined(invoker.name)) {
rust_library_name = target_name
} else {
rust_library_name = invoker.name
}
rust_library_name = string_replace(rust_library_name, ".", "_")
rust_library_name = string_replace(rust_library_name, "-", "_")
# The library name is a string that is also used internally by configc when generating
# FIDL and Rust source files. It is not visible to the end user.
fidl_library_name = string_replace(rust_library_name, "_", "")
fidl_library_name = "cf.sc.internal.${fidl_library_name}"
fidl_source_target = "${target_name}_fidl_config_lib_source"
rust_source_target = "${target_name}_rust_config_lib_source"
if (current_toolchain == default_toolchain) {
assert(defined(invoker.cm_label), "must provide a component manifest label")
manifest_outputs = get_target_outputs(invoker.cm_label)
compiled_manifest = manifest_outputs[0]
# generate the client library FIDL source
fidl_config_client_lib_source(fidl_source_target) {
forward_variables_from(invoker, [ "testonly" ])
name = fidl_library_name
compiled_manifest = compiled_manifest
deps = [ invoker.cm_label ]
# prevent manifest from getting into package this way
metadata = {
distribution_entries_barrier = []
}
}
# generate the rust source
rust_config_client_lib_source(rust_source_target) {
forward_variables_from(invoker, [ "testonly" ])
fidl_library_name = fidl_library_name
compiled_manifest = compiled_manifest
deps = [ invoker.cm_label ]
# prevent manifest from getting into package this way
metadata = {
distribution_entries_barrier = []
}
}
} else {
group(fidl_source_target) {
forward_variables_from(invoker, [ "testonly" ])
deps = [ ":${fidl_source_target}($default_toolchain)" ]
}
group(rust_source_target) {
forward_variables_from(invoker, [ "testonly" ])
deps = [ ":${rust_source_target}($default_toolchain)" ]
}
not_needed(invoker, "*")
}
# Cannot call get_target_outputs on `client_lib_source`, so we must get the path to the
# generated source files manually.
source_gen_dir =
get_label_info(":anything($default_toolchain)", "target_out_dir")
fidl_source_file = "${source_gen_dir}/${fidl_source_target}.fidl"
rust_source_file = "${source_gen_dir}/${rust_source_target}.rs"
# generate the FIDL library
fidl_library_target = "${target_name}_fidl_internal"
fidl(fidl_library_target) {
name = fidl_library_name
forward_variables_from(invoker, [ "testonly" ])
sources = [ fidl_source_file ]
non_fidl_deps = [ ":${fidl_source_target}($default_toolchain)" ]
excluded_checks = [
"invalid-copyright-for-platform-source-library",
"wrong-prefix-for-platform-source-library",
]
}
# generate the wrapper Rust library
rustc_library(target_name) {
name = rust_library_name
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
edition = "2018"
sources = [ rust_source_file ]
source_root = rust_source_file
deps = [
":${fidl_library_target}_rust",
"//src/lib/diagnostics/inspect/rust",
"//src/lib/fidl/rust/fidl",
"//src/lib/fuchsia-runtime",
"//src/lib/zircon/rust:fuchsia-zircon",
]
configs -= [ "//build/config/rust/lints:allow_unused_results" ]
non_rust_deps = [ ":${rust_source_target}($default_toolchain)" ]
}
}
# Defines a C++ configuration client library for a Fuchsia ELF component.
#
# A config client library is produced from a component manifest that contains a schema.
#
# For example, if a component manifest defines the `enable_foo` flag:
#
# ```
# // ./meta/my_component.cml
# {
# // ...
# config: {
# enable_foo: { type: "bool" }
# }
# }
# ```
#
# Building the config client library requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
# component = "my_component"
# manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
# cm_label = ":my_component_manifest"
# deps = [ ... ]
# }
#
# fuchsia_structured_config_cpp_elf_lib("my_component_config_lib") {
# cm_label = ":my_component_manifest"
# }
# ```
#
# Finally, a C++ binary can import this library for use
#
# ```
# # ./BUILD.gn
# executable("my_binary") {
# ...
# deps = [
# ":my_component_config_lib",
# ...
# ]
# }
# ```
#
# And in my_binary source, it can be used like this
#
# ```
# #import <my_component_config_lib/config.h>
#
# int main(int argc, void** argv) {
# auto config = Config::from_args();
# FX_LOGS(INFO) << "Is foo enabled = " << config.enable_foo;
# }
# ```
#
# Parameters
#
# cm_label (required)
# The label of the fuchsia_component_manifest target for which the file will be generated.
# Type: GN label, e.g. `:my_component_manifest`
#
# namespace (optional)
# Namespace used by the generated C++ library. If not specified, the target name is used.
# Type: string
#
# fidl_library_name (optional)
# Name for the generated FIDL library. If not specified, the default (cf.sc.internal) is used.
# Type: string
#
# testonly (optional)
# Standard GN meaning.
template("fuchsia_structured_config_cpp_elf_lib") {
if (!defined(invoker.namespace)) {
namespace = target_name
} else {
namespace = invoker.namespace
}
# The library name is a string that is also used internally by configc when generating
# FIDL and C++ source files. It is not visible to the end user.
if (defined(invoker.fidl_library_name)) {
fidl_library_name = invoker.fidl_library_name
} else {
fidl_library_name = "cf.sc.internal"
}
namespace = string_replace(namespace, ".", "_")
namespace = string_replace(namespace, "-", "_")
fidl_source_target = "${target_name}_fidl_config_lib_source"
cpp_elf_source_target = "${target_name}_cpp_elf_config_lib_source"
cpp_elf_headers_target = "${target_name}_cpp_elf_config_lib_headers"
if (current_toolchain == default_toolchain) {
assert(defined(invoker.cm_label), "must provide a component manifest label")
manifest_outputs = get_target_outputs(invoker.cm_label)
compiled_manifest = manifest_outputs[0]
# generate the client library FIDL source
fidl_config_client_lib_source(fidl_source_target) {
forward_variables_from(invoker, [ "testonly" ])
name = fidl_library_name
compiled_manifest = compiled_manifest
deps = [ invoker.cm_label ]
}
# generate the C++ source
cpp_config_client_lib_source(cpp_elf_source_target) {
forward_variables_from(invoker, [ "testonly" ])
namespace = namespace
fidl_library_name = fidl_library_name
compiled_manifest = compiled_manifest
flavor = "elf"
deps = [ invoker.cm_label ]
}
} else {
group(fidl_source_target) {
forward_variables_from(invoker, [ "testonly" ])
deps = [ ":${fidl_source_target}($default_toolchain)" ]
}
group(cpp_elf_source_target) {
forward_variables_from(invoker, [ "testonly" ])
deps = [ ":${cpp_elf_source_target}($default_toolchain)" ]
}
not_needed(invoker, "*")
}
# Cannot call get_target_outputs on `client_lib_source`, so we must get the path to the
# generated source files manually.
source_gen_dir =
get_label_info(":anything($default_toolchain)", "target_out_dir")
fidl_source_file = "${source_gen_dir}/${fidl_source_target}.fidl"
cpp_elf_cc_source_file = "${source_gen_dir}/${namespace}.cc"
# generate the FIDL library
fidl_library_target = "${target_name}_fidl_internal"
fidl(fidl_library_target) {
name = fidl_library_name
forward_variables_from(invoker, [ "testonly" ])
sources = [ fidl_source_file ]
non_fidl_deps = [ ":${fidl_source_target}($default_toolchain)" ]
excluded_checks = [
"invalid-copyright-for-platform-source-library",
"wrong-prefix-for-platform-source-library",
]
}
library_headers(cpp_elf_headers_target) {
forward_variables_from(invoker, [ "testonly" ])
include_dir = "$root_build_dir/obj"
headers = [ "${namespace}/config.h" ]
public_deps = [ ":${cpp_elf_source_target}($default_toolchain)" ]
}
# generate the wrapper C++ library
source_set(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
sources = [ cpp_elf_cc_source_file ]
deps = [
":${fidl_library_target}_cpp",
"//sdk/lib/fidl",
]
public_deps = [
":${cpp_elf_headers_target}",
":${cpp_elf_source_target}($default_toolchain)",
"//zircon/system/ulib/inspect",
]
configs += [ "//build/config:sdk_extra_warnings" ]
# prevent manifest from getting into package this way
metadata = {
distribution_entries_barrier = []
}
}
}