| # 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", |
| "__deprecated_system_image", |
| "deps", |
| "public_deps", |
| "drivers", |
| "libraries", |
| "loadable_modules", |
| "meta", |
| "package_name", |
| "resources", |
| "resource_deps", |
| "visibility", |
| "tests", |
| "testonly", |
| ]) |
| if (!defined(binaries)) { |
| binaries = [] |
| } |
| if (!defined(__deprecated_system_image)) { |
| __deprecated_system_image = false |
| } |
| 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 (pkg.__deprecated_system_image) { |
| assert( |
| pkg.meta == [], |
| "$pkg_desc __deprecated_system_image (likely from driver_package) incompatible with meta") |
| |
| # No package should contribute to /system/bin. |
| assert(pkg.binaries == [], |
| "no new packages are allowed to add binaries to /system/bin") |
| |
| assert(pkg.tests == [], "tests are not allowed in /system") |
| } else { |
| 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") { |
| 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/*", |
| ] |
| } |
| |
| # 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) { |
| system_rsp_label = pkg_target_name + ".system.rsp" |
| |
| # System image packages just donate manifest arguments |
| generate_response_file(system_rsp_label) { |
| forward_variables_from(pkg, |
| [ |
| "testonly", |
| "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" |
| 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 |
| |
| if (pkg.__deprecated_system_image) { |
| public_deps += [ ":" + system_rsp_label ] |
| } |
| |
| if (!pkg.__deprecated_system_image) { |
| # Prevent the contents of this package from getting picked up in a zbi. |
| # See fxbug.dev/45680 for more information. |
| metadata.distribution_entries_barrier = [] |
| } |
| |
| # A solution for deprecated_system_image is not yet available. |
| # |
| # See https://fuchsia.dev/fuchsia-src/development/components/build#other_unsupported_features. |
| if (!defined(invoker.__deprecated_system_image)) { |
| 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, "*") |
| } |
| } |