blob: 673df43615a1d2bab7e3aea38b07c0054c280557 [file] [log] [blame]
# Copyright 2016 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/config/fuchsia/sdk.gni")
import("//build/dart/dart.gni")
import("//build/dart/dart_package_config.gni")
import("//build/dart/toolchain.gni")
import("//build/json/validate_json.gni")
import("//build/python/python.gni")
import("//build/sdk/sdk_atom.gni")
import("//build/toolchain/concurrent_jobs.gni")
declare_args() {
# Enable all dart analysis
enable_dart_analysis = true
# Enable all strict deps.
disable_dart_strict_deps = false
# Detect dart API changes
# TODO(fxbug.dev/36723, fxbug.dev/6623) Remove this flag once issues are resolved
enable_api_diff = false
}
# Defines a Dart library
#
# Parameters
#
# sources
# The list of all sources in this library.
# These sources must be within source_dir.
#
# package_root (optional)
# Path to the directory hosting the library.
# This is useful for generated content, and can be ignored otherwise.
# Defaults to ".".
#
# package_name (optional)
# Name of the Dart package. This is used as an identifier in code that
# depends on this library.
#
# language_version (optional)
# Specify the Dart language version to use for this package.
# If language_version is not specified but pubspec is then the language
# version will be read from the pubspec. If no language version can be
# determined then we will default to version "2.8".
# It is recommended to specify a language_version if it is well known
# instead of relying on the pubspec file since this will improve compilation
# times.
#
# infer_package_name (optional)
# Infer the package name based on the path to the package.
#
# NOTE: Exactly one of package_name or infer_package_name must be set.
#
# source_dir (optional)
# Path to the directory containing the package sources, relative to
# package_root. All non third-party dart files under source_dir must be
# included in sources.
# Defaults to "lib".
#
# deps (optional)
# List of labels this library depends on.
#
# TODO(fxb/63133): non_dart_deps is deprecated. Use deps instead.
# non_dart_deps (optional, deprecated)
# List of labels this library depends on that are not Dart libraries. This
# includes things like actions that generate Dart code. It typically doesn't
# need to be set.
# Note that these labels *must* have an explicit toolchain attached.
#
# disable_analysis (optional)
# Prevents analysis from being run on this target.
#
# TODO(fxbug.dev/71902): set up allowlist for disable_source_verification when
# dart_test no longer depends on dart_library.
# NOTE: Do NOT disable source verification unless you are 100% sure it is
# absolutely necessary.
# disable_source_verification (optional)
# Prevents source verification from being run on this target.
#
# sdk_category (optional)
# Publication level of the library in SDKs.
# See //build/sdk/sdk_atom.gni.
#
# api (optional)
# Path to the file representing the API of this library.
# This file is used to ensure modifications to the library's API are
# explicitly acknowledged. This file must exist for publication categories
# of "partner" or "public"; if it does not exist, it will be automatically
# generated by analyzing the given sources list.
# Defaults to "<Dart library name>.api".
#
# extra_sources (optional)
# Additional sources to consider for analysis.
#
# pubspec (optional)
# Path to the pubspec.yaml. If not provided, will default to looking for
# the pubspec.yaml in the package root. It is not common that this value will
# need to be set but can be useful for generated code.
#
# options_file (optional)
# Path to the analysis_options.yaml file. If not provided, will default to
# looking for the analysis_options.yaml in the package root. It is not common
# that this value needs to be set but can be useful for generated code.
#
# disable_metadata_entry (optional)
# Prevents metedata entry from being written to the dart_packag_config json file.
#
# null_safe (optional)
# A flag that enables null safety check in dart libraries.
#
# Example of usage:
#
# dart_library("baz") {
# package_name = "foo.bar.baz"
#
# sources = [
# "blah.dart",
# ]
#
# deps = [
# "//foo/bar/owl",
# ]
# }
if (current_toolchain == dart_toolchain) {
template("dart_library") {
forward_variables_from(invoker,
[
"visibility",
"hermetic_deps",
])
if (defined(invoker.package_name)) {
package_name = invoker.package_name
} else if (defined(invoker.infer_package_name) &&
invoker.infer_package_name) {
# Compute a package name from the label:
# //foo/bar --> foo.bar
# //foo/bar:blah --> foo.bar._blah
# //garnet/public/foo/bar --> foo.bar
# Strip public directories.
full_dir = get_label_info(":$target_name", "dir")
foreach(sdk_dir, sdk_dirs) {
full_dir = string_replace(full_dir, "$sdk_dir/", "")
}
package_name = full_dir
package_name = string_replace(package_name, "//", "")
package_name = string_replace(package_name, "/", ".")
# If the last directory name does not match the target name, add the
# target name to the resulting package name.
name = get_label_info(":$target_name", "name")
last_dir = get_path_info(full_dir, "name")
if (last_dir != name) {
package_name = "$package_name._$name"
}
} else {
assert(false, "Must specify either a package_name or infer_package_name")
}
_dart_deps = []
if (defined(invoker.deps)) {
foreach(dep, invoker.deps) {
_dart_deps += [ get_label_info(dep, "label_no_toolchain") ]
}
}
_non_dart_deps = []
if (defined(invoker.non_dart_deps)) {
_non_dart_deps += invoker.non_dart_deps
}
package_root = "."
if (defined(invoker.package_root)) {
package_root = invoker.package_root
}
source_dir = "$package_root/lib"
if (defined(invoker.source_dir)) {
source_dir = "$package_root/${invoker.source_dir}"
}
assert(defined(invoker.sources), "Sources must be defined")
disable_source_verification =
defined(invoker.disable_source_verification) &&
invoker.disable_source_verification
if (disable_source_verification && invoker.sources == []) {
not_needed([ source_dir ])
}
rebased_sources = []
foreach(source, invoker.sources) {
rebased_source_dir = rebase_path(source_dir)
rebased_sources += [ "$rebased_source_dir/$source" ]
}
if (defined(invoker.extra_sources)) {
foreach(source, invoker.extra_sources) {
rebased_sources += [ rebase_path(source) ]
}
}
source_file = "$target_gen_dir/$target_name.sources"
write_file(source_file, rebased_sources, "list lines")
# Dependencies of the umbrella group for the targets in this file.
group_deps = []
_public_deps = []
if (defined(invoker.public_deps)) {
_public_deps = invoker.public_deps
}
_metadata = {
package_config_entries = [
{
name = package_name
if (defined(invoker.language_version)) {
language_version = invoker.language_version
} else if (defined(invoker.null_safe) && invoker.null_safe) {
language_version = "2.12"
} else if (defined(invoker.pubspec)) {
pubspec_path = rebase_path(invoker.pubspec, root_build_dir)
}
root_uri = rebase_path(package_root, root_build_dir)
if (defined(invoker.source_dir)) {
package_uri = invoker.source_dir
} else {
package_uri = "lib"
}
},
]
dart_build_info = [
{
__is_current_target = false
__package_name = package_name
__deps = _dart_deps + _non_dart_deps
__public_deps = _public_deps
__rebased_sources = rebased_sources
},
]
dart_build_info_barrier = []
}
# When we generate a package_config for the analyzer we need to make sure
# that we are including this library in that file. The dart_package_config
# collects metadata from its dependencies so we create this group to expose
# that data. We also expose this in the group target below so that users of
# the dart_package_config target can just add the targets to the deps list.
_publish_metadata_target_name = "${target_name}_package_metadata"
group(_publish_metadata_target_name) {
metadata = _metadata
}
_dart_package_config_target_name = "${target_name}_dart_package"
_packages_path = "$target_gen_dir/${target_name}_package_config.json"
dart_package_config(_dart_package_config_target_name) {
# Do not publish the metadata to the dart_package_config json file if the
# disable_metadata_entry flag is enabled in the dart_tool. The reason this is here
# is to avoid entries that may have identical rootUris as fxb/58781 has highlighted.
deps = _dart_deps
if (!defined(invoker.disable_metadata_entry) ||
!invoker.disable_metadata_entry) {
deps += [ ":$_publish_metadata_target_name" ]
}
public_deps = _non_dart_deps
outputs = [ _packages_path ]
forward_variables_from(invoker, [ "testonly" ])
}
group_deps += [ ":$_dart_package_config_target_name" ]
################################
# Dart source "verification"
#
# Warn if there are dart sources from the source directory that are
# not explicitly part of sources. This may cause a failure when syncing to
# another repository, as they will be excluded from the resulting BUILD
# file.
#
# Also warn if nonexistent files are included in sources.
if (!disable_source_verification) {
source_verification_target_name = "${target_name}_source_verification"
action(source_verification_target_name) {
script = "//build/dart/verify_sources.py"
output_file = "$target_gen_dir/$target_name.missing"
sources = rebased_sources
outputs = [ output_file ]
args = [
"--source_dir",
rebase_path(source_dir),
"--stamp",
rebase_path(output_file),
] + invoker.sources
forward_variables_from(invoker, [ "testonly" ])
# Deps may include codegen dependencies that generate dart sources.
deps = _dart_deps + _non_dart_deps
}
group_deps += [ ":$source_verification_target_name" ]
}
# Generate a file that lists files containing full (including all direct and
# transitive dependencies) sources for this target's dependencies.
_all_deps_sources_list_target = "${target_name}.all_deps_sources.list"
_all_deps_sources_list_file =
"${target_gen_dir}/${target_name}.all_deps_sources.list"
generated_file(_all_deps_sources_list_target) {
forward_variables_from(invoker, [ "testonly" ])
outputs = [ _all_deps_sources_list_file ]
data_keys = [ "all_deps_sources" ]
walk_keys = [ "all_deps_sources_barrier" ]
deps = _dart_deps
}
# Generate full sources for this target by combining sources of this target
# with full sources of all dependencies.
#
# The generated file contains sources of this target and all of its direct
# and transitive dependencies.
#
# The output file is useful when writing depfiles for actions like dart
# analyzer, which recursively reads all sources.
_all_deps_sources_target = "${target_name}.all_deps_sources"
_all_deps_sources_file = "${target_gen_dir}/${target_name}.all_deps_sources"
action(_all_deps_sources_target) {
forward_variables_from(invoker, [ "testonly" ])
script = "//build/dart/merge_deps_sources.py"
outputs = [ _all_deps_sources_file ]
depfile = "${_all_deps_sources_file}.d"
args = [
"--output",
rebase_path(outputs[0], root_build_dir),
"--depfile",
rebase_path(depfile, root_build_dir),
"--source_lists",
"@" + rebase_path(_all_deps_sources_list_file, root_build_dir),
"--sources",
] + rebased_sources
inputs = [ _all_deps_sources_list_file ]
deps = [ ":${_all_deps_sources_list_target}" ]
metadata = {
all_deps_sources = [ rebase_path(outputs[0], root_build_dir) ]
all_deps_sources_barrier = []
}
}
group_deps += [ ":${_all_deps_sources_target}" ]
################################
# Don't run the analyzer if it is explicitly disabled or if we are using
# a custom-built Dart SDK in a cross-build.
with_analysis =
!defined(invoker.disable_analysis) || !invoker.disable_analysis
with_analysis = enable_dart_analysis && with_analysis
if (with_analysis) {
options_file = "$package_root/analysis_options.yaml"
if (defined(invoker.options_file)) {
options_file = invoker.options_file
}
invocation_file = "$target_gen_dir/$target_name.analyzer.sh"
invocation_target_name = "${target_name}_analysis_runner"
group_deps += [ ":$invocation_target_name" ]
dart_analyzer_binary = "$dart_sdk/bin/dartanalyzer"
# Creates a script which can be used to manually perform analysis.
# TODO(fxbug.dev/3065): remove this target.
action(invocation_target_name) {
script = "//build/dart/gen_analyzer_invocation.py"
deps = _dart_deps + dart_sdk_deps + [
":$_dart_package_config_target_name",
":$_all_deps_sources_target",
]
inputs = [
_packages_path,
dart_analyzer_binary,
options_file,
source_file,
]
outputs = [ invocation_file ]
args = [
"--out",
rebase_path(invocation_file),
"--source-file",
rebase_path(source_file),
"--dot-packages",
rebase_path(_packages_path, root_build_dir),
"--dartanalyzer",
rebase_path(dart_analyzer_binary),
"--dart-sdk",
rebase_path(dart_sdk),
"--options",
rebase_path(options_file),
"--package-name",
package_name,
]
forward_variables_from(invoker, [ "testonly" ])
}
analysis_target_name = "${target_name}_analysis"
group_deps += [ ":$analysis_target_name" ]
# Runs analysis on the sources.
action(analysis_target_name) {
forward_variables_from(concurrent_jobs.local, "*")
# This script is built by "//build/dart:run_analysis". However, because
# that target is not defined in this file we can't use
# `get_target_outputs`, which only supports targets previously defined
# in the current file. We don't want to include that target as part of
# the template, to avoid repeatedly building it for every dart_library.
script = "${root_out_dir}/obj/build/dart/run_analysis.pyz"
depfile = "$target_gen_dir/$target_name.analysis.d"
_pubspec = "${package_root}/pubspec.yaml"
if (defined(invoker.pubspec)) {
_pubspec = invoker.pubspec
}
inputs = [
_all_deps_sources_file,
_packages_path,
_pubspec,
dart_analyzer_binary,
options_file,
source_file,
] + rebased_sources
output_file = "$target_gen_dir/$target_name.analyzed"
outputs = [ output_file ]
args = [
"--source-file",
rebase_path(source_file),
"--dot-packages",
rebase_path(_packages_path),
"--dartanalyzer",
rebase_path(dart_analyzer_binary),
"--dart-sdk",
rebase_path(dart_sdk),
"--options",
rebase_path(options_file),
"--stamp",
rebase_path(output_file),
"--depname",
rebase_path(output_file, root_build_dir),
"--depfile",
rebase_path(depfile),
"--all-deps-sources-file",
rebase_path(_all_deps_sources_file, root_build_dir),
]
deps = _dart_deps + dart_sdk_deps + [
":$_all_deps_sources_target",
":$_dart_package_config_target_name",
"//build/dart:run_analysis",
]
forward_variables_from(invoker, [ "testonly" ])
}
} else {
not_needed(invoker, [ "options_file" ])
}
################################
# Dart strict deps
#
# Ensures that this target specifies all of the Dart packages that it
# imports as direct dependencies.
group("${invoker.target_name}_dart_build_info_self_marker") {
metadata = {
dart_build_info = [
{
__is_current_target = true
__package_name = package_name
__deps = _dart_deps + _non_dart_deps
__public_deps = _public_deps
__rebased_sources = rebased_sources
},
]
}
forward_variables_from(invoker, [ "testonly" ])
}
_metadata_collection_results_path =
"$target_gen_dir/${target_name}_dart_build_info.out"
generated_file("${invoker.target_name}_dart_build_info") {
data_keys = [ "dart_build_info" ]
walk_keys = [ "dart_build_info_barrier" ]
outputs = [ _metadata_collection_results_path ]
output_conversion = "json"
deps =
_dart_deps + [ ":${invoker.target_name}_dart_build_info_self_marker" ]
forward_variables_from(invoker, [ "testonly" ])
}
_strict_deps_tool_label =
"//tools/dart-strict-deps:strict_deps($host_toolchain)"
_strict_deps_tool_path =
get_label_info(_strict_deps_tool_label, "root_out_dir") +
"/dart-tools/strict_deps"
_strict_deps_stamp =
"${invoker.target_gen_dir}/${invoker.target_name}_strict_deps.report"
_strict_deps_snapshot_path =
get_label_info(_strict_deps_tool_label, "target_gen_dir") +
"/strict_deps.snapshot"
action("${invoker.target_name}_strict_deps") {
deps = [
":$_dart_package_config_target_name",
":${invoker.target_name}_dart_build_info",
"//tools/dart-strict-deps:strict_deps_snapshot($host_toolchain)",
_strict_deps_tool_label,
] + _dart_deps
sources = []
if (defined(invoker.sources)) {
foreach(src, invoker.sources) {
sources += [ rebase_path("$source_dir/$src") ]
}
}
inputs = [
_metadata_collection_results_path,
_packages_path,
_strict_deps_snapshot_path,
]
outputs = [ _strict_deps_stamp ]
script = _strict_deps_tool_path
args = [
"--metadata-file",
rebase_path(_metadata_collection_results_path, root_build_dir),
"--output-file",
rebase_path(_strict_deps_stamp, root_build_dir),
"--packages-file",
rebase_path(_packages_path, root_build_dir),
]
forward_variables_from(invoker, [ "testonly" ])
}
# The strict deps tool is excluded from the strict deps check to
# prevent a circular dependency.
_strict_deps_package_excludes = [
"strict_deps_dart_library",
"dart_strict_deps_lib",
"dart_strict_deps_proto",
"protoc_gen_dart_dart_library",
]
_package_excludes_contains_target =
_strict_deps_package_excludes + [ target_name ] - [ target_name ] !=
_strict_deps_package_excludes
_full_target_label = get_label_info(":$target_name", "label_no_toolchain")
_is_third_party_lib =
string_replace(_full_target_label, "//third_party/dart-pkg/pub", "") !=
_full_target_label
group(target_name) {
# _dart_deps are added here to ensure they are fully built.
# Up to this point, only the targets generating .packages had been
# depended on.
deps = _dart_deps + _non_dart_deps
if (!disable_dart_strict_deps && !_package_excludes_contains_target &&
!_is_third_party_lib) {
deps += [ ":${invoker.target_name}_strict_deps" ]
} else {
not_needed([
"_package_excludes_contains_target",
"_is_third_party_lib",
])
}
public_deps = group_deps
metadata = _metadata
forward_variables_from(invoker, [ "testonly" ])
}
################################################
# SDK support
#
with_api_diff = enable_api_diff && (defined(invoker.sdk_category) &&
invoker.sdk_category != "excluded")
if (with_api_diff) {
detect_target_name = "${target_name}_detect_action"
verify_target_name = "${target_name}_verify"
detect_target_file = "$target_gen_dir/$package_name.api"
api_file = rebase_path(".") + "/$package_name.api"
action(detect_target_name) {
runner_label = "//build/dart/sdk/detect_api_changes($dart_toolchain)"
runner_out_dir = get_label_info(runner_label, "root_out_dir")
script = "$runner_out_dir/dart-tools/detect_api_changes"
output_file = detect_target_file
inputs = [
_packages_path,
source_file,
] + rebased_sources
outputs = [ output_file ]
args = [
"--api-name",
get_label_info(":$target_name", "label_no_toolchain"),
"--source-file",
rebase_path(source_file),
"--dot-packages",
rebase_path(_packages_path),
"--output-file",
rebase_path("$detect_target_file"),
"--golden-api",
api_file,
# Uncomment to force updating the .api files
#"--overwrite",
]
deps = dart_sdk_deps + [ runner_label ]
forward_variables_from(invoker, [ "testonly" ])
}
# Verify that the library API file complies with the specified schema.
validate_json(verify_target_name) {
forward_variables_from(invoker, [ "testonly" ])
data = rebase_path("$detect_target_file")
schema = rebase_path("//build/dart/sdk/detect_api_changes/schema.json")
deps = dart_sdk_deps + [ ":$detect_target_name" ]
public_deps = [ ":$detect_target_name" ]
}
}
if (defined(invoker.sdk_category) && invoker.sdk_category != "excluded") {
assert(
defined(invoker.package_name),
"Dart libraries published in SDKs must have an explicit package name")
assert(
!defined(invoker.extra_sources),
"Extra sources can not be included in SDKs: put them in source_dir")
# Dependencies that should normally be included in any SDK containing this
# target.
sdk_deps = []
# Path to SDK metadata files for first-party dependencies.
sdk_metas = []
# Path to Dart manifest files for third-party dependencies.
third_party_pubspecs = []
local_deps = filter_exclude(_dart_deps, [ "//third_party/*" ])
third_party_deps = filter_include(_dart_deps, [ "//third_party/*" ])
foreach(dep, local_deps) {
sdk_dep = "${dep}_sdk"
sdk_deps += [ sdk_dep ]
gen_dir = get_label_info(sdk_dep, "target_gen_dir")
name = get_label_info(sdk_dep, "name")
sdk_metas += [ rebase_path("$gen_dir/$name.meta.json") ]
}
foreach(dep, third_party_deps) {
path = get_label_info(dep, "dir")
third_party_pubspecs += [ rebase_path("$path/pubspec.yaml") ]
}
file_base = "dart/${invoker.package_name}"
sdk_sources = []
sdk_source_mappings = []
foreach(source, rebased_sources) {
relative_source = rebase_path(source, source_dir)
dest = "$file_base/lib/$relative_source"
sdk_sources += [ dest ]
sdk_source_mappings += [
{
source = source
dest = dest
},
]
}
metadata_target_name = "${target_name}_sdk_metadata"
metadata_file = "$target_gen_dir/$target_name.sdk_meta.json"
action(metadata_target_name) {
# This script is built by "//build/dart:gen_meta_file". However, because
# that target is not defined in this file we can't use
# `get_target_outputs`, which only supports targets previously defined
# in the current file. We don't want to include that target as part of
# the template, to avoid repeatedly building it for every dart_library.
script = "${root_out_dir}/obj/build/dart/gen_meta_file.pyz"
inputs = sdk_metas + third_party_pubspecs
outputs = [ metadata_file ]
args = [
"--out",
rebase_path(metadata_file),
"--name",
package_name,
"--root",
file_base,
]
args += [ "--specs" ] + sdk_metas
args += [ "--sources" ] + sdk_sources
args += [ "--third-party-specs" ] + third_party_pubspecs
if (defined(invoker.null_safe) && invoker.null_safe) {
args += [ "--dart-library-null-safe" ]
}
deps = sdk_deps + [ "//build/dart:gen_meta_file" ]
forward_variables_from(invoker, [ "testonly" ])
}
sdk_atom("${target_name}_sdk") {
id = "sdk://dart/${invoker.package_name}"
category = invoker.sdk_category
meta = {
source = metadata_file
dest = "$file_base/meta.json"
schema = "dart_library"
}
deps = sdk_deps
non_sdk_deps = [ ":$metadata_target_name" ]
if (with_api_diff) {
non_sdk_deps += [ ":$verify_target_name" ]
}
if (defined(invoker.non_dart_deps)) {
non_sdk_deps += invoker.non_dart_deps
}
files = sdk_source_mappings
}
}
}
} else { # Not the Dart toolchain.
template("dart_library") {
group(target_name) {
forward_variables_from(invoker, [ "testonly" ])
not_needed(invoker, "*")
public_deps = [ ":$target_name($dart_toolchain)" ]
}
}
}