# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/features.gni")
import(
"//tools/code_coverage/js_source_maps/create_js_source_maps/create_js_source_maps.gni")
import(
"//tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni")
import("//tools/grit/grit_rule.gni")
import("//tools/grit/preprocess_if_expr.gni")
import("//tools/polymer/css_to_wrapper.gni")
import("//tools/polymer/html_to_wrapper.gni")
import("//tools/typescript/ts_library.gni")
import("//tools/typescript/webui_path_mappings.gni")
import("//ui/webui/resources/tools/bundle_js.gni")
import("//ui/webui/resources/tools/bundle_js_excludes.gni")
import("//ui/webui/resources/tools/eslint_ts.gni")
import("//ui/webui/resources/tools/generate_code_cache.gni")
import("//ui/webui/resources/tools/generate_grd.gni")
import("//ui/webui/resources/tools/minify_js.gni")
import("//ui/webui/webui_features.gni")
if (use_blink) {
import("//chrome/common/features.gni")
}
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
import("//arkweb/chromium_ext/ui/webui/resources/tools/arkweb_replace_scheme.gni")
}
#endif
if (is_chromeos) {
# Folders hosting Ash-only WebUIs. Should be kept in sync with
# //tools/typescript/path_utils.py. This is used later in this file to choose
# a CrOS-specific default TS config file for some targets.
ash_folders = [
# Source code folders
"//ash/webui",
"//chrome/browser/resources/ash",
"//chrome/browser/resources/chromeos",
"//chrome/browser/resources/dlp_internals",
"//ui/file_manager",
# Test folders
"//chrome/test/data/webui/chromeos",
"//chrome/test/data/webui/cr_components/chromeos",
]
}
# See documentation at https://chromium.googlesource.com/chromium/src/+/HEAD/docs/webui_build_configuration.md#build_webui
template("build_webui") {
not_needed([ "target_name" ])
forward_variables_from(invoker, [ "grd_prefix" ])
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
scheme_replaced_dir = "${target_gen_dir}/scheme_replaced"
}
}
#endif
preprocess_dir = "${target_gen_dir}/preprocessed"
ts_out_dir = "${target_gen_dir}/tsc"
if (defined(invoker.ts_out_dir)) {
ts_out_dir = invoker.ts_out_dir
}
optimize = optimize_webui
if (defined(invoker.optimize)) {
optimize = invoker.optimize
}
minify = optimize
bundle = optimize && defined(invoker.optimize_webui_in_files)
has_ts_in_files =
defined(invoker.web_component_files) || defined(invoker.ts_files)
enable_source_maps = enable_webui_inline_sourcemaps && has_ts_in_files
if (defined(invoker.enable_source_maps) && has_ts_in_files) {
assert(!invoker.enable_source_maps || !optimize)
enable_source_maps = invoker.enable_source_maps
}
# Determine whether a :build_grd or :build_grdp target should be defined.
generate_grdp = false
generate_grd_target_name = "build_grd"
if (defined(invoker.generate_grdp) && invoker.generate_grdp) {
generate_grdp = true
generate_grd_target_name = "build_grdp"
}
generate_code_cache = false
if (defined(invoker.generate_code_cache)) {
generate_code_cache = invoker.generate_code_cache
}
### Compute the lists of files that are used across multiple targets.
# At least one of `web_component_files`, `css_files`, `mojo_files`, or
# 'ts_files` must be defined. Otherwise, the build rule does not actually
# generate any input for TS compiler.
assert(has_ts_in_files || defined(invoker.css_files) ||
defined(invoker.mojo_files))
all_ts_files = []
if (defined(invoker.web_component_files)) {
all_ts_files += invoker.web_component_files
}
if (defined(invoker.ts_files)) {
all_ts_files += invoker.ts_files
}
if (defined(invoker.web_component_files) ||
defined(invoker.icons_html_files)) {
# Files that are passed as input to html_to_wrapper().
html_files = []
if (defined(invoker.web_component_files)) {
web_component_ts_files =
filter_include(invoker.web_component_files, [ "*.ts" ])
foreach(f, web_component_ts_files) {
html_files += [ string_replace(f, ".ts", ".html") ]
}
web_component_js_files =
filter_include(invoker.web_component_files, [ "*.js" ])
foreach(f, web_component_js_files) {
html_files += [ string_replace(f, ".js", ".html") ]
}
}
if (defined(invoker.icons_html_files)) {
html_files += invoker.icons_html_files
}
# Files that are generated by html_to_wrapper().
html_wrapper_files = []
foreach(f, html_files) {
html_wrapper_files += [ f + ".ts" ]
}
}
if (defined(invoker.css_files)) {
# Files that are generated by css_to_wrapper().
css_wrapper_files = []
foreach(f, invoker.css_files) {
css_wrapper_files += [ f + ".ts" ]
}
}
# Generated Mojo JS files.
if (defined(invoker.mojo_files)) {
assert(defined(invoker.mojo_files_deps))
mojo_files = []
foreach(mojo_file, invoker.mojo_files) {
mojo_files += [ get_path_info(invoker.mojo_files, "file") ]
}
}
if (defined(invoker.static_files)) {
# Compute which static_files should be preprocessed.
non_preprocessed_files_filter = [
"*.jpg",
"*.png",
"*.svg",
]
static_non_preprocessed_files =
filter_include(invoker.static_files, non_preprocessed_files_filter)
static_preprocessed_files =
filter_exclude(invoker.static_files, non_preprocessed_files_filter)
static_js_files = filter_include(invoker.static_files, [ "*.js" ])
assert(static_js_files == [],
"JS files not allowed in static_files, found: " +
string_join(", ", static_js_files))
}
preprocessor_defines = []
if (use_blink) {
preprocessor_defines += chrome_grit_defines
}
if (defined(invoker.preprocessor_defines)) {
preprocessor_defines += invoker.preprocessor_defines
}
### Define the various targets that are required by the build pipeline.
# Specifically the order in which these targets are executed is:
#
# 1) preprocess_if_expr()
# 2) create_js_source_maps() (only if `invoker.enable_source_maps=true`)
# 3) html_to_wrapper(), css_to_wrapper()
# 4) ts_library()
# 5) eslint_ts() (only if `invoker.enable_type_aware_eslint_checks=true`)
# 6) merge_js_source_maps() (only if `invoker.enable_source_maps=true`)
# 7) optimize_webui() (only if invoker.optimize && defined(invoker.optimize_webui_in_files))
# 8) minify_js() (only if invoker.optimize && !defined(invoker.optimize_webui_in_files))
# 9) generate_grd()
# 10) grit()
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
# 0) extra preprocess replace_scheme() for arkweb extension webUI:
# chrome:// -> arkweb://.
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
arkweb_replace_scheme("replace_scheme_in_resources_files") {
visibility = [
":preprocess_html_css_files",
":preprocess_static_files",
":preprocess_ts_files",
]
# defines = preprocessor_defines
in_folder = "."
out_folder = scheme_replaced_dir
in_files = invoker.static_files + invoker.ts_files +
invoker.css_files + invoker.icons_html_files
}
}
}
#endif
if (defined(invoker.static_files)) {
preprocess_if_expr("preprocess_static_files") {
visibility = [ ":$generate_grd_target_name" ]
defines = preprocessor_defines
in_folder = "."
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
in_folder = scheme_replaced_dir
}
}
#endif
out_folder = preprocess_dir
in_files = static_preprocessed_files
out_manifest = "${target_gen_dir}/preprocess_static_files_manifest.json"
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
deps = [ ":replace_scheme_in_resources_files" ]
}
}
#endif
}
}
if (has_ts_in_files) {
preprocess_if_expr("preprocess_ts_files") {
visibility = [
":build_ts",
":html_wrapper_files",
":lint",
]
if (enable_source_maps) {
visibility += [ ":create_source_maps" ]
}
defines = preprocessor_defines
in_folder = "."
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
in_folder = scheme_replaced_dir
}
}
#endif
out_folder = preprocess_dir
if (enable_source_maps) {
out_folder = "${preprocess_dir}/_tmp"
enable_removal_comments = enable_source_maps
}
in_files = all_ts_files
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
deps = [ ":replace_scheme_in_resources_files" ]
}
}
#endif
}
}
if (enable_source_maps) {
create_js_source_maps("create_source_maps") {
# TODO(dpapad): Make this only visible to ":build_ts" and
# ":html_wrapper_files".
inline_sourcemaps = true
sources = filter_include(
get_target_outputs(":preprocess_ts_files"),
[
"*.ts",
# TODO(crbug.com/40167175): Remove once we no longer pass JS
# files to build_webui.
"*.js",
])
originals = []
outputs = []
foreach(in_file, sources) {
rebased_path = rebase_path(in_file, "${preprocess_dir}/_tmp")
originals += [ rebased_path ]
outputs += [ "${preprocess_dir}/" + rebased_path ]
}
deps = [ ":preprocess_ts_files" ]
}
}
if (defined(html_files) || defined(invoker.css_files)) {
preprocess_if_expr("preprocess_html_css_files") {
visibility = [
":css_wrapper_files",
":html_wrapper_files",
]
defines = preprocessor_defines
in_folder = "."
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
in_folder = scheme_replaced_dir
}
}
#endif
out_folder = preprocess_dir
in_files = []
if (defined(html_files)) {
in_files += html_files
}
if (defined(invoker.css_files)) {
in_files += invoker.css_files
}
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
if (arkweb_arkweb_extensions) {
if (invoker.grd_prefix == "extensions") {
deps = [ ":replace_scheme_in_resources_files" ]
}
}
#endif
}
}
# Determine whether the current invocation corresponds to an Ash WebUI.
is_ash_webui = false
if (is_chromeos) {
target_dir = get_label_info(":build_ts", "dir")
foreach(prefix, ash_folders) {
if (!is_ash_webui) {
is_ash_webui = string_replace(target_dir, prefix, "") != target_dir
}
}
}
# Determine if there is a Lit and/or Polymer dependency. This information is
# used for determining the default template for html_to_wrapper() as well as
# the default tsconfig file for ts_library().
if ((defined(html_files) && !defined(invoker.html_to_wrapper_template)) ||
(defined(invoker.ts_deps) && !defined(invoker.ts_tsconfig_base))) {
has_polymer = false
has_lit = false
if (defined(invoker.ts_deps)) {
has_polymer =
filter_include(invoker.ts_deps,
[ "//third_party/polymer/v3_0:library" ]) != []
has_lit = filter_include(invoker.ts_deps,
[ "//third_party/lit/v3_0:build_ts" ]) != []
}
}
if (defined(html_files)) {
html_to_wrapper("html_wrapper_files") {
visibility = [
":build_ts",
":lint",
]
deps = [ ":preprocess_html_css_files" ]
in_folder = preprocess_dir
out_folder = preprocess_dir
in_files = html_files
minify = optimize
if (defined(invoker.html_to_wrapper_scheme)) {
scheme = invoker.html_to_wrapper_scheme
}
if (defined(invoker.html_to_wrapper_template)) {
template = invoker.html_to_wrapper_template
} else {
template = "native"
if (has_lit) {
template = "lit"
if (has_polymer) {
template = "detect"
}
} else if (has_polymer) {
template = "polymer"
if (defined(invoker.icons_html_files) &&
(!is_chromeos || !is_ash_webui)) {
# Need to use detect for non-Ash Polymer UIs using icons files,
# because only cr-iconset is supported for non-Ash WebUIs.
template = "detect"
}
}
}
if (template == "detect") {
if (enable_source_maps) {
deps += [ ":create_source_maps" ]
} else {
deps += [ ":preprocess_ts_files" ]
}
}
}
}
if (defined(invoker.css_files)) {
css_to_wrapper("css_wrapper_files") {
visibility = [ ":build_ts" ]
deps = [ ":preprocess_html_css_files" ]
in_folder = preprocess_dir
out_folder = preprocess_dir
in_files = invoker.css_files
minify = optimize
}
}
if (defined(invoker.mojo_files_deps)) {
mojo_base_path = "."
if (defined(invoker.mojo_base_path)) {
mojo_base_path = invoker.mojo_base_path
}
copy("copy_mojo") {
visibility = [ ":build_ts" ]
deps = invoker.mojo_files_deps
sources = invoker.mojo_files
outputs = [ "${preprocess_dir}/${mojo_base_path}/{{source_file_part}}" ]
}
}
if (defined(invoker.ts_deps)) {
webui_path_mappings("build_path_map") {
ts_deps = invoker.ts_deps
visibility = [ ":build_ts" ]
webui_context_type = "relative"
if (defined(invoker.webui_context_type)) {
webui_context_type = invoker.webui_context_type
}
}
}
ts_library("build_ts") {
root_dir = preprocess_dir
out_dir = ts_out_dir
composite = false
if (defined(invoker.ts_composite)) {
composite = invoker.ts_composite
}
if (!composite) {
visibility = [
":$generate_grd_target_name",
":build_bundle",
":build_min_js",
":lint",
]
if (enable_source_maps) {
visibility += [ ":merge_source_maps" ]
}
}
if (defined(invoker.ts_tsconfig_base)) {
tsconfig_base = invoker.ts_tsconfig_base
} else if (defined(invoker.ts_deps)) {
# Default tsconfig is set to tsconfig_base_polymer.json for targets
# with a Polymer dependency, set to tsconfig_base_lit.json for
# targets with a Lit dependency and no Polymer dependency, and left
# unset (defaults to //tools/typescript/tsconfig_base.json) for other
# non-Polymer, non-Lit targets.
if (has_polymer) {
if (has_lit) {
# Config with Lit parameters also for migrating UIs.
tsconfig_base = "//tools/typescript/tsconfig_base_mixed.json"
} else {
tsconfig_base = "//tools/typescript/tsconfig_base_polymer.json"
}
# Detect whether the target corresponds to an Ash-only WebUI and use
# tsconfig_base_polymer_cros.json instead in such cases.
if (is_chromeos && is_ash_webui) {
tsconfig_base = "//tools/typescript/tsconfig_base_polymer_cros.json"
}
} else if (has_lit) {
tsconfig_base = "//tools/typescript/tsconfig_base_lit.json"
}
}
in_files = all_ts_files
if (defined(invoker.ts_deps)) {
path_mappings_file = "$target_gen_dir/path_mappings_build_path_map.json"
extra_deps = [ ":build_path_map" ]
} else {
extra_deps = []
}
if (enable_source_maps) {
# ts_library()'s |enable_source_maps| param inherited from the outer
# scope's enable_source_maps
extra_deps += [ ":create_source_maps" ]
} else if (has_ts_in_files) {
extra_deps += [ ":preprocess_ts_files" ]
}
if (defined(html_files)) {
in_files += html_wrapper_files
extra_deps += [ ":html_wrapper_files" ]
}
if (defined(invoker.css_files)) {
in_files += css_wrapper_files
extra_deps += [ ":css_wrapper_files" ]
}
deps = [ "//tools/typescript/definitions:build_ts" ]
if (defined(invoker.ts_deps)) {
deps += invoker.ts_deps
}
definitions = [ "//tools/typescript/definitions/strings.d.ts" ]
if (defined(invoker.ts_definitions)) {
definitions += invoker.ts_definitions
}
if (defined(invoker.ts_path_mappings)) {
path_mappings = invoker.ts_path_mappings
}
if (defined(invoker.mojo_files_deps)) {
assert(defined(invoker.mojo_files))
target_outputs = get_target_outputs(":copy_mojo")
# Add all Mojo JS files produced by `:copy_mojo` as inputs to the TS
# compiler.
foreach(o, target_outputs) {
in_files += [ rebase_path(o, preprocess_dir) ]
}
extra_deps += [ ":copy_mojo" ]
}
if (defined(invoker.ts_extra_deps)) {
extra_deps += invoker.ts_extra_deps
}
}
enable_type_aware_eslint_checks =
!defined(invoker.enable_type_aware_eslint_checks) ||
invoker.enable_type_aware_eslint_checks
if (enable_type_aware_eslint_checks) {
eslint_ts("lint") {
in_folder = preprocess_dir
in_files = filter_exclude(all_ts_files, [ "*.js" ])
tsconfig = "$target_gen_dir/tsconfig_build_ts.json"
deps = [ ":build_ts" ]
if (defined(html_wrapper_files)) {
deps += [ ":html_wrapper_files" ]
in_files += html_wrapper_files
}
if (enable_source_maps) {
deps += [ ":create_source_maps" ]
} else {
deps += [ ":preprocess_ts_files" ]
}
# Enable by default on non-Ash WebUIs.
enable_web_component_missing_deps = true
if (is_ash_webui) {
enable_web_component_missing_deps = false
}
if (defined(invoker.eslint_enable_web_component_missing_deps)) {
enable_web_component_missing_deps =
invoker.eslint_enable_web_component_missing_deps
}
}
}
if (enable_source_maps) {
merge_js_source_maps("merge_source_maps") {
deps = [ ":build_ts" ]
manifest_files =
filter_include(get_target_outputs(":build_ts"), [ "*_manifest.json" ])
js_files = filter_include(get_target_outputs(":build_ts"), [ "*.js" ])
sources = []
outputs = []
out_dir = "$target_gen_dir/merge_source_maps"
foreach(_js_file, js_files) {
sources += [ _js_file ]
outputs += [ string_replace(_js_file, ts_out_dir, out_dir) ]
}
}
}
if (bundle) {
bundle_js("build_bundle") {
visibility = [ ":build_min_js" ]
host = invoker.optimize_webui_host
input = rebase_path(ts_out_dir, root_build_dir)
js_module_in_files = invoker.optimize_webui_in_files
out_folder = "$target_gen_dir/bundled"
excludes = BUNDLE_JS_EXCLUDES
if (defined(invoker.optimize_webui_excludes)) {
excludes += invoker.optimize_webui_excludes
}
if (defined(invoker.optimize_webui_external_paths)) {
external_paths = invoker.optimize_webui_external_paths
}
deps = [ ":build_ts" ]
}
}
if (minify) {
minify_js("build_min_js") {
visibility = [
":$generate_grd_target_name",
":build_code_cache",
]
in_folder = ts_out_dir
in_target = ":build_ts"
if (bundle) {
in_folder = "$target_gen_dir/bundled"
in_target = ":build_bundle"
}
out_folder = "$target_gen_dir/minified"
js_files = filter_include(get_target_outputs(in_target), [ "*.js" ])
in_files = []
foreach(_js_file, js_files) {
in_files += [ string_replace(_js_file, "$in_folder/", "") ]
}
deps = [ in_target ]
}
}
if (generate_code_cache) {
generate_code_cache_grd_prefix = grd_prefix + "_code_cache"
generate_code_cache_grd_target_name = "build_code_cache_grd"
if (generate_grdp) {
generate_code_cache_grd_target_name = "build_code_cache_grdp"
}
generate_code_cache("build_code_cache") {
visibility = [ ":$generate_code_cache_grd_target_name" ]
# Rebase the minified file paths under the minified directory.
minified_outputs =
filter_include(get_target_outputs(":build_min_js"), [ "*.js" ])
in_files = []
foreach(minified_output, minified_outputs) {
in_files += [ rebase_path(minified_output, "$target_gen_dir/minified") ]
}
in_folder = "$target_gen_dir/minified"
out_folder = "$target_gen_dir/code_cache"
out_manifest = "$target_gen_dir/build_code_cache_manifest.json"
deps = [ ":build_min_js" ]
}
generate_grd(generate_code_cache_grd_target_name) {
if (!generate_grdp) {
visibility = [ ":code_cache_resources_grit" ]
}
if (defined(invoker.grd_resource_path_prefix)) {
resource_path_prefix = invoker.grd_resource_path_prefix
}
grd_prefix = generate_code_cache_grd_prefix
out_grd = "$target_gen_dir/code_cache_resources.grd"
if (generate_grdp) {
out_grd = "$target_gen_dir/code_cache_resources.grdp"
}
manifest_files = [ "$target_gen_dir/build_code_cache_manifest.json" ]
deps = [ ":build_code_cache" ]
}
if (!generate_grdp) {
grit("code_cache_resources") {
enable_input_discovery_for_gn_analyze = false
source = "$target_gen_dir/code_cache_resources.grd"
outputs = [
"grit/${generate_code_cache_grd_prefix}_resources.h",
"grit/${generate_code_cache_grd_prefix}_resources_map.cc",
"grit/${generate_code_cache_grd_prefix}_resources_map.h",
"${generate_code_cache_grd_prefix}_resources.pak",
]
grit_output_dir = "$root_gen_dir/chrome"
if (defined(invoker.grit_output_dir)) {
grit_output_dir = invoker.grit_output_dir
}
output_dir = grit_output_dir
deps = [ ":$generate_code_cache_grd_target_name" ]
}
}
}
generate_grd(generate_grd_target_name) {
if (!generate_grdp) {
visibility = [ ":resources_grit" ]
}
grd_prefix = grd_prefix
out_grd = "$target_gen_dir/resources.grd"
if (generate_grdp) {
out_grd = "$target_gen_dir/resources.grdp"
}
deps = []
manifest_files = []
if (defined(invoker.static_files)) {
input_files = static_non_preprocessed_files
input_files_base_dir = rebase_path(".", "//")
deps += [ ":preprocess_static_files" ]
manifest_files +=
[ "${target_gen_dir}/preprocess_static_files_manifest.json" ]
}
if (optimize) {
deps += [ ":build_min_js" ]
manifest_files += filter_include(get_target_outputs(":build_min_js"),
[ "*_manifest.json" ])
if (bundle) {
resource_path_rewrites = []
foreach(f, invoker.optimize_webui_in_files) {
name = get_path_info(f, "name")
output_file = string_replace(f, "${name}.js", "${name}.rollup.js")
resource_path_rewrites += [ "${output_file}|${f}" ]
}
}
} else if (enable_source_maps) {
deps += [ ":merge_source_maps" ]
manifest_files += filter_include(get_target_outputs(":merge_source_maps"),
[ "*_manifest__processed.json" ])
} else {
deps += [ ":build_ts" ]
manifest_files +=
filter_include(get_target_outputs(":build_ts"), [ "*_manifest.json" ])
}
if (defined(invoker.extra_grdp_deps)) {
deps += invoker.extra_grdp_deps
grdp_files = invoker.extra_grdp_files
}
if (defined(invoker.grd_resource_path_prefix)) {
resource_path_prefix = invoker.grd_resource_path_prefix
}
if (enable_type_aware_eslint_checks) {
# Adding as a dependency so that it runs as part of the build, even though
# this `generate_grd()` target does not use any outputs from it.
deps += [ ":lint" ]
}
}
if (!generate_grdp) {
grit("resources") {
# These arguments are needed since the grd is generated at build time.
enable_input_discovery_for_gn_analyze = false
source = "$target_gen_dir/resources.grd"
deps = [ ":$generate_grd_target_name" ]
outputs = [
"grit/${grd_prefix}_resources.h",
"grit/${grd_prefix}_resources_map.cc",
"grit/${grd_prefix}_resources_map.h",
"${grd_prefix}_resources.pak",
]
grit_output_dir = "$root_gen_dir/chrome"
if (defined(invoker.grit_output_dir)) {
grit_output_dir = invoker.grit_output_dir
}
grit_flags = [
"-E",
"add_filepath_to_resource_map=${load_webui_from_disk}",
]
output_dir = grit_output_dir
}
}
}