blob: 7b4f4e34e0ba6c8ddb2debfea91f27fac2c1a3e3 [file] [log] [blame]
# Copyright 2020 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/dist/resource.gni")
import("//tools/cmc/build/cmc.gni")
import("//tools/cmc/build/cml.gni")
import("//tools/cmc/build/cmx.gni")
# Defines a Fuchsia component's manifest.
# See: https://fuchsia.dev/fuchsia-src/development/components/build
#
# Parameters
#
# manifest (required)
# The component manifest.
# Type: path
#
# component_name (optional)
# The name of the component.
# Type: string
# Default: target_name
#
# required_offers (optional)
# Check that all children and collections are offered each protocol listed.
# Type: list of strings
#
# required_uses (optional)
# Check that all children and collections use each protocol listed.
# Type: list of strings
#
# restricted_features (optional)
# The set of restricted CML features to allow.
# The set of features is allowlisted here: //tools/cmc/build/restricted_features/BUILD.gn
# where each feature name is represented by a group of the same name.
# Type: list of strings
# Default: []
#
# experimental_force_runner (optional)
# Set the --experimental-force-runner flag to the given value.
# This flag is experimental and may be removed without warning.
# Type: string
#
# data_deps
# deps
# testonly
# visibility
template("fuchsia_component_manifest") {
if (current_toolchain == default_toolchain) {
assert(
defined(invoker.manifest),
"A `manifest` argument was missing when calling fuchsia_component_manifest($target_name)")
component_name = target_name
if (defined(invoker.component_name)) {
component_name = invoker.component_name
}
# Handle different manifest versions
manifest_extension = get_path_info(invoker.manifest, "extension")
if (manifest_extension == "cml") {
manifest_processor = "cm"
manifest_name = "$component_name.cm"
} else if (manifest_extension == "cmx") {
manifest_processor = "cmx"
manifest_name = "$component_name.cmx"
} else {
assert(
false,
"Unknown manifest format for \"${invoker.manifest}\", must be \".cml\" or \".cmx\"")
}
manifest_resource_target = "${target_name}_manifest_resource"
# Process the manifest
target(manifest_processor, target_name) {
output_name = "$manifest_extension/$target_name/$manifest_name"
forward_variables_from(invoker,
[
"deps",
"manifest",
"required_offers",
"required_uses",
"restricted_features",
"testonly",
"visibility",
"experimental_force_runner",
])
if (!defined(deps)) {
deps = []
}
deps += [ ":$manifest_resource_target" ]
if (manifest_extension == "cml") {
# NOTE: must be kept in sync with path in fuchsia_structured_config.gni
config_values_package_path = "meta/$component_name.cvf"
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
component_manifest_path = [ manifest ]
component_manifest_path_barrier = []
if (!defined(component_catalog)) {
component_catalog = []
}
component_catalog += [
{
component_manifest_path = manifest
label = get_label_info(":$target_name", "label_with_toolchain")
},
]
}
}
# Add the manifest
resource(manifest_resource_target) {
forward_variables_from(invoker, [ "data_deps" ])
sources = get_target_outputs(":${invoker.target_name}")
outputs = [ "meta/$manifest_name" ]
visibility = [ ":*" ]
}
} else {
group(target_name) {
forward_variables_from(invoker,
[
"testonly",
"visibility",
])
deps = [ ":$target_name($default_toolchain)" ]
}
not_needed(invoker, "*")
}
}
# Defines a Fuchsia component.
# See: https://fuchsia.dev/fuchsia-src/development/components/build
#
# A component is defined by a component manifest.
# Component manifests typically reference files in the package that they are
# distributed in. Therefore a component can also have dependencies on
# `resource()`, such that any package that depends on the component # will
# also include that resource.
#
# A component is launched by a URL.
# See: https://fuchsia.dev/fuchsia-src/glossary#component_url
#
# A component's URL is a function of the name of a package that includes it,
# and the path within that package to the component's manifest. For instance if
# you defined the following:
# ```
# executable("my_program") {
# ...
# }
#
# fuchsia_component("my-component") {
# manifest = "manifest.cml"
# deps = [ ":my_program" ]
# }
#
# fuchsia_package("my-package") {
# deps = [ ":my-component" ]
# }
# ```
# The component above will have the following launch URL:
# `fuchsia-pkg://fuchsia.com/my-package#meta/my-component.cm`
#
# Since the component depends on the executable target, the binary produced by
# the executable will be packaged with the manifest. Therefore the manifest
# author can reference the path `bin/my_program`.
#
# Components may depend on any number of `resource()` targets to ensure that
# any `fuchsia_package()` that includes them will include the same resources.
#
# ```
# resource("my_file") {
# sources = [ "my_file.txt" ]
# outputs = [ "data/{{source_file_part}}" ]
# }
#
# fuchsia_component("my-component") {
# deps = [ ":my_file" ]
# ...
# }
# ```
#
# The component defined above will be able to read my_file.txt under the path
# "/pkg/data/my_file.txt" in its sandbox.
#
# Parameters
#
# manifest (must specify either manifest or cm_label)
# The component manifest.
# Type: path
#
# cm_label (must specify either manifest or cm_label)
# Use label of a fuchsia_component_manifest target instead of supplying the manifest source.
# Type: string, GN label e.g. `:my-manifest`
#
# component_name (optional)
# The name of the component.
# Type: string
# Default: target_name
#
# check_includes (optional)
# Check against expect_includes() in deps.
# Warning: Do not disable this unless there is a good, documented reason.
# Type: boolean
# Default: true
#
# check_references (optional)
# Check component manifest references (e.g. "program.binary") against
# package manifest.
# Type: boolean
# Default: true
#
# restricted_features (optional)
# The set of restricted CML features to allow.
# The set of features is allowlisted here: //tools/cmc/build/restricted_features/BUILD.gn
# where each feature name is represented by a group of the same name.
# Type: list of strings
# Default: []
#
# experimental_force_runner (optional)
# Set the --experimental-force-runner flag to the given value.
# This flag is experimental and may be removed without warning.
# Type: string
#
# manifest_deps (optional)
# Dependencies for the component's manifest, in case it is generated by another target.
# Type: list of targets
#
# data_deps
# deps
# testonly
# visibility
template("fuchsia_component") {
if (current_toolchain == default_toolchain) {
_manifest_defined = defined(invoker.manifest)
_cm_label_defined = defined(invoker.cm_label)
assert(
(_manifest_defined && !_cm_label_defined) ||
(!_manifest_defined && _cm_label_defined),
"Exactly one of `manifest` or `cm_label` argument must be specified when calling fuchsia_component($target_name)")
if (defined(invoker.manifest)) {
# Compile the manifest from source
cm_target = "${target_name}_manifest_compile"
fuchsia_component_manifest(cm_target) {
forward_variables_from(invoker,
[
"data_deps",
"deps",
"component_name",
"manifest",
"metadata",
"restricted_features",
"testonly",
"visibility",
"experimental_force_runner",
])
if (!defined(component_name)) {
component_name = invoker.target_name
}
if (defined(invoker.manifest_deps)) {
if (!defined(deps)) {
deps = []
}
deps += invoker.manifest_deps
}
}
cm_label = ":$cm_target"
} else {
cm_label = invoker.cm_label
}
if (!defined(invoker.check_includes) || invoker.check_includes) {
_find_manifest_target = "${target_name}_locate_manifest"
generated_file(_find_manifest_target) {
forward_variables_from(invoker, [ "testonly" ])
visibility = [ ":*" ]
outputs = [ "$target_out_dir/$_find_manifest_target.json" ]
data_keys = [ "component_manifest_path" ]
walk_keys = [ "component_manifest_path_barrier" ]
rebase = root_build_dir
output_conversion = "json"
deps = [ cm_label ]
}
_find_manifest_target = ":$_find_manifest_target"
_include_check_target = "${target_name}_check_includes"
cmc_check_includes(_include_check_target) {
forward_variables_from(invoker,
[
"deps",
"testonly",
])
_find_manifest_outputs = get_target_outputs(_find_manifest_target)
assert(_find_manifest_outputs == [ _find_manifest_outputs[0] ],
"$_find_manifest_target should have yielded exactly one output")
manifest = _find_manifest_outputs[0]
visibility = [ ":*" ]
if (!defined(deps)) {
deps = []
}
deps += [ _find_manifest_target ]
}
_include_check_target = ":$_include_check_target"
}
# Check that the files the manifest references are going to be packaged
check_references = true
if (defined(invoker.check_references)) {
check_references = invoker.check_references
}
if (check_references) {
# Get the compiled manifest path
manifest_outputs = get_target_outputs(cm_label)
compiled_manifest = manifest_outputs[0]
validate_target = "${target_name}_cmc_validate_references"
cmc_validate_references(validate_target) {
forward_variables_from(invoker,
[
"deps",
"testonly",
])
if (!defined(deps)) {
deps = []
}
deps += [ cm_label ]
visibility = [ ":*" ]
component_manifest = compiled_manifest
label = get_label_info(":$target_name", "label_with_toolchain")
}
validate_target = ":$validate_target"
}
component_catalog_target = "${target_name}.component_catalog"
generated_file(component_catalog_target) {
forward_variables_from(invoker,
[
"testonly",
"deps",
"public_deps",
])
outputs = [ "$target_out_dir/${component_catalog_target}.json" ]
output_conversion = "json"
data_keys = [ "component_catalog" ]
visibility = [ ":*" ]
if (!defined(deps)) {
deps = []
}
deps += [ cm_label ]
}
# We want to adopt the outputs of the cm_label, so we
# copy them to a new directory.
copy(target_name) {
forward_variables_from(invoker, [ "testonly" ])
sources = get_target_outputs(cm_label)
outputs = [ "$target_out_dir/${target_name}_component_manifest/{{source_file_part}}" ]
deps = [
":$component_catalog_target",
cm_label,
]
if (check_references) {
deps += [ validate_target ]
}
if (defined(_include_check_target)) {
deps += [ _include_check_target ]
}
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
test_component_manifest_program_barrier = []
test_component_manifest_barrier = []
# Don't collect expected includes via transitive deps of components
expect_includes_barrier = []
}
}
} else {
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
deps = [ ":$target_name($default_toolchain)" ]
}
not_needed(invoker, "*")
}
}