blob: 682ad5cffc14328633602209ad999e6a944be7e0 [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/images/args.gni")
import("//build/images/manifest.gni")
import("//build/testing/test_spec.gni")
import("//src/sys/cmc/build/cml.gni")
import("//src/sys/cmc/build/cmx.gni")
import("//src/sys/component_index/component_index.gni")
import("//src/sys/pkg/bin/pm/pm.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/`.
#
# binary (optional, *DEPRECATED*)
# [string] The path to the primary binary for the package, relative to
# `$root_out_dir`. The binary will be placed in the assembled package at
# `bin/app` and will be executed by default when running the package.
#
# 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.
#
# 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.
#
# 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 (optional)
# [list of paths] Manifest files containing extra entries, which
# might be generated by the build.
#
# deps (optional)
# public_deps (optional)
# data_deps (optional)
# testonly (optional)
# Usual GN meanings.
#
template("package") {
if (current_toolchain == target_toolchain) {
forward_variables_from(invoker, [ "testonly" ])
pkg_target_name = target_name
pkg = {
package_version = "0" # placeholder
forward_variables_from(
invoker,
[
"binaries",
"binary",
"data_deps",
"deprecated_system_image", # TODO(bwb) remove after soft-transition
"__deprecated_system_image",
"deps",
"public_deps",
"drivers",
"extra",
"libraries",
"loadable_modules",
"meta",
"package_name",
"resources",
"visibility",
"tests",
])
if (!defined(binaries)) {
binaries = []
}
if (!defined(__deprecated_system_image) &&
!defined(deprecated_system_image)) {
__deprecated_system_image = false
}
if (defined(deprecated_system_image)) {
__deprecated_system_image = deprecated_system_image
}
if (!defined(deps)) {
deps = []
}
if (!defined(data_deps)) {
data_deps = []
}
if (!defined(public_deps)) {
public_deps = []
}
if (!defined(extra)) {
extra = []
}
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(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 (pkg.__deprecated_system_image) {
assert(
pkg.meta == [],
"$pkg_desc __deprecated_system_image (likely from driver_package) incompatible with meta")
assert(
!defined(pkg.binary),
"$pkg_desc __deprecated_system_image (likely from driver_package) incompatible with binary")
# Only appmgr can contribute binaries to /system/bin.
assert(pkg.binaries == [] || pkg.package_name == "appmgr",
"no new packages are allowed to add binaries to /system/bin")
assert(pkg.tests == [], "tests are not allowed in /system")
} else {
assert(pkg.drivers == [],
"$pkg_desc drivers requires __deprecated_system_image")
assert(pkg.libraries == [],
"$pkg_desc libraries requires __deprecated_system_image")
if (defined(pkg.binary)) {
pkg.binaries += [
{
name = "app"
source = pkg.binary
},
]
}
}
formatted_manifest = []
foreach(meta, pkg.meta) {
manifest_target = pkg_target_name + "_" + get_path_info(meta.dest, "file")
if (get_path_info(meta.dest, "extension") == "cmx") {
cmx(manifest_target) {
manifest = meta.path
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" ]
} else if (get_path_info(meta.path, "extension") == "cml") {
cm(manifest_target) {
manifest = meta.path
forward_variables_from(pkg,
[
"deps",
"testonly",
])
}
}
manifest_output = []
manifest_output = get_target_outputs(":$manifest_target")
meta.path = manifest_output[0]
pkg.deps += [ ":$manifest_target" ]
formatted_manifest += [ meta ]
}
# 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 = []
foreach(meta, formatted_manifest) {
pkg_manifest += [
{
dest = "meta/${meta.dest}"
source = rebase_path(meta.path)
},
]
}
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 +=
[ "${dest}=" + rebase_path(shellfile, root_build_dir) ]
# 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}"
}
if (bootfs_only) {
dest = "test/sys/$dest"
path = "/boot/$dest"
} else {
url = "fuchsia-pkg://fuchsia.com"
dest = "test/$dest"
if (pkg.__deprecated_system_image) {
url_pkg_name = "system"
path = "/system/$dest"
} else {
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 (!pkg.__deprecated_system_image && !bootfs_only && !is_disabled &&
(!defined(require_bare_tests_allowlist) ||
!require_bare_tests_allowlist)) {
require_bare_tests_allowlist = true
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) {
require_bare_tests_allowlist = false
url_resource_path = "#meta/${meta.dest}"
}
}
}
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
if (!bootfs_only) {
package_url = url
}
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)
},
]
}
foreach(resource, pkg.resources) {
pkg_manifest += [
{
dest = "data/${resource.dest}"
source = rebase_path(resource.path)
},
]
}
# TODO(mcgrathr): Remove this when we can! Packages installing
# libraries in the system image is all kinds of wrong.
foreach(library, pkg.libraries) {
pkg_manifest += [
{
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)
},
]
}
# Collect all the arguments describing input manifest files
# and all the entries we've just synthesized in `pkg_manifest`.
manifest_sources = pkg.extra
manifest_args = []
foreach(manifest_file, pkg.extra) {
manifest_file = rebase_path(manifest_file, root_build_dir)
manifest_args += [ "--manifest=${manifest_file}" ]
}
manifest_args += [ "--entry-manifest=${pkg_label}" ]
foreach(entry, pkg_manifest) {
manifest_sources += [ entry.source ]
manifest_args += [ "--entry=${entry.dest}=${entry.source}" ]
}
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/*",
]
}
# An empty package() target doesn't actually generate a package at all.
# Conveniently, an empty system_image package has exactly that effect.
if (manifest_sources == []) {
pkg.__deprecated_system_image = true
}
if (pkg.__deprecated_system_image || bootfs_only) {
system_rsp_label = pkg_target_name + ".system.rsp"
# System image packages just donate manifest arguments
generate_response_file(system_rsp_label) {
if (defined(pkg.visibility)) {
visibility = pkg.visibility
}
deps = pkg.deps
data_deps = pkg.data_deps
public_deps = pkg.public_deps
output_name = "${pkg_target_name}.system.rsp"
response_file_contents = manifest_args
metadata = {
forward_variables_from(pkg.metadata, "*")
system_image_rsps = [ "@" +
rebase_path(target_out_dir + "/" + output_name,
root_build_dir) ]
system_image_barrier = []
}
}
not_needed([ "component_indices" ])
}
# Synthesize the meta/package file.
pkg_meta_generated = "${pkg_target_name}_meta_package.json"
pkg_meta_output = "$target_out_dir/${pkg_target_name}_meta_package.json"
generated_file(pkg_meta_generated) {
contents = "{\"name\":\"${pkg.package_name}\",\"version\":\"${pkg.package_version}\"}"
outputs = [ pkg_meta_output ]
output_conversion = "string"
}
# Fuchsia package aggregates a manifest from its arguments and builds a
# metadata archive.
manifest = "${pkg_target_name}.manifest"
generate_manifest(manifest) {
visibility = [ ":" + pkg_target_name ]
sources = manifest_sources + [ pkg_meta_output ]
args =
manifest_args + [ "--entry=meta/package=" +
rebase_path(pkg_meta_output, "", target_out_dir) ]
deps = pkg.deps + [ ":$pkg_meta_generated" ]
public_deps = pkg.public_deps
}
# Next generate a sealed package file.
pm_build(pkg_target_name) {
if (defined(pkg.visibility)) {
visibility = pkg.visibility
}
package_name = pkg.package_name
manifest = ":$manifest"
metadata = {
forward_variables_from(pkg.metadata, "*")
}
deps = pkg.deps
data_deps = pkg.data_deps
public_deps = pkg.public_deps
if (pkg.__deprecated_system_image || bootfs_only) {
public_deps += [ ":" + system_rsp_label ]
}
# Add the dep on the allowlist here rather than in pkg.deps earlier so that we can allow
# specific targets by the package target's name using visibility.
if (defined(require_bare_tests_allowlist) &&
require_bare_tests_allowlist) {
deps += [ "//build:deprecated_bare_tests_allowlist" ]
}
}
} 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, "*")
}
}