blob: a806369babe3640d64bcfb2bc7a940f14c61a69a [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/json/validate_json.gni")
import("config.gni")
# Defines an SDK 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/sdk/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:
# - excluded : the atom may not be included in SDKs;
# - experimental : the atom is available with no quality guarantee;
# - internal : the atom is exposed within the Fuchsia tree;
# - partner : the atom may be used by select partners;
# - public : the atom may be included in published SDKs.
#
# 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, one of "current_api" and "api_contents" must be
# set as well.
#
# current_api (optional)
# Path to the file representing the API locally exposed by this atom.
# This file is used to verify that the API has not changed locally.
# Only relevant when "api" is set, and mutually exclusive with
# "api_contents".
#
# 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.
# Only relevant when "api" is set, and mutually exclusive with
# "current_api".
#
# 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)
# Path to the metadata file in the SDK, relatively to the SDK root
#
# schema (required)
# Name of the schema for this file, ignoring the extension.
# Metadata files are hosted under //build/sdk/meta.
# If the metadata file conforms to //build/sdk/meta/foo.json, the
# present attribute should have a value of "foo".
#
# 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 SDK root.
template("sdk_atom") {
assert(defined(invoker.category), "Must define an SDK category")
category = invoker.category
assert(defined(invoker.id), "Must define an SDK ID")
assert(defined(invoker.meta), "Must specify some metadata")
meta = invoker.meta
if (defined(invoker.api)) {
if (!defined(invoker.current_api) && !defined(invoker.api_contents)) {
assert(false, "Must set one of 'current_api' and 'api_contents'")
} else if (defined(invoker.current_api) && defined(invoker.api_contents)) {
assert(false, "Must set only one of 'current_api' and 'api_contents'")
}
}
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 += [ rebase_path("$gen_dir/$name.sdk") ]
}
}
assert(defined(invoker.files), "An atom must specify some files")
file_args = []
file_inputs = []
foreach(file, invoker.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 ]
source = rebase_path(file.source)
file_args += [
"--file",
file.dest,
source,
]
}
meta_target_name = "${target_name}_meta"
meta_content = invoker.meta
meta_file = "$target_gen_dir/$target_name.meta.json"
if (defined(meta_content.value)) {
# Directly write the value into a file in the output directory.
write_file(meta_file, meta_content.value, "json")
meta_deps = gn_deps
} else {
meta_copy_target_name = "${target_name}_meta_copy"
assert(defined(meta_content.source), "Meta scope needs a source or value")
# Copy the file to a canonical location for access by other rules.
# TODO(DX-497): instead, make sure that all atoms generate their metadata
# file in the right location.
copy(meta_copy_target_name) {
forward_variables_from(invoker, [ "testonly" ])
sources = [
meta_content.source,
]
outputs = [
meta_file,
]
deps = gn_deps
}
meta_deps = [ ":$meta_copy_target_name" ]
}
# Verify that the metadata complies with the specified schema.
validate_json(meta_target_name) {
forward_variables_from(invoker, [ "testonly" ])
data = meta_file
schema = "//build/sdk/meta/${meta_content.schema}.json"
sources = [
# This file is imported by all schemas.
"//build/sdk/meta/common.json",
]
public_deps = meta_deps
}
# Add the metadata file to the set of files to include in SDKs.
meta_source = rebase_path(meta_file)
file_args += [
"--file",
meta.dest,
meta_source,
]
if (defined(invoker.api)) {
if (defined(invoker.api_contents)) {
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, [ "testonly" ])
script = "//build/sdk/compute_atom_api.py"
inputs = []
outputs = [
current_api_file,
]
args = [
"--output",
rebase_path(current_api_file),
]
foreach(file, invoker.api_contents) {
inputs += [ file.source ]
args += [
"--file",
file.dest,
rebase_path(file.source),
]
}
}
} else {
current_api_file = invoker.current_api
}
verify_api_target_name = "${target_name}_verify_api"
action(verify_api_target_name) {
forward_variables_from(invoker, [ "testonly" ])
script = "//build/sdk/verify_atom_api.py"
stamp_file = "$target_gen_dir/$target_name.api_verified"
inputs = [
current_api_file,
invoker.api,
]
outputs = [
stamp_file,
]
args = [
"--reference",
rebase_path(invoker.api),
"--current",
rebase_path(current_api_file),
"--stamp",
rebase_path(stamp_file),
]
if (warn_on_sdk_changes) {
args += [ "--warn" ]
}
if (defined(invoker.api_contents)) {
deps = [
":$generate_api_target_name",
]
}
}
} # if defined(invoker.api)
# Builds a manifest representing this element.
action(target_name) {
forward_variables_from(invoker,
[
"testonly",
"metadata",
])
manifest = "$target_gen_dir/$target_name.sdk"
depfile = "$manifest.d"
script = "//build/sdk/create_atom_manifest.py"
public_deps = gn_deps + [ ":$meta_target_name" ]
if (defined(invoker.api)) {
public_deps += [ ":$verify_api_target_name" ]
}
inputs = dep_manifests + file_inputs + [
# Imported by the action's script.
"//build/sdk/sdk_common.py",
]
outputs = [
manifest,
]
args = [
"--id",
invoker.id,
"--out",
rebase_path(manifest, root_build_dir),
"--depfile",
rebase_path(depfile),
"--gn-label",
get_label_info(":$target_name", "label_with_toolchain"),
"--category",
category,
"--meta",
meta.dest,
"--type",
meta.schema,
"--deps",
] + dep_manifests + file_args
if (defined(invoker.file_list)) {
args += [
"--file-list",
rebase_path(invoker.file_list),
]
}
}
}