blob: 0e22f48a647cf007adee6fb1e68db2efca609ede [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/dist/fini_manifest.gni")
import("//build/dist/resource.gni")
import("//build/images/args.gni")
import("//build/images/manifest.gni")
import("//build/packages/package_metadata.gni")
import("//build/testing/test_spec.gni")
import("//src/sys/component_index/component_index.gni")
import("//src/sys/pkg/bin/pm/pm.gni")
import("//tools/cmc/build/cmc.gni")
import("//tools/cmc/build/cml.gni")
import("//tools/cmc/build/cmx.gni")
# Defines a package
#
# The package template is used to define a unit of related code and data.
# A package always has a name (defaulting to the target name) and lists of
# scopes describing the components of the package.
#
# Parameters
#
# meta (optional)
# [list of scopes] Defines the metadata entries in the package. A metadata
# entry is typically a source file and is placed in the `meta/` directory of
# the assembled package.
#
# Entries in a scope in the meta list:
#
# path (required)
# [path] Location of entry in source or build directory. If the
# resource is checked in, this will typically be specified as a
# path relative to the BUILD.gn file containing the `package()`
# target. If the resource is generated, this will typically be
# specified relative to `$target_gen_dir`.
#
# dest (required)
# [path] Location the resource will be placed within `meta/`.
#
# binaries (optional)
# [list of scopes] Defines the binaries in the package. A binary is
# typically produced by the build system and is placed in the `bin/`
# directory of the assembled package.
#
# Entries in a scope in the binaries list:
#
# name (required)
# [string] Name of the binary.
#
# source (optional)
# [path] Location of the binary in the build directory if it is not
# at `$root_out_dir/$name`.
#
# dest (optional)
# [path] Location the binary will be placed within `bin/`.
#
# shell (optional)
# [boolean] (default: false) When true, the binary is runnable from the shell.
# Shell binaries are run in the shell namespace and are not run as components.
#
# tests (optional)
# [list of scopes] Defines the test binaries in the package. A test is
# typically produced by the build system and is placed in the `test/`
# directory of the assembled package.
#
# Entries in a scope in the tests list:
#
# name (required)
# [string] Name of the test.
#
# dest (optional, default: name)
# [path] Location the binary will be placed within `test/`.
#
# disabled (optional)
# [bool] Whether to disable the test on continuous integration
# jobs. This can be used when a test is temporarily broken, or if
# it is too flaky or slow for CI. The test will also be skipped by
# the `runtests` command.
#
# environments (optional, default: [ { dimensions = { device_type = "QEMU" } } ])
# [list of scopes] Device environments in which the test should run.
#
# Each scope in $environments contains:
#
# dimensions (required)
# [scope] Dimensions of bots to target. Valid dimensions are
# element-wise subsets of the test platform entries defined in
# //build/testing/platforms.gni.
#
# tags (optional)
# [list of strings] Keys on which tests may be grouped. Tests with
# given keys will be run (1) together, and (2) only with support
# from the Infrastructure team. Labels are used as an escape hatch
# from the default testing pipeline for special tests or environments.
#
# log_settings (optional, default: see below properties)
# [json] Properties of logs produced by this test run.
#
# Following properties are supported:
#
# max_severity (optional, default: WARN)
# [string] Defines maximum severity of logs which can be produced by test
# environment. This can only be defined for test components. Test will fail
# if any component in its environment produces log with greater severity
# than defined here.
#
# parallel (optional)
# [int] Defines maximum concurrent test cases to run. This only works with v2 tests.
# If not defined, test runner will decide the default value.
#
# drivers (optional)
# [list of scopes] Defines the drivers in the package. A driver is
# typically produced by the build system and is placed in the `driver/`
# directory of the assembled package.
#
# Entries in a scope in the drivers list:
#
# name (required)
# [string] Name of the driver.
#
# loadable_modules (optional)
# [list of scopes] Defines the loadable modules in the package. These
# are produced by `loadable_module()` GN targets, and are typically
# placed in the `lib/` directory of the assembled packaged.
#
# Entries in a scope in the loadable_modules list:
#
# name (required)
# [string] Name of the loadable_module.
#
# dest (optional, default: "lib")
# [string] Location the binary will be placed in the package.
#
# libraries (optional, *DEPRECATED*)
# [list of scopes] Defines the (shared) libraries in the package. A library
# is placed in the `lib/` directory of the assembled package.
#
# This is deprecated but is necessary in some `system_image` packages
# that install libraries used by things that don't properly isolate
# their dependencies. Do not use it unless you are sure you have to.
#
# Entries in a scope in the libraries list:
#
# name (required)
# [string] Name of the library
#
# source (optional)
# [path] Location of the binary in the build directory if it is not at
# `$root_out_dir/$name`
#
# dest (optional)
# [path] Location the binary will be placed within `lib/`
#
# resources (optional)
# [list of scopes] Defines the resources in the package. A resource is a
# data file that may be produced by the build system, checked in to a
# source repository, or produced by another system that runs before the
# build. Resources are placed in the `data/` directory of the assembled
# package.
#
# resource_deps (optional)
# [list of labels] Dependencies on resource() targets to include in the
# package.
#
# Entries in a scope in the resources list:
#
# path (required)
# [path] Location of resource in source or build directory. If the
# resource is checked in, this will typically be specified as a
# path relative to the BUILD.gn file containing the `package()`
# target. If the resource is generated, this will typically be
# specified relative to `$target_gen_dir`.
#
# dest (required)
# [path] Location the resource will be placed within `data/`.
#
# extra_manifests (optional)
# [list of GN paths] An optional list of paths to extra manifests to
# add items to this package. This is a horrible hack used to ensure
# that variant_shared_library_toolchain() instances can be installed
# into a package() instance. Do not use this for any other purpose.
#
# deps (optional)
# public_deps (optional)
# data_deps (optional)
# testonly (optional)
# Usual GN meanings.
#
template("package") {
if (current_toolchain == target_toolchain) {
pkg_target_name = target_name
pkg = {
forward_variables_from(invoker,
[
"binaries",
"data_deps",
"deps",
"public_deps",
"drivers",
"libraries",
"loadable_modules",
"meta",
"package_name",
"resources",
"resource_deps",
"visibility",
"tests",
"testonly",
])
if (!defined(binaries)) {
binaries = []
}
if (!defined(deps)) {
deps = []
}
if (!defined(data_deps)) {
data_deps = []
}
if (!defined(public_deps)) {
public_deps = []
}
if (!defined(drivers)) {
drivers = []
}
if (!defined(loadable_modules)) {
loadable_modules = []
}
if (!defined(libraries)) {
libraries = []
}
if (!defined(meta)) {
meta = []
}
if (!defined(package_name)) {
package_name = pkg_target_name
}
if (!defined(resources)) {
resources = []
}
if (!defined(resource_deps)) {
resource_deps = []
}
if (!defined(tests)) {
tests = []
}
}
assert(pkg.package_name != "tests" && pkg.package_name != "app" &&
pkg.package_name != "bin" && pkg.package_name != "pkg" &&
pkg.package_name != "lib" && pkg.package_name != "package" &&
pkg.package_name != "binary" && pkg.package_name != "service" &&
pkg.package_name != "svc" && pkg.package_name != "component",
"${pkg.package_name} is too generic a name for a package")
pkg_label = get_label_info(":$pkg_target_name", "label_no_toolchain")
pkg_desc = "Package ${pkg_label} (${pkg.package_name}):"
if (!defined(pkg.testonly) || !pkg.testonly) {
assert(pkg.drivers == [],
"$pkg_desc drivers requires __deprecated_system_image")
}
assert(pkg.libraries == [],
"$pkg_desc libraries requires __deprecated_system_image")
foreach(meta, pkg.meta) {
manifest_target = pkg_target_name + "_" + get_path_info(meta.dest, "file")
manifest_output = []
if (get_path_info(meta.dest, "extension") == "cmx") {
pkg.deps += [ "//build/components/cmx:cmx_allowlist" ]
cmx(manifest_target) {
manifest = meta.path
# We don't know which deps are associated with which component manifest.
# Disable collecting expected includes to avoid false errors.
check_includes = false
forward_variables_from(pkg,
[
"deps",
"testonly",
])
}
component_index_target = "${manifest_target}_component_index"
add_to_component_index(component_index_target) {
package_name = pkg.package_name
manifest = "meta/${meta.dest}"
}
pkg.deps += [ ":$component_index_target" ]
manifest_output = get_target_outputs(":$manifest_target")
meta.path = manifest_output[0]
pkg.deps += [ ":$manifest_target" ]
} else if (get_path_info(meta.path, "extension") == "cml") {
cm(manifest_target) {
manifest = meta.path
# We don't know which deps are associated with which component manifest.
# Disable collecting expected includes to avoid false errors.
check_includes = false
forward_variables_from(pkg,
[
"deps",
"testonly",
])
}
manifest_output = get_target_outputs(":$manifest_target")
meta.path = manifest_output[0]
pkg.deps += [ ":$manifest_target" ]
} else {
manifest_target = false
}
resource_target =
pkg_target_name + "_resource_" + get_path_info(meta.dest, "file")
resource(resource_target) {
forward_variables_from(pkg, [ "testonly" ])
if (manifest_target != false) {
deps = [ ":$manifest_target" ]
}
sources = [ meta.path ]
outputs = [ "meta/${meta.dest}" ]
}
pkg.resource_deps += [ ":$resource_target" ]
}
# Collect the package's primary manifest. For a system_image package,
# this is its contributions to the /system manifest. For an isolated
# package, this is the manifest for the package's `pkg/` filesystem.
pkg_manifest = []
shell_binaries = []
foreach(binary, pkg.binaries) {
if (defined(binary.dest)) {
dest = binary.dest
} else {
dest = binary.name
}
dest = "bin/${dest}"
if (defined(binary.shell) && binary.shell) {
shellfile = target_gen_dir + "/" + dest + ".shell"
write_file(
shellfile,
[ "#!resolve fuchsia-pkg://fuchsia.com/${pkg.package_name}#${dest}" ])
shell_binaries += [
{
source = rebase_path(shellfile, root_build_dir)
destination = dest
label = get_label_info(":$pkg_target_name", "label_with_toolchain")
},
]
# The shell environment is not intended for production use.
pkg.deps += [ "//build/validate:non_production_tag" ]
}
pkg_manifest += [
{
dest = dest
if (defined(binary.source)) {
source = binary.source
} else {
source = binary.name
}
source = rebase_path(source, "", root_out_dir)
},
]
}
foreach(test, pkg.tests) {
assert(!defined(test.tags),
"tags are only valid within an environment scope")
# It's a common mistake to specify "tags" in the scope of the test
# instead of in an environment scope.
is_disabled = defined(test.disabled) && test.disabled
if (defined(test.dest)) {
dest = test.dest
} else {
dest = test.name
}
if (is_disabled) {
dest = "disabled/${dest}"
}
url = "fuchsia-pkg://fuchsia.com"
dest = "test/$dest"
url_pkg_name = pkg.package_name
path = ""
url += "/${url_pkg_name}"
dest_name = get_path_info(dest, "name")
not_needed([ "dest_name" ])
# Check that all tests are components with cmx/cm files at the expected path.
if (!is_disabled) {
found_manifest = false
url_resource_path = ""
foreach(meta, pkg.meta) {
extension = get_path_info(meta.dest, "extension")
if (extension == "cmx" || extension == "cm") {
meta_name = get_path_info(meta.dest, "name")
if (dest_name == meta_name) {
found_manifest = true
url_resource_path = "#meta/${meta.dest}"
}
}
}
assert(
found_manifest,
"Test ${test.name} did not have a matching component manifest file")
url += url_resource_path
}
if (!is_disabled) {
test_spec_name = "${pkg.package_name}_${dest}_test_spec"
pkg.deps += [ ":$test_spec_name" ]
test_spec(test_spec_name) {
target = ":${invoker.target_name}"
path = path
package_url = url
forward_variables_from(test,
[
"log_settings",
"parallel",
])
forward_variables_from(test, [ "environments" ])
}
} else {
not_needed([
"path",
"url",
])
}
pkg_manifest += [
{
dest = dest
source = rebase_path(test.name, "", root_out_dir)
},
]
}
foreach(module, pkg.loadable_modules) {
pkg_manifest += [
{
if (defined(module.dest)) {
dest = module.dest
} else {
dest = "lib"
}
dest += "/${module.name}"
source = rebase_path(module.name, "", root_out_dir)
},
]
}
foreach(driver, pkg.drivers) {
pkg_manifest += [
{
dest = "driver/${driver.name}"
source = rebase_path(driver.name, "", root_out_dir)
},
]
}
resources_deps = []
foreach(resource, pkg.resources) {
resource_entry = {
}
resource_entry = {
dest = "data/${resource.dest}"
source = rebase_path(resource.path)
}
pkg_manifest += [ resource_entry ]
resource_name = string_replace(resource.dest, "/", "_")
resource_target = "$target_name.resource.resource.$resource_name"
resource(resource_target) {
forward_variables_from(pkg, [ "testonly" ])
deps = pkg.deps
sources = [ resource_entry.source ]
outputs = [ resource_entry.dest ]
}
resources_deps += [ ":$resource_target" ]
}
library_deps = []
# TODO(mcgrathr): Remove this when we can! Packages installing
# libraries in the system image is all kinds of wrong.
foreach(library, pkg.libraries) {
library_entry = {
}
library_entry = {
if (defined(library.dest)) {
dest = library.dest
} else {
dest = library.name
}
dest = "lib/${dest}"
if (defined(library.source)) {
source = library.source
} else {
# TODO(mcgrathr): This breaks when everything is a variant so
# that only this here is using the non-variant shlib build.
source = get_label_info(shlib_toolchain, "name")
source += "/${library.name}"
}
source = rebase_path(source, "", root_out_dir)
}
pkg_manifest += [ library_entry ]
resource_target = "$target_name.library.resource.${library.name}"
resource(resource_target) {
forward_variables_from(pkg, [ "testonly" ])
deps = pkg.deps
sources = [ library_entry.source ]
outputs = [ library_entry.dest ]
}
library_deps += [ ":$resource_target" ]
}
manifest_sources = []
manifest_args = []
manifest_args += [ "--entry-manifest=${pkg_label}" ]
foreach(entry, pkg_manifest) {
manifest_sources += [ entry.source ]
manifest_args += [ "--entry=${entry.dest}=${entry.source}" ]
}
if (defined(invoker.extra_manifests)) {
foreach(manifest, invoker.extra_manifests) {
manifest_args += [ "@" + rebase_path(manifest, root_build_dir) ]
}
}
pkg.metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*")
}
shell_binary_entries = shell_binaries
}
if (defined(pkg.visibility)) {
pkg.visibility += [
":$target_name",
# TODO(raggi): tighten build/images visibility once we've
# finished doing large scale cleanups.
"//build/images:*",
"//build/packages/*",
"//bundles/*",
"//garnet/packages/*",
"//src/modular/bundles/*",
"//topaz/packages/*",
"//vendor/*",
]
}
# Synthesize the meta/package file.
pkg_meta_generated = "${pkg_target_name}_meta_package.json"
generate_meta_package(pkg_meta_generated) {
forward_variables_from(pkg, [ "testonly" ])
package_name = pkg.package_name
visibility = [ ":*" ]
}
# Generate an extra manifest with meta/package and resource_deps
fini_target = "${pkg_target_name}_resource_deps"
fini_manifest("$fini_target") {
forward_variables_from(pkg, [ "testonly" ])
visibility = [ ":*" ]
deps = [ ":$pkg_meta_generated" ] + pkg.resource_deps
}
fini_file = get_target_outputs(":$fini_target")
fini_file = rebase_path(fini_file[0], root_build_dir)
manifest_args += [ "--manifest=$fini_file" ]
# Fuchsia package aggregates a manifest from its arguments and builds a
# metadata archive.
manifest = "${pkg_target_name}.manifest"
generate_manifest(manifest) {
forward_variables_from(pkg, [ "testonly" ])
visibility = [ ":*" ]
sources = manifest_sources
args = manifest_args
deps = pkg.deps + [
":$fini_target",
"//zircon/public/sysroot:system_libc_deps",
]
public_deps = pkg.public_deps
}
manifest_file = get_target_outputs(":$manifest")
manifest_file = manifest_file[0]
# Validate component manifests against package manifest
foreach(meta, pkg.meta) {
validate_target = pkg_target_name + "_validate_manifests_" +
get_path_info(meta.dest, "file")
cmc_validate_references(validate_target) {
forward_variables_from(pkg,
[
"deps",
"testonly",
])
visibility = [ ":*" ]
deps += [ ":$manifest" ]
package_manifest = manifest_file
component_manifest = meta.path
label = get_label_info(":$pkg_target_name", "label_with_toolchain")
}
pkg.deps += [ ":$validate_target" ]
}
# Next generate a sealed package file.
pm_build(pkg_target_name) {
forward_variables_from(pkg, [ "testonly" ])
if (defined(pkg.visibility)) {
visibility = pkg.visibility
}
package_name = pkg.package_name
manifest = ":$manifest"
metadata = {
forward_variables_from(pkg.metadata, "*")
}
deps = pkg.deps + resources_deps + library_deps
data_deps = pkg.data_deps
public_deps = pkg.public_deps
# Prevent the contents of this package from getting picked up in a zbi.
# See fxbug.dev/45680 for more information.
metadata.distribution_entries_barrier = []
# See https://fuchsia.dev/fuchsia-src/development/components/build#other_unsupported_features.
deps += [ "//build:deprecated_package" ]
}
} else {
# A reference from a different toolchain, e.g. a variant toolchain, is
# just an indirect way to get the package into the system images.
# Redirect it as data_deps on the $target_toolchain package. This
# really ought to be a pure redirect, i.e. public_deps. But using
# data_deps here avoids problems in case some dependency on a package()
# target is not in data_deps as it (almost surely) should be.
group(target_name) {
forward_variables_from(invoker,
[
"visibility",
"testonly",
])
data_deps = [ ":$target_name($target_toolchain)" ]
}
# Suppress unused variable warnings.
not_needed(invoker, "*")
}
}