# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//third_party/closure_compiler/closure_args.gni")
import("//third_party/closure_compiler/compile_js.gni")
import("//third_party/protobuf/proto_library.gni")
import("//ui/webui/resources/tools/generate_grd.gni")
import("//ui/webui/webui_features.gni")

import("//build/config/cast.gni")

# TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
# used to conditionally enable message ID scrambling in a way which is
# consistent across toolchains and which is affected by branded vs non-branded
# Chrome builds. Ideally we could create some generic knobs here that could be
# flipped elsewhere though.
import("//build/config/chrome_build.gni")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/features.gni")
import("//build/config/nacl/config.gni")
import("//build/toolchain/kythe.gni")
import("//components/nacl/features.gni")
import("//third_party/jinja2/jinja2.gni")
import("//third_party/ply/ply.gni")
import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
declare_args() {
  # Indicates whether typemapping should be supported in this build
  # configuration. This may be disabled when building external projects which
  # depend on //mojo but which do not need/want all of the Chromium tree
  # dependencies that come with typemapping.
  #
  # Note that (perhaps obviously) a huge amount of Chromium code will not build
  # with typemapping disabled, so it is never valid to set this to |false| in
  # any Chromium build configuration.
  enable_mojom_typemapping = true

  # Controls message ID scrambling behavior. If |true|, message IDs are
  # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on
  # non-Chrome OS desktop platforms. Enabled on official builds by default.
  # Set to |true| to enable message ID scrambling on a specific build.
  # See also `enable_scrambled_message_ids` below for more details.
  enable_mojom_message_id_scrambling = is_official_build

  # Enables generating javascript fuzzing-related code and the bindings for the
  # MojoLPM fuzzer targets. Off by default.
  enable_mojom_fuzzer = false

  # Enables Closure compilation of generated JS lite bindings. In environments
  # where compilation is supported, any mojom target "foo" will also have a
  # corresponding "foo_js_library_for_compile" target generated.
  if (is_chromeos_ash) {
    enable_mojom_closure_compile = enable_js_type_check && optimize_webui
  }
}

# Closure libraries are needed for mojom_closure_compile, and when
# js_type_check is enabled on Ash.
if (is_chromeos_ash) {
  generate_mojom_closure_libraries =
      enable_mojom_closure_compile || enable_js_type_check
} else {
  generate_mojom_closure_libraries = false
}

# NOTE: We would like to avoid scrambling message IDs where it doesn't add
# value, so we limit the behavior to desktop builds for now. There is some
# redundancy in the conditions here, but it is tolerated for clarity:
# We're explicit about Mac, Windows, and Linux desktop support, but it's
# also necessary to ensure that bindings in alternate toolchains (e.g.
# NaCl IRT) are always consistent with the default toolchain; for that
# reason we always enable scrambling within NaCl toolchains when possible,
# as well as within the default toolchain when NaCl is enabled.
#
# Finally, because we *cannot* enable scrambling on Chrome OS (it would break
# ARC) we have to explicitly opt out there even when NaCl is enabled (and
# consequently also when building for NaCl toolchains.) For this reason we
# check |target_os| explicitly, as it's consistent across all toolchains.
#
# TODO(crbug.com/1052397): Remove !chromeos_is_browser_only once
# lacros-chrome switches to target_os="chromeos"
enable_scrambled_message_ids =
    enable_mojom_message_id_scrambling &&
    (is_mac || is_win || (is_linux && !is_castos) ||
     ((enable_nacl || is_nacl) &&
      (target_os != "chromeos" && !chromeos_is_browser_only)))

_mojom_tools_root = "//mojo/public/tools"
_mojom_library_root = "$_mojom_tools_root/mojom/mojom"
mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py"
mojom_parser_sources = [
  "$_mojom_library_root/__init__.py",
  "$_mojom_library_root/error.py",
  "$_mojom_library_root/fileutil.py",
  "$_mojom_library_root/generate/__init__.py",
  "$_mojom_library_root/generate/check.py",
  "$_mojom_library_root/generate/generator.py",
  "$_mojom_library_root/generate/module.py",
  "$_mojom_library_root/generate/pack.py",
  "$_mojom_library_root/generate/template_expander.py",
  "$_mojom_library_root/generate/translate.py",
  "$_mojom_library_root/parse/__init__.py",
  "$_mojom_library_root/parse/ast.py",
  "$_mojom_library_root/parse/conditional_features.py",
  "$_mojom_library_root/parse/lexer.py",
  "$_mojom_library_root/parse/parser.py",
  "//tools/diagnosis/crbug_1001171.py",
]

mojom_generator_root = "$_mojom_tools_root/bindings"
mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
mojom_generator_sources =
    mojom_parser_sources + [
      "$mojom_generator_root/checks/__init__.py",
      "$mojom_generator_root/checks/mojom_attributes_check.py",
      "$mojom_generator_root/checks/mojom_definitions_check.py",
      "$mojom_generator_root/checks/mojom_interface_feature_check.py",
      "$mojom_generator_root/checks/mojom_restrictions_check.py",
      "$mojom_generator_root/generators/__init__.py",
      "$mojom_generator_root/generators/cpp_util.py",
      "$mojom_generator_root/generators/mojom_cpp_generator.py",
      "$mojom_generator_root/generators/mojom_java_generator.py",
      "$mojom_generator_root/generators/mojom_js_generator.py",
      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
      "$mojom_generator_root/generators/mojom_ts_generator.py",
      "$mojom_generator_script",
      "//build/action_helpers.py",
      "//build/gn_helpers.py",
      "//build/zip_helpers.py",
    ]

if (enable_scrambled_message_ids) {
  declare_args() {
    # The path to a file whose contents can be used as the basis for a message
    # ID scrambling salt.
    mojom_message_id_salt_path = "//chrome/VERSION"
  }

  assert(mojom_message_id_salt_path != "")
  message_scrambling_args = [
    "--scrambled_message_id_salt_path",
    rebase_path(mojom_message_id_salt_path, root_build_dir),
  ]
  message_scrambling_inputs = [ mojom_message_id_salt_path ]
} else {
  message_scrambling_args = []
  message_scrambling_inputs = []
}

# Generates targets for building C++, JavaScript and Java bindings from mojom
# files. The output files will go under the generated file directory tree with
# the same path as each input file.
#
# Other targets should depend on one of these generated targets (where "foo"
# is the target name):
#
#   foo
#       C++ bindings.
#
#   foo_blink
#       C++ bindings using Blink standard types.
#
#   foo_java
#       Java bindings.
#
#   foo_js
#       JavaScript bindings; used as compile-time dependency.
#
#   foo_js_data_deps
#       JavaScript bindings; used as run-time dependency.
#
# Parameters:
#
#   sources (optional if one of the deps sets listed below is present)
#       List of source .mojom files to compile.
#
#   deps (optional)
#       Note: this can contain only other mojom targets.
#
#       DEPRECATED: This is synonymous with public_deps because all mojom
#       dependencies must be public by design. Please use public_deps.
#
#   public_deps (optional)
#       Note: this can contain only other mojom targets.
#
#   parser_deps (optional)
#       List of non-mojom targets required for the mojom sources to be parsed.
#
#   import_dirs (optional)
#       List of import directories that will get added when processing sources.
#
#   input_root_override (optional)
#       Root path for the .mojom files used to generate the namespaces for
#       interfaces. Useful with targets outside //, e.g. in parent directories
#       above "//". The default input root is //
#       Example: Vivaldi's source root is "//vivaldi/",
#       and "//vivaldi/chromium/" is "//"
#       In such cases, not using this argument lead to the output files being
#       located in different directories than expected.
#
#   testonly (optional)
#
#   visibility (optional)
#
#   visibility_blink (optional)
#       The value to use for visibility for the blink variant. If unset,
#       |visibility| is used.
#
#   cpp_only (optional)
#       If set to true, only the C++ bindings targets will be generated.
#
#       NOTE: If the global |enable_mojom_fuzzer| build arg is true, JS bindings
#       will still be generated even when |cpp_only| is set to |true|, unless
#       you also set |enable_fuzzing| to |false| in your mojom target.
#
#   cpp_typemaps (optional)
#       A list of typemaps to be applied to the generated C++ bindings for this
#       mojom target. Note that this only applies to the non-Blink variant of
#       generated C++ bindings.
#
#       Every typemap is a GN scope describing how one or more mojom types maps
#       to a non-mojom C++ type, including necessary deps and headers required
#       for the mapping to work. See the Typemaps section below.
#
#   blink_cpp_typemaps (optional)
#       Same as above, but for the Blink variant of generated C++ bindings.
#
#   cpp_proxy_target (optional)
#       The name of a target which all C++ dependencies will link against
#       instead of linking directly against this mojom target's generated C++
#       sources. Normally when declaring invoking the mojom("foo") target, GN
#       emits a source_set or component target named "foo" which encompasses the
#       default variant of generated C++ bindings. This changes that to instead
#       emit a group("foo") which merely forwards public_deps to the named
#       `cpp_proxy_target`. That target must in turn depend on
#       "foo_cpp_sources".
#
#       This is useful primarily in conjunction with export_define et al to
#       embed generated C++ bindings within an existing component target.
#
#   blink_cpp_proxy_target (optional)
#       Same concept as `cpp_proxy_target` above, but affects the generated
#       "foo_blink" Blink-variant C++ bindings.
#
#   cpp_configs (optional)
#       A list of extra configs to apply to the default variant of generated C++
#       bindings.
#
#   blink_cpp_configs (optional)
#       A list of extra configs to apply to the Blink variant of generated C++
#       bindings.
#
#   mojom_source_deps (optional)
#       A list of mojoms this target depends upon. This is equivalent to
#       public_deps except that the C++ bindings depend on each of the named
#       "foo" targets' "foo_cpp_sources" rather than on foo's
#       `cpp_proxy_target`. It only makes sense to use this for dependencies
#       that set `cpp_proxy_target`, and only when the dependent mojom() would
#       otherwise have circular dependencies with that proxy target.
#
#   mojom_blink_source_deps (optional)
#       Same as above but depends on "foo_blink_cpp_sources" and is used for
#       dependencies that specify a `blink_cpp_proxy_target`.
#
#   generate_java (optional)
#       If set to true, Java bindings are generated for Android builds. If
#       |cpp_only| is set to true, it overrides this to prevent generation of
#       Java bindings.
#
#   enable_js_fuzzing (optional)
#       Enables generation of javascript fuzzing sources for the target if the
#       global build arg |enable_mojom_fuzzer| is also set to |true|.
#       Defaults to |true|. If JS fuzzing generation is enabled for a target,
#       the target will always generate JS bindings even if |cpp_only| is set to
#       |true|. See note above.
#
#   enable_mojolpm_fuzzing (optional)
#       Enables generation of fuzzing sources for the target if the global build
#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.
#
#   support_lazy_serialization (optional)
#       If set to |true|, generated C++ bindings will effectively prefer to
#       transmit messages in an unserialized form when going between endpoints
#       in the same process. This avoids the runtime cost of serialization,
#       deserialization, and validation logic at the expensive of increased
#       code size. Defaults to |false|.
#
#   disable_variants (optional)
#       If |true|, no variant sources will be generated for the target. Defaults
#       to |false|.
#
#   disallow_native_types (optional)
#       If set to |true|, mojoms in this target may not apply the [Native]
#       attribute to struct or enum declarations. This avoids emitting code
#       which depends on legacy IPC serialization. Default is |false|, meaning
#       [Native] types are allowed.
#
#   disallow_interfaces (optional)
#       If set to |true|, mojoms in this target may not define interfaces.
#       Generates bindings with a smaller set of dependencies. Defaults to
#       |false|.
#
#   scramble_message_ids (optional)
#       If set to |true| (the default), generated mojom interfaces will use
#       scrambled ordinal identifiers in encoded messages.
#
#   component_output_prefix (optional)
#       The prefix to use for the output_name of any component library emitted
#       for generated C++ bindings. If this is omitted, C++ bindings targets are
#       emitted as source_sets instead. Because this controls the name of the
#       output shared library binary in the root output directory, it must be
#       unique across the entire build configuration.
#
#       This is required if |component_macro_prefix| is specified.
#
#   component_macro_prefix (optional)
#       This specifies a macro prefix to use for component export macros and
#       should therefore be globally unique in the project. For example if this
#       is "FOO_BAR", then the generated C++ sources will be built with
#       IS_FOO_BAR_{suffix}_IMPL defined, and the generated public headers will
#       annotate public symbol definitions with
#       COMPONENT_EXPORT(FOO_BAR_{suffix}). "suffix" in this case depends on
#       which internal subtarget is generating the code (e.g. "SHARED", or a
#       variant name like "BLINK").
#
#   enabled_features (optional)
#       Definitions in a mojom file can be guarded by an EnableIf attribute. If
#       the value specified by the attribute does not match any items in the
#       list of enabled_features, the definition will be disabled, with no code
#       emitted for it.
#
#   generate_closure_exports (optional)
#       Generates JS lite bindings will use goog.provide and goog.require
#       annotations to export its symbols and import core Mojo bindings support
#       and other mojom dependency modules. Use this if you plan to compile your
#       bindings into a larger JS binary. Defaults to |false|, instead
#       generating JS lite bindings which assume they will be manually loaded in
#       correct dependency order. Note that this only has an effect if
#       the |enable_mojom_closure_compile| global arg is set to |true| as well.
#
#   generate_webui_js_bindings (optional)
#       Generate WebUI bindings in JavaScript rather than TypeScript. Defaults
#       to false. ChromeOS only parameter.
#
#   generate_legacy_js_bindings (optional)
#       Generate js_data_deps target containing legacy JavaScript bindings files
#       for Blink tests and other non-WebUI users when generating TypeScript
#       bindings for WebUI. Ignored if generate_webui_js_bindings is set to
#       true.
#
#   js_generate_struct_deserializers (optional)
#       Generates JS deerialize methods for structs.
#
#   extra_cpp_template_paths (optional)
#       List of extra C++ templates that are used to generate additional source
#       and/or header files. The templates should end with extension ".tmpl".
#
#   webui_module_path (optional)
#       The path or URL at which modules generated by this target will be
#       accessible to WebUI pages. This may either be an absolute path or
#       a full URL path starting with "chrome://resources/mojo". If this path
#       is not specified, WebUI bindings will not be generated.
#
#       If an absolute path, a WebUI page may only import these modules if
#       they are added to that page's data source (usually by adding the
#       modules to the mojo_files list for build_webui(), or by listing the
#       files as inputs to the page's ts_library() and/or generate_grd() build
#       steps.
#
#       If this is is instead a URL string starting with
#       "chrome://resources/mojo", the resulting bindings files should
#       be added to one of the lists in ui/webui/resources/mojo/BUILD.gn,
#       at which point they will be made available to all WebUI pages at the
#       given URL.
#
#       Note: WebUI module bindings are generated in TypeScript by default,
#       unless |generate_webui_js_bindings| is specified as true.
#
# The following parameters are used to support the component build. They are
# needed so that bindings which are linked with a component can use the same
# export settings for classes. The first three are for the chromium variant, and
# the last three are for the blink variant. These parameters can also override
# |component_macro_prefix| for a specific variant, allowing e.g. one variant
# to be linked into a larger non-mojom component target, while all other
# variants get their own unique component target.
#   export_class_attribute (optional)
#       The attribute to add to the class declaration. e.g. "CONTENT_EXPORT"
#   export_define (optional)
#       A define to be added to the source_set which is needed by the export
#       header. e.g. "CONTENT_IMPLEMENTATION=1"
#   export_header (optional)
#       A header to be added to the generated bindings to support the component
#       build. e.g. "content/common/content_export.h"
#   export_class_attribute_blink (optional)
#   export_define_blink (optional)
#   export_header_blink (optional)
#       These three parameters are the blink variants of the previous 3.
#
# The following parameters are used to correct component build dependencies.
# They are needed so mojom-mojom dependencies follow the rule that dependencies
# on a source set in another component are replaced by a dependency on the
# containing component. The first two are for the chromium variant; the other
# two are for the blink variant.
#   overridden_deps (optional)
#       The list of mojom deps to be overridden.
#   component_deps (optional)
#       The list of component deps to add to replace overridden_deps.
#   overridden_deps_blink (optional)
#   component_deps_blink (optional)
#       These two parameters are the blink variants of the previous two.
#
#   check_includes_blink (optional)
#       Overrides the check_includes variable for the blink variant.
#       If check_includes_blink is not defined, the check_includes variable
#       retains its original value.
#
# Typemaps
# ========
# The cpp_typemaps and blink_cpp_typemaps each specify an optional list of
# typemapping configurations. Each configuration is a GN scope with metadata
# describing what and how to map.
#
# Typemap scope parameters:
#   types
#       A list of type specifications for this typemap. Each type specification
#       is a nested GN scope which can be expressed with the following syntax:
#
#           {
#             mojom = "foo.mojom.Bar"
#             cpp = "::foo::LegitBar"
#             move_only = true
#             # etc...
#           }
#
#       Each type specification supports the following values:
#
#         mojom (required)
#             The fully qualified name of a mojom type to be mapped. This is a
#             string like "foo.mojom.Bar".
#
#         cpp (required)
#             The fully qualified name of the C++ type to which the mojom type
#             should be mapped in generated bindings. This is a string like
#             "::base::Value" or "std::vector<::base::Value>".
#
#         copyable_pass_by_value (optional)
#             A boolean value (default false) which effectively indicates
#             whether the C++ type is very cheap to copy. If so, generated
#             bindings will pass by value but not use std::move() at call sites.
#
#         default_constructible (optional)
#             A boolean value (default true) which indicates whether the C++
#             type is default constructible. If a C++ type is not default
#             constructible (e.g. the implementor of the type prefers not to
#             publicly expose a default constructor that creates an object in an
#             invalid state), Mojo will instead construct C++ type with an
#             argument of the type `mojo::DefaultConstruct::Tag` (essentially a
#             passkey-like type specifically for this use case).
#
#         force_serialize (optional)
#             A boolean value (default false) which disables lazy serialization
#             of the typemapped type if lazy serialization is enabled for the
#             mojom target applying this typemap.
#
#         forward_declaration (optional)
#             A forward declaration of the C++ type, which bindings that don't
#             need the full type definition can use to reduce the size of
#             the generated code. This is a string like
#             "namespace base { class Value; }".
#
#         hashable (optional)
#             A boolean value (default false) indicating whether the C++ type is
#             hashable. Set to true if true AND needed (i.e. you need to use the
#             type as the key of a mojom map).
#
#         move_only (optional)
#             A boolean value (default false) which indicates whether the C++
#             type is move-only. If true, generated bindings will pass the type
#             by value and use std::move() at call sites.
#
#         nullable_is_same_type (optional)
#             A boolean value (default false) which indicates that the C++ type
#             has some baked-in semantic notion of a "null" state. If true, the
#             traits for the type must define IsNull and SetToNull methods.
#
#             When false, nullable fields are represented by wrapping the C++
#             type with absl::optional, and null values are simply
#             absl::nullopt.
#
# Additional typemap scope parameters:
#
#   traits_headers (optional)
#       Headers which must be included in the generated mojom in order for
#       serialization to be possible. This generally means including at least
#       the header for the corresponding mojom traits definitions.
#
#   traits_private_headers (optional)
#       Headers which must be included in generated C++ serialization code for
#       a mojom using the typemap. This should be used only when including a
#       header in |traits_headers| is problematic for compilation, as is
#       sometimes the case with legacy IPC message headers.
#
#   traits_sources (optional)
#       The references to the source files (typically a single .cc and .h file)
#       defining an appropriate set of EnumTraits or StructTraits, etc for the
#       the type-mapping. Using this will cause the listed sources to be
#       integrated directly into the dependent mojom's generated type-mapping
#       targets.
#
#       Prefer using |traits_public_deps| over inlined |traits_sources|, as this
#       will generally lead to easier build maintenance over time.
#
#       NOTE: If a typemap is shared by Blink and non-Blink bindings, you cannot
#       use this and MUST use |traits_public_deps| to reference traits built
#       within a separate target.
#
#   traits_deps / traits_public_deps (optional)
#       Any dependencies of sources in |traits_headers| or |traits_sources| must
#       be listed here.
#
template("mojom") {
  assert(
      defined(invoker.sources) || defined(invoker.deps) ||
          defined(invoker.public_deps),
      "\"sources\" or \"deps\" must be defined for the $target_name template.")

  if (defined(invoker.export_class_attribute) ||
      defined(invoker.export_define) || defined(invoker.export_header)) {
    assert(defined(invoker.export_class_attribute))
    assert(defined(invoker.export_define) || defined(invoker.cpp_configs))
    assert(defined(invoker.export_header))
  }
  if (defined(invoker.export_class_attribute_blink) ||
      defined(invoker.export_define_blink) ||
      defined(invoker.export_header_blink)) {
    assert(defined(invoker.export_class_attribute_blink))
    assert(defined(invoker.export_define_blink) ||
           defined(invoker.blink_cpp_configs))
    assert(defined(invoker.export_header_blink))

    # Not all platforms use the Blink variant, so make sure GN doesn't complain
    # about these values being inconsequential.
    not_needed(invoker,
               [
                 "export_class_attribute_blink",
                 "export_define_blink",
                 "export_header_blink",
               ])
  }
  if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {
    assert(defined(invoker.overridden_deps))
    assert(defined(invoker.component_deps))
  }

  if (defined(invoker.overridden_deps_blink) ||
      defined(invoker.component_deps_blink)) {
    assert(defined(invoker.overridden_deps_blink))
    assert(defined(invoker.component_deps_blink))
  }

  # Type-mapping may be disabled or we may not generate C++ bindings.
  not_needed(invoker,
             [
               "cpp_typemaps",
               "blink_cpp_typemaps",
             ])

  require_full_cpp_deps =
      !defined(invoker.disallow_native_types) ||
      !invoker.disallow_native_types || !defined(invoker.disallow_interfaces) ||
      !invoker.disallow_interfaces

  all_deps = []
  mojom_cpp_deps = []
  if (defined(invoker.deps)) {
    all_deps += invoker.deps
    mojom_cpp_deps += invoker.deps
  }
  if (defined(invoker.public_deps)) {
    all_deps += invoker.public_deps
    mojom_cpp_deps += invoker.public_deps
  }
  if (defined(invoker.mojom_source_deps)) {
    all_deps += invoker.mojom_source_deps
  }
  if (defined(invoker.mojom_blink_source_deps)) {
    all_deps += invoker.mojom_blink_source_deps
  }
  not_needed([ "mojom_deps" ])

  if (defined(invoker.component_macro_prefix)) {
    assert(defined(invoker.component_output_prefix))
  }

  group("${target_name}__is_mojom") {
  }

  # Explicitly ensure that all dependencies (invoker.deps and
  # invoker.public_deps) are mojom targets.
  group("${target_name}__check_deps_are_all_mojom") {
    deps = []
    foreach(d, all_deps) {
      name = get_label_info(d, "label_no_toolchain")
      toolchain = get_label_info(d, "toolchain")
      deps += [ "${name}__is_mojom(${toolchain})" ]
    }
  }

  sources_list = []
  if (defined(invoker.sources)) {
    sources_list = invoker.sources
  }

  # Listed sources may be relative to the current target dir, or they may be
  # absolute paths, including paths to generated mojom files. While those are
  # fine as-is for input references, deriving output paths can be more subtle.
  #
  # Here we rewrite all source paths to be relative to the root build dir and
  # strip any root_gen_dir prefixes.
  #
  # So for a target in //foo/bar with:
  #
  #     sources = [
  #       "a.mojom",
  #       "b/c.mojom",
  #       "//baz/d.mojom",
  #       "$target_gen_dir/e.mojom",
  #     ]
  #
  # output_file_base_paths will be:
  #
  #     [
  #       "foo/bar/a.mojom",
  #       "foo/bar/b/c.mojom",
  #       "baz/d.mojom",
  #       "foo/bar/e.mojom",
  #     ]
  #
  # This result is essentially a list of base filename paths which are suitable
  # for the naming of any generated output files derived from their
  # corresponding input mojoms. These paths are always considered to be relative
  # to root_gen_dir.
  if (defined(invoker.input_root_override)) {
    source_abspaths = rebase_path(sources_list, invoker.input_root_override)
  } else {
    source_abspaths = rebase_path(sources_list, "//")
  }
  output_file_base_paths = []
  foreach(path, source_abspaths) {
    output_file_base_paths +=
        [ string_replace(path, rebase_path(root_gen_dir, "//") + "/", "") ]
  }

  # Sanity check that either all input files have a .mojom extension, or
  # all input files have a .test-mojom extension AND |testonly| is |true|.
  sources_list_filenames =
      process_file_template(sources_list, "{{source_file_part}}")
  sources_list_filenames_with_mojom_extension =
      process_file_template(sources_list, "{{source_name_part}}.mojom")
  if (sources_list_filenames != sources_list_filenames_with_mojom_extension) {
    sources_list_filenames_with_test_mojom_extension =
        process_file_template(sources_list, "{{source_name_part}}.test-mojom")
    if (sources_list_filenames ==
        sources_list_filenames_with_test_mojom_extension) {
      assert(
          defined(invoker.testonly) && invoker.testonly,
          "mojom targets for .test-mojom files must set |testonly| to |true|")
    } else {
      assert(
          false,
          "One or more mojom files has an invalid extension. The only " +
              "allowed extensions are .mojom and .test-mojom, and any given " +
              "mojom target must use one or the other exclusively.")
    }
  }

  build_metadata_filename = "$target_gen_dir/$target_name.build_metadata"
  build_metadata = {
  }
  build_metadata.sources = rebase_path(sources_list, target_gen_dir)
  build_metadata.deps = []
  foreach(dep, all_deps) {
    dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
    dep_name = get_label_info(dep, "name")
    build_metadata.deps +=
        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
                      target_gen_dir) ]
  }
  write_file(build_metadata_filename, build_metadata, "json")

  generate_js_fuzzing =
      (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&
      enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)

  generate_mojolpm_fuzzing =
      (!defined(invoker.enable_mojolpm_fuzzing) ||
       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
      (!defined(invoker.testonly) || !invoker.testonly)

  parser_target_name = "${target_name}__parser"
  parser_deps = []
  foreach(dep, all_deps) {
    _label = get_label_info(dep, "label_no_toolchain")
    parser_deps += [ "${_label}__parser" ]
  }
  if (defined(invoker.parser_deps)) {
    parser_deps += invoker.parser_deps
  }
  if (sources_list == []) {
    # Even without sources we generate a parser target to at least forward
    # other parser dependencies.
    group(parser_target_name) {
      public_deps = parser_deps
    }
  } else {
    enabled_features = []
    if (defined(invoker.enabled_features)) {
      enabled_features += invoker.enabled_features
    }
    if (is_posix) {
      enabled_features += [ "is_posix" ]
    }
    if (is_android) {
      enabled_features += [ "is_android" ]
    } else if (is_chromeos_ash) {
      enabled_features += [
        "is_chromeos",
        "is_chromeos_ash",
      ]
    } else if (is_chromeos_lacros) {
      enabled_features += [
        "is_chromeos",
        "is_chromeos_lacros",
      ]
    } else if (is_fuchsia) {
      enabled_features += [ "is_fuchsia" ]
    } else if (is_ios) {
      enabled_features += [ "is_ios" ]
    } else if (is_linux) {
      enabled_features += [ "is_linux" ]
    } else if (is_mac) {
      enabled_features += [ "is_mac" ]
    } else if (is_win) {
      enabled_features += [ "is_win" ]
    }

    if (is_apple) {
      enabled_features += [ "is_apple" ]
    }

    action(parser_target_name) {
      allow_remote = true
      custom_processor = "mojom_parser"
      script = mojom_parser_script
      inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]
      sources = sources_list
      public_deps = parser_deps
      outputs = []
      foreach(base_path, output_file_base_paths) {
        filename = get_path_info(base_path, "file")
        dirname = get_path_info(base_path, "dir")
        outputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
      }

      filelist = []
      foreach(source, sources_list) {
        filelist += [ rebase_path(source, root_build_dir) ]
      }

      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
      rsp_file = "$target_gen_dir/${target_name}.rsp"
      write_file(rsp_file, filelist)
      inputs += [ rsp_file ]

      args = [
        # Resolve relative input mojom paths against both the root src dir and
        # the root gen dir.
        "--input-root",
        rebase_path("//.", root_build_dir),
        "--input-root",
        rebase_path(root_gen_dir, root_build_dir),

        "--output-root",
        rebase_path(root_gen_dir, root_build_dir),

        "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),

        "--check-imports",
        rebase_path(build_metadata_filename, root_build_dir),
      ]

      if (defined(invoker.input_root_override)) {
        args += [
          "--input-root",
          rebase_path(invoker.input_root_override, root_build_dir),
        ]
      }

      foreach(enabled_feature, enabled_features) {
        args += [
          "--enable-feature",
          enabled_feature,
        ]
      }

      if (defined(invoker.webui_module_path)) {
        args += [
          "--add-module-metadata",
          "webui_module_path=${invoker.webui_module_path}",
        ]
        if (defined(invoker.generate_webui_js_bindings) &&
            invoker.generate_webui_js_bindings) {
          args += [
            "--add-module-metadata",
            "generate_webui_js=True",
          ]
        }
      }
    }
  }

  generator_cpp_message_ids_target_name = "${target_name}__generate_message_ids"

  # Generate code that is shared by different variants.
  if (sources_list != []) {
    base_dir = "//"
    if (defined(invoker.input_root_override)) {
      base_dir = invoker.input_root_override
    }

    common_generator_args = [
      "--use_bundled_pylibs",
      "-o",
      rebase_path(root_gen_dir, root_build_dir),
      "generate",
      "-d",
      rebase_path(base_dir, root_build_dir),
      "-I",
      rebase_path("//", root_build_dir),
      "--bytecode_path",
      rebase_path("$root_gen_dir/mojo/public/tools/bindings", root_build_dir),
    ]
    if (defined(invoker.input_root_override)) {
      common_generator_args += [
        "-I",
        rebase_path(invoker.input_root_override, root_build_dir),
      ]
    }

    if (defined(invoker.disallow_native_types) &&
        invoker.disallow_native_types) {
      common_generator_args += [ "--disallow_native_types" ]
    }

    if (defined(invoker.disallow_interfaces) && invoker.disallow_interfaces) {
      common_generator_args += [ "--disallow_interfaces" ]
    }

    if (defined(invoker.import_dirs)) {
      foreach(import_dir, invoker.import_dirs) {
        common_generator_args += [
          "-I",
          rebase_path(import_dir, root_build_dir),
        ]
      }
    }

    if (defined(invoker.component_macro_prefix)) {
      shared_component_export_macro =
          "COMPONENT_EXPORT(${invoker.component_macro_prefix}_SHARED)"
      shared_component_impl_macro =
          "IS_${invoker.component_macro_prefix}_SHARED_IMPL"
      shared_component_output_name = "${invoker.component_output_prefix}_shared"
    } else if (defined(invoker.export_class_attribute_shared) ||
               defined(invoker.export_class_attribute)) {
      if (defined(invoker.export_class_attribute_shared)) {
        assert(defined(invoker.export_header_shared))
        shared_component_export_macro = invoker.export_class_attribute_shared
        shared_component_impl_macro = invoker.export_define_shared
      } else {
        assert(!defined(invoker.export_header_shared))

        # If no explicit shared attribute/define was provided by the invoker,
        # we derive some reasonable settings frorm the default variant.
        shared_component_export_macro = "COMPONENT_EXPORT(MOJOM_SHARED_" +
                                        invoker.export_class_attribute + ")"
        shared_component_impl_macro =
            "IS_MOJOM_SHARED_" + invoker.export_class_attribute + "_IMPL"
      }

      if (defined(invoker.component_output_prefix)) {
        shared_component_output_name =
            "${invoker.component_output_prefix}_shared"
      } else {
        shared_component_output_name = "${target_name}_shared"
      }
    }

    action(generator_cpp_message_ids_target_name) {
      allow_remote = true
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources = sources_list +
                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
      deps = [
        ":$parser_target_name",
        "//mojo/public/tools/bindings:precompile_templates",
      ]
      if (defined(invoker.parser_deps)) {
        deps += invoker.parser_deps
      }
      outputs = []
      args = common_generator_args
      filelist = []
      foreach(source, sources_list) {
        filelist += [ rebase_path(source, root_build_dir) ]
      }
      foreach(base_path, output_file_base_paths) {
        filename = get_path_info(base_path, "file")
        dirname = get_path_info(base_path, "dir")
        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
        outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
      }

      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
      rsp_file = "$target_gen_dir/${target_name}.rsp"
      write_file(rsp_file, filelist)
      inputs += [ rsp_file ]

      args += [
        "--filelist=" + rebase_path(rsp_file, root_build_dir),
        "--generate_non_variant_code",
        "--generate_message_ids",
        "-g",
        "c++",
      ]

      if (!defined(invoker.scramble_message_ids) ||
          invoker.scramble_message_ids) {
        inputs += message_scrambling_inputs
        args += message_scrambling_args
      }
    }

    generator_shared_target_name = "${target_name}_shared__generator"

    action(generator_shared_target_name) {
      allow_remote = true
      visibility = [ ":*" ]
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources = sources_list +
                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
      deps = [
        ":$parser_target_name",
        "//mojo/public/tools/bindings:precompile_templates",
      ]
      if (defined(invoker.parser_deps)) {
        deps += invoker.parser_deps
      }

      outputs = []
      args = common_generator_args
      filelist = []
      foreach(source, sources_list) {
        filelist += [ rebase_path(source, root_build_dir) ]
      }
      foreach(base_path, output_file_base_paths) {
        # Need the mojom-module as an input to this action.
        filename = get_path_info(base_path, "file")
        dirname = get_path_info(base_path, "dir")
        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]

        outputs += [
          "$root_gen_dir/$base_path-features.h",
          "$root_gen_dir/$base_path-params-data.h",
          "$root_gen_dir/$base_path-shared-internal.h",
          "$root_gen_dir/$base_path-shared.cc",
          "$root_gen_dir/$base_path-shared.h",
        ]
      }

      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
      rsp_file = "$target_gen_dir/${target_name}.rsp"
      write_file(rsp_file, filelist)
      inputs += [ rsp_file ]

      args += [
        "--filelist=" + rebase_path(rsp_file, root_build_dir),
        "--generate_non_variant_code",
        "-g",
        "c++",
      ]

      if (defined(shared_component_export_macro)) {
        args += [
          "--export_attribute",
          shared_component_export_macro,
          "--export_header",
          "base/component_export.h",
        ]
      }

      # Enable adding annotations to generated C++ headers that are used for
      # cross-references in CodeSearch.
      if (enable_kythe_annotations) {
        args += [ "--enable_kythe_annotations" ]
      }
    }
  } else {
    group(generator_cpp_message_ids_target_name) {
    }
  }

  shared_cpp_sources_target_name = "${target_name}_shared_cpp_sources"
  source_set(shared_cpp_sources_target_name) {
    if (defined(invoker.testonly)) {
      testonly = invoker.testonly
    }
    configs += [ "//build/config/compiler:wexit_time_destructors" ]
    deps = []
    public_deps = []
    if (output_file_base_paths != []) {
      sources = []
      foreach(base_path, output_file_base_paths) {
        sources += [
          "$root_gen_dir/$base_path-features.h",
          "$root_gen_dir/$base_path-params-data.h",
          "$root_gen_dir/$base_path-shared-internal.h",
          "$root_gen_dir/$base_path-shared.cc",
          "$root_gen_dir/$base_path-shared.h",
        ]
      }
      public_deps += [ ":$generator_shared_target_name" ]
    }
    if (require_full_cpp_deps) {
      public_deps += [ "//mojo/public/cpp/bindings" ]
    } else {
      public_deps += [ "//mojo/public/cpp/bindings:bindings_base" ]
    }
    foreach(d, all_deps) {
      # Resolve the name, so that a target //mojo/something becomes
      # //mojo/something:something and we can append shared_cpp_sources_suffix
      # to get the cpp dependency name.
      full_name = get_label_info("$d", "label_no_toolchain")
      public_deps += [ "${full_name}_shared" ]
    }
    if (defined(shared_component_impl_macro)) {
      defines = [ shared_component_impl_macro ]
    }
  }

  shared_cpp_library_target_name = "${target_name}_shared"
  if (defined(shared_component_output_name)) {
    component(shared_cpp_library_target_name) {
      if (defined(invoker.testonly)) {
        testonly = invoker.testonly
      }
      output_name = "$shared_component_output_name"
      public_deps = [ ":$shared_cpp_sources_target_name" ]
    }
  } else {
    group(shared_cpp_library_target_name) {
      if (defined(invoker.testonly)) {
        testonly = invoker.testonly
      }
      public_deps = [ ":$shared_cpp_sources_target_name" ]
    }
  }

  if (generate_mojolpm_fuzzing) {
    # This block generates the proto files used for the MojoLPM fuzzer,
    # and the corresponding proto targets that will be linked in the fuzzer
    # targets. These are independent of the typemappings, and can be done
    # separately here.

    generator_mojolpm_proto_target_name =
        "${target_name}_mojolpm_proto_generator"

    action(generator_mojolpm_proto_target_name) {
      allow_remote = true
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources =
          invoker.sources + [
            "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
          ]
      deps = [
        ":$parser_target_name",
        "//mojo/public/tools/bindings:precompile_templates",
      ]

      outputs = []
      args = common_generator_args
      filelist = []

      # Split the input into generated and non-generated source files. They
      # need to be processed separately.
      gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
      non_gen_sources =
          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
      gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])

      foreach(source, non_gen_sources) {
        filelist += [ rebase_path(source, root_build_dir) ]
        inputs += [ "$target_gen_dir/$source-module" ]
        outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
      }

      foreach(source, gen_sources) {
        filelist += [ rebase_path(source, root_build_dir) ]

        # For generated files, we assume they're in the target_gen_dir or a
        # sub-folder of it. Rebase the path so we can get the relative location.
        source_file = rebase_path(source, target_gen_dir)
        inputs += [ "$target_gen_dir/$source_file-module" ]
        outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
      }

      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
      rsp_file = "$target_gen_dir/${target_name}.rsp"
      write_file(rsp_file, filelist)
      inputs += [ rsp_file ]

      args += [
        "--filelist=" + rebase_path(rsp_file, root_build_dir),
        "--generate_non_variant_code",
        "-g",
        "mojolpm",
      ]
    }

    mojolpm_proto_target_name = "${target_name}_mojolpm_proto"
    if (defined(invoker.sources)) {
      proto_library(mojolpm_proto_target_name) {
        testonly = true
        generate_python = false

        # Split the input into generated and non-generated source files. They
        # need to be processed separately.
        gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
        non_gen_sources =
            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
        gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
        sources = process_file_template(
                non_gen_sources,
                [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
        sources += process_file_template(
                gen_sources,
                [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])

        import_dirs = [ "//" ]
        proto_in_dir = "${root_gen_dir}"
        proto_out_dir = "."
        proto_deps = [ ":$generator_mojolpm_proto_target_name" ]
        link_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ]

        foreach(d, all_deps) {
          # Resolve the name, so that a target //mojo/something becomes
          # //mojo/something:something and we can append mojolpm_proto_suffix
          # to get the proto dependency name.
          full_name = get_label_info("$d", "label_no_toolchain")
          proto_deps += [ "${full_name}_mojolpm_proto" ]
          link_deps += [ "${full_name}_mojolpm_proto" ]
        }
      }
    } else {
      group(mojolpm_proto_target_name) {
        testonly = true
        public_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ]
        if (defined(generator_shared_target_name)) {
          public_deps += [ ":$generator_shared_target_name" ]
        }
        foreach(d, all_deps) {
          # Resolve the name, so that a target //mojo/something becomes
          # //mojo/something:something and we can append #mojolpm_proto_suffix
          # to get the proto dependency name.
          full_name = get_label_info("$d", "label_no_toolchain")
          public_deps += [ "${full_name}_mojolpm_proto" ]
        }
      }
    }
  }

  # Generate code for variants.
  default_variant = {
    component_macro_suffix = ""
  }
  if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&
      use_blink) {
    blink_variant = {
      variant = "blink"
      component_macro_suffix = "_BLINK"
      for_blink = true
    }
    enabled_configurations = [
      default_variant,
      blink_variant,
    ]
  } else {
    enabled_configurations = [ default_variant ]
  }
  foreach(bindings_configuration, enabled_configurations) {
    cpp_only = false
    if (defined(invoker.cpp_only)) {
      cpp_only = invoker.cpp_only
    }
    variant_suffix = ""
    if (defined(bindings_configuration.variant)) {
      variant = bindings_configuration.variant
      variant_suffix = "_${variant}"
      cpp_only = true
    }

    cpp_typemap_configs = []
    export_defines = []
    export_defines_overridden = false
    force_source_set = false
    proxy_target = ""
    extra_configs = []
    output_visibility = []
    output_visibility = [ "*" ]
    cpp_source_deps = []
    if (defined(bindings_configuration.for_blink) &&
        bindings_configuration.for_blink) {
      if (defined(invoker.blink_cpp_typemaps)) {
        cpp_typemap_configs = invoker.blink_cpp_typemaps
      }
      if (defined(invoker.export_define_blink)) {
        export_defines_overridden = true
        export_defines = [ invoker.export_define_blink ]
        force_source_set = true
      }
      if (defined(invoker.blink_cpp_configs)) {
        extra_configs += invoker.blink_cpp_configs
      }
      if (defined(invoker.blink_cpp_proxy_target)) {
        proxy_target = invoker.blink_cpp_proxy_target
      }
      if (defined(invoker.visibility_blink)) {
        output_visibility = []
        output_visibility = invoker.visibility_blink
      }
      if (defined(invoker.mojom_blink_source_deps)) {
        cpp_source_deps = invoker.mojom_blink_source_deps
      }
    } else {
      if (defined(invoker.cpp_typemaps)) {
        cpp_typemap_configs = invoker.cpp_typemaps
      }
      if (defined(invoker.export_define)) {
        export_defines_overridden = true
        export_defines = [ invoker.export_define ]
        force_source_set = true
      }
      if (defined(invoker.cpp_configs)) {
        extra_configs += invoker.cpp_configs
      }
      if (defined(invoker.cpp_proxy_target)) {
        proxy_target = invoker.cpp_proxy_target
      }
      if (defined(invoker.visibility)) {
        output_visibility = []
        output_visibility = invoker.visibility
      }
      if (defined(invoker.mojom_source_deps)) {
        cpp_source_deps = invoker.mojom_source_deps
      }
    }
    not_needed([ "cpp_typemap_configs" ])
    if (proxy_target != "") {
      group("${target_name}${variant_suffix}__has_cpp_proxy") {
      }
    }

    if (!export_defines_overridden && defined(invoker.component_macro_prefix)) {
      output_name_override =
          "${invoker.component_output_prefix}${variant_suffix}"
      export_defines =
          [ "IS_${invoker.component_macro_prefix}" +
            "${bindings_configuration.component_macro_suffix}_IMPL" ]
    }

    generate_java = false
    if (!cpp_only && defined(invoker.generate_java)) {
      generate_java = invoker.generate_java
    }
    type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings"
    type_mappings_path =
        "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
    if (sources_list != []) {
      export_args = []
      export_args_overridden = false
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink) {
        if (defined(invoker.export_class_attribute_blink)) {
          export_args_overridden = true
          export_args += [
            "--export_attribute",
            invoker.export_class_attribute_blink,
            "--export_header",
            invoker.export_header_blink,
          ]
        }
      } else if (defined(invoker.export_class_attribute)) {
        export_args_overridden = true
        export_args += [
          "--export_attribute",
          invoker.export_class_attribute,
          "--export_header",
          invoker.export_header,
        ]
      }
      if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
        export_args += [
          "--export_attribute",
          "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
              "${bindings_configuration.component_macro_suffix})",
          "--export_header",
          "base/component_export.h",
        ]
      }

      generator_cpp_output_suffixes = []
      variant_dash_suffix = ""
      if (defined(variant)) {
        variant_dash_suffix = "-${variant}"
      }
      generator_cpp_output_suffixes += [
        "${variant_dash_suffix}-forward.h",
        "${variant_dash_suffix}-import-headers.h",
        "${variant_dash_suffix}-test-utils.h",
        "${variant_dash_suffix}.cc",
        "${variant_dash_suffix}.h",
      ]

      generator_target_name = "${target_name}${variant_suffix}__generator"

      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
      action(generator_target_name) {
        allow_remote = true
        visibility = [ ":*" ]
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources =
            sources_list + [
              "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
              type_mappings_path,
            ]
        if (generate_mojolpm_fuzzing &&
            !defined(bindings_configuration.variant)) {
          sources += [
            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
          ]
        }
        deps = [
          ":$parser_target_name",
          ":$type_mappings_target_name",
          "//mojo/public/tools/bindings:precompile_templates",
        ]

        if (defined(invoker.parser_deps)) {
          deps += invoker.parser_deps
        }
        outputs = []
        args = common_generator_args + export_args
        filelist = []
        foreach(source, sources_list) {
          filelist += [ rebase_path(source, root_build_dir) ]
        }
        foreach(base_path, output_file_base_paths) {
          filename = get_path_info(base_path, "file")
          dirname = get_path_info(base_path, "dir")
          inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]

          outputs += [
            "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
            "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
            "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
            "$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
            "$root_gen_dir/${base_path}${variant_dash_suffix}.h",
          ]
          if (generate_mojolpm_fuzzing &&
              !defined(bindings_configuration.variant)) {
            outputs += [
              "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
              "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
            ]
          }
        }

        # Workaround for https://github.com/ninja-build/ninja/issues/1966.
        rsp_file = "$target_gen_dir/${target_name}.rsp"
        write_file(rsp_file, filelist)
        inputs += [ rsp_file ]
        args += [
          "--filelist=" + rebase_path("$rsp_file", root_build_dir),
          "-g",
        ]

        if (generate_mojolpm_fuzzing &&
            !defined(bindings_configuration.variant)) {
          args += [ "c++,mojolpm" ]
        } else {
          args += [ "c++" ]
        }

        if (defined(bindings_configuration.variant)) {
          args += [
            "--variant",
            bindings_configuration.variant,
          ]
        }

        args += [
          "--typemap",
          rebase_path(type_mappings_path, root_build_dir),
        ]

        if (defined(bindings_configuration.for_blink) &&
            bindings_configuration.for_blink) {
          args += [ "--for_blink" ]
        }

        if (defined(invoker.support_lazy_serialization) &&
            invoker.support_lazy_serialization) {
          args += [ "--support_lazy_serialization" ]
        }

        if (enable_kythe_annotations) {
          args += [ "--enable_kythe_annotations" ]
        }

        if (!defined(invoker.scramble_message_ids) ||
            invoker.scramble_message_ids) {
          inputs += message_scrambling_inputs
          args += message_scrambling_args
        }

        if (defined(invoker.extra_cpp_template_paths)) {
          foreach(extra_cpp_template, invoker.extra_cpp_template_paths) {
            args += [
              "--extra_cpp_template_paths",
              rebase_path(extra_cpp_template, root_build_dir),
            ]
            inputs += [ extra_cpp_template ]

            assert(
                get_path_info(extra_cpp_template, "extension") == "tmpl",
                "--extra_cpp_template_paths only accepts template files ending in extension .tmpl")
            foreach(base_path, output_file_base_paths) {
              template_file_name = get_path_info("$extra_cpp_template", "name")
              outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-${template_file_name}" ]
            }
          }
        }
      }
    }

    # Write the typemapping configuration for this target out to a file to be
    # validated by a Python script. This helps catch mistakes that can't
    # be caught by logic in GN.
    _typemap_config_filename =
        "$target_gen_dir/${target_name}${variant_suffix}.typemap_config"
    _typemap_stamp_filename = "${_typemap_config_filename}.validated"
    _typemap_validator_target_name = "${type_mappings_target_name}__validator"
    _rebased_typemap_configs = []
    foreach(config, cpp_typemap_configs) {
      _rebased_config = {
      }
      _rebased_config = config
      if (defined(config.traits_headers)) {
        _rebased_config.traits_headers = []
        _rebased_config.traits_headers =
            rebase_path(config.traits_headers, "//")
      }
      if (defined(config.traits_private_headers)) {
        _rebased_config.traits_private_headers = []
        _rebased_config.traits_private_headers =
            rebase_path(config.traits_private_headers, "//")
      }
      _rebased_typemap_configs += [ _rebased_config ]
    }
    write_file(_typemap_config_filename, _rebased_typemap_configs, "json")
    _mojom_target_name = target_name

    action(_typemap_validator_target_name) {
      allow_remote = true
      script = "$mojom_generator_root/validate_typemap_config.py"
      inputs = [ _typemap_config_filename ]
      outputs = [ _typemap_stamp_filename ]
      args = [
        get_label_info(_mojom_target_name, "label_no_toolchain"),
        rebase_path(_typemap_config_filename, root_build_dir),
        rebase_path(_typemap_stamp_filename, root_build_dir),
      ]
    }

    action(type_mappings_target_name) {
      allow_remote = true
      inputs =
          mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]
      outputs = [ type_mappings_path ]
      script = "$mojom_generator_root/generate_type_mappings.py"
      deps = [ ":$_typemap_validator_target_name" ]
      args = [
        "--output",
        rebase_path(type_mappings_path, root_build_dir),
      ]

      sources = []
      foreach(d, all_deps) {
        name = get_label_info(d, "label_no_toolchain")
        toolchain = get_label_info(d, "toolchain")
        dependency_output = "${name}${variant_suffix}__type_mappings"
        dependency_target = "${dependency_output}(${toolchain})"
        deps += [ dependency_target ]
        dependency_output_dir =
            get_label_info(dependency_output, "target_gen_dir")
        dependency_name = get_label_info(dependency_output, "name")
        dependency_path = "$dependency_output_dir/${dependency_name}"
        sources += [ dependency_path ]
        args += [
          "--dependency",
          rebase_path(dependency_path, root_build_dir),
        ]
      }

      # Newer GN-based typemaps are aggregated into a single config.
      inputs += [ _typemap_config_filename ]
      args += [
        "--cpp-typemap-config",
        rebase_path(_typemap_config_filename, root_build_dir),
      ]
    }

    group("${target_name}${variant_suffix}_headers") {
      public_deps = []
      if (sources_list != []) {
        public_deps += [
          ":$generator_cpp_message_ids_target_name",
          ":$generator_shared_target_name",
          ":$generator_target_name",
        ]
      }
      foreach(d, all_deps) {
        full_name = get_label_info("$d", "label_no_toolchain")
        public_deps += [ "${full_name}${variant_suffix}_headers" ]
      }
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink) {
        public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
      }
    }

    js_data_deps_target_name = target_name + "_js_data_deps"
    not_needed([ "js_data_deps_target_name" ])

    if (!force_source_set && defined(invoker.component_macro_prefix)) {
      sources_target_type = "component"
    } else {
      sources_target_type = "source_set"
    }

    output_target_name = "${target_name}${variant_suffix}"
    if (proxy_target != "") {
      group(output_target_name) {
        public_deps = [ proxy_target ]
        visibility = output_visibility
        if (defined(invoker.testonly)) {
          testonly = invoker.testonly
        }
      }
      sources_target_name = "${output_target_name}_cpp_sources"
    } else {
      sources_target_name = output_target_name
    }

    target(sources_target_type, sources_target_name) {
      if (defined(output_name_override)) {
        output_name = output_name_override
      }
      visibility = output_visibility + [
                     ":$output_target_name",
                     ":${target_name}_mojolpm",
                   ]
      if (defined(invoker.testonly)) {
        testonly = invoker.testonly
      }
      defines = export_defines
      configs += [ "//build/config/compiler:wexit_time_destructors" ]
      configs += extra_configs
      if (output_file_base_paths != []) {
        sources = []
        foreach(base_path, output_file_base_paths) {
          foreach(suffix, generator_cpp_output_suffixes) {
            sources += [ "$root_gen_dir/${base_path}$suffix" ]
          }
        }
      }
      deps = [
        ":$generator_cpp_message_ids_target_name",
        "//mojo/public/cpp/bindings:struct_traits",
        "//mojo/public/interfaces/bindings:bindings_headers",
      ]
      public_deps = [
        ":$shared_cpp_library_target_name",
        "//base",
      ]
      if (require_full_cpp_deps) {
        public_deps += [ "//mojo/public/cpp/bindings" ]
      } else {
        public_deps += [ "//mojo/public/cpp/bindings:bindings_base" ]
      }

      if (sources_list != []) {
        public_deps += [ ":$generator_target_name" ]
      }
      foreach(d, mojom_cpp_deps) {
        # Resolve the name, so that a target //mojo/something becomes
        # //mojo/something:something and we can append variant_suffix to
        # get the cpp dependency name.
        full_name = get_label_info(d, "label_no_toolchain")
        public_deps += [ "${full_name}${variant_suffix}" ]
      }
      foreach(d, cpp_source_deps) {
        full_name = get_label_info(d, "label_no_toolchain")
        public_deps += [
          "${full_name}${variant_suffix}__has_cpp_proxy",
          "${full_name}${variant_suffix}_cpp_sources",
        ]
      }
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink) {
        if (defined(invoker.overridden_deps_blink)) {
          foreach(d, invoker.overridden_deps_blink) {
            # Resolve the name, so that a target //mojo/something becomes
            # //mojo/something:something and we can append variant_suffix
            # to get the cpp dependency name.
            full_name = get_label_info("$d", "label_no_toolchain")
            public_deps -= [ "${full_name}${variant_suffix}" ]
          }
          public_deps += invoker.component_deps_blink
        }
        if (defined(invoker.check_includes_blink)) {
          check_includes = invoker.check_includes_blink
        }
      } else {
        if (defined(invoker.check_includes_blink)) {
          not_needed(invoker, [ "check_includes_blink" ])
        }
        if (defined(invoker.overridden_deps)) {
          foreach(d, invoker.overridden_deps) {
            # Resolve the name, so that a target //mojo/something becomes
            # //mojo/something:something and we can append variant_suffix
            # to get the cpp dependency name.
            full_name = get_label_info("$d", "label_no_toolchain")
            public_deps -= [ "${full_name}${variant_suffix}" ]
          }
          public_deps += invoker.component_deps
        }
      }
      foreach(config, cpp_typemap_configs) {
        if (defined(config.traits_sources)) {
          sources += config.traits_sources
        }
        if (defined(config.traits_deps)) {
          deps += config.traits_deps
        }
        if (defined(config.traits_public_deps)) {
          public_deps += config.traits_public_deps
        }
      }
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink) {
        public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
      }
    }

    if (generate_mojolpm_fuzzing && !defined(variant)) {
      # This block contains the C++ targets for the MojoLPM fuzzer, we need to
      # do this here so that we can use the typemap configuration for the
      # empty-variant Mojo target.

      mojolpm_target_name = "${target_name}_mojolpm"
      mojolpm_generator_target_name = "${target_name}__generator"
      source_set(mojolpm_target_name) {
        # There are still a few missing header dependencies between mojo targets
        # with typemaps and the dependencies of their typemap headers. It would
        # be good to enable include checking for these in the future though.
        check_includes = false
        testonly = true
        if (defined(invoker.sources)) {
          # Split the input into generated and non-generated source files. They
          # need to be processed separately.
          gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
          non_gen_sources =
              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
          gen_sources =
              filter_include(invoker.sources, [ gen_dir_path_wildcard ])
          sources = process_file_template(
                  non_gen_sources,
                  [
                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
                  ])
          sources += process_file_template(
                  gen_sources,
                  [
                    "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
                    "{{source_dir}}/{{source_file_part}}-mojolpm.h",
                  ])
          deps = [ ":$output_target_name" ]
        } else {
          sources = []
          deps = []
        }

        public_deps = [
          ":$generator_shared_target_name",

          # NB: hardcoded dependency on the no-variant variant generator, since
          # mojolpm only uses the no-variant type.
          ":$mojolpm_generator_target_name",
          ":$mojolpm_proto_target_name",
          "//base",
          "//mojo/public/tools/fuzzers:mojolpm",
        ]

        foreach(d, all_deps) {
          # Resolve the name, so that a target //mojo/something becomes
          # //mojo/something:something and we can append variant_suffix to
          # get the cpp dependency name.
          full_name = get_label_info("$d", "label_no_toolchain")
          public_deps += [ "${full_name}_mojolpm" ]
        }

        foreach(config, cpp_typemap_configs) {
          if (defined(config.traits_deps)) {
            deps += config.traits_deps
          }
          if (defined(config.traits_public_deps)) {
            public_deps += config.traits_public_deps
          }
        }
      }
    }

    if (generate_java && is_android) {
      import("//build/config/android/rules.gni")

      java_generator_target_name = target_name + "_java__generator"
      if (sources_list != []) {
        action(java_generator_target_name) {
          script = mojom_generator_script
          inputs = mojom_generator_sources + jinja2_sources
          sources = sources_list
          deps = [
            ":$parser_target_name",
            ":$type_mappings_target_name",
            "//mojo/public/tools/bindings:precompile_templates",
          ]
          outputs = []
          args = common_generator_args
          filelist = []
          foreach(source, sources_list) {
            filelist += [ rebase_path(source, root_build_dir) ]
          }
          foreach(base_path, output_file_base_paths) {
            outputs += [ "$root_gen_dir/$base_path.srcjar" ]
          }

          response_file_contents = filelist

          args += [
            "--filelist={{response_file_name}}",
            "-g",
            "java",
          ]

          if (!defined(invoker.scramble_message_ids) ||
              invoker.scramble_message_ids) {
            inputs += message_scrambling_inputs
            args += message_scrambling_args
          }
        }
      } else {
        group(java_generator_target_name) {
        }
      }

      java_srcjar_target_name = target_name + "_java_sources"

      action(java_srcjar_target_name) {
        script = "//build/android/gyp/zip.py"
        inputs = []
        if (output_file_base_paths != []) {
          foreach(base_path, output_file_base_paths) {
            inputs += [ "$root_gen_dir/${base_path}.srcjar" ]
          }
        }
        output = "$target_gen_dir/$target_name.srcjar"
        outputs = [ output ]
        rebase_inputs = rebase_path(inputs, root_build_dir)
        rebase_output = rebase_path(output, root_build_dir)
        args = [
          "--input-zips=$rebase_inputs",
          "--output=$rebase_output",
        ]
        deps = []
        if (sources_list != []) {
          deps = [ ":$java_generator_target_name" ]
        }
      }

      java_target_name = target_name + "_java"
      android_library(java_target_name) {
        forward_variables_from(invoker, [ "enable_bytecode_checks" ])
        deps = [
          "//mojo/public/java:bindings_java",
          "//mojo/public/java:system_java",
          "//third_party/androidx:androidx_annotation_annotation_java",
        ]

        # Disable warnings/checks on these generated files.
        chromium_code = false

        foreach(d, all_deps) {
          # Resolve the name, so that a target //mojo/something becomes
          # //mojo/something:something and we can append "_java" to get the java
          # dependency name.
          full_name = get_label_info(d, "label_no_toolchain")
          deps += [ "${full_name}_java" ]
        }

        srcjar_deps = [ ":$java_srcjar_target_name" ]
      }
    }
  }

  if (defined(invoker.generate_webui_js_bindings)) {
    assert(is_chromeos_ash,
           "generate_webui_js_bindings can only be used on ChromeOS Ash")
    assert(invoker.generate_webui_js_bindings,
           "generate_webui_js_bindings should be set to true or removed")
  }

  use_typescript_for_target = defined(invoker.webui_module_path) &&
                              !defined(invoker.generate_webui_js_bindings)

  generate_legacy_js = !use_typescript_for_target ||
                       (defined(invoker.generate_legacy_js_bindings) &&
                        invoker.generate_legacy_js_bindings)

  if (!use_typescript_for_target &&
      defined(invoker.generate_legacy_js_bindings)) {
    not_needed(invoker, [ "generate_legacy_js_bindings" ])
  }

  # Targets needed by both TS and JS bindings targets. These are needed
  # unconditionally for JS bindings targets, and are needed for TS bindings
  # targets when generate_legacy_js_bindings is true. This option is provided
  # since the legacy bindings are needed by Blink tests and non-Chromium users,
  # which are not expected to migrate to modules or TypeScript.
  if (generate_legacy_js && (generate_js_fuzzing ||
                             !defined(invoker.cpp_only) || !invoker.cpp_only)) {
    if (sources_list != []) {
      generator_js_target_name = "${target_name}_js__generator"

      action(generator_js_target_name) {
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources = sources_list
        deps = [
          ":$parser_target_name",
          "//mojo/public/tools/bindings:precompile_templates",
        ]
        if (defined(invoker.parser_deps)) {
          deps += invoker.parser_deps
        }
        outputs = []
        args = common_generator_args
        filelist = []
        foreach(source, sources_list) {
          filelist += [ rebase_path(source, root_build_dir) ]
        }
        foreach(base_path, output_file_base_paths) {
          outputs += [
            "$root_gen_dir/$base_path.js",
            "$root_gen_dir/$base_path.m.js",
            "$root_gen_dir/$base_path-lite.js",
            "$root_gen_dir/$base_path-lite-for-compile.js",
          ]

          if (defined(invoker.webui_module_path) &&
              !use_typescript_for_target) {
            outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
          }
        }

        response_file_contents = filelist

        args += [
          "--filelist={{response_file_name}}",
          "-g",
          "javascript",
        ]

        if (defined(invoker.js_generate_struct_deserializers) &&
            invoker.js_generate_struct_deserializers) {
          args += [ "--js_generate_struct_deserializers" ]
        }

        if (!defined(invoker.scramble_message_ids) ||
            invoker.scramble_message_ids) {
          inputs += message_scrambling_inputs
          args += message_scrambling_args
        }

        if (generate_js_fuzzing) {
          args += [ "--generate_fuzzing" ]
        }
      }
    }

    js_target_name = target_name + "_js"
    group(js_target_name) {
      public_deps = []
      if (sources_list != []) {
        public_deps += [ ":$generator_js_target_name" ]
      }

      foreach(d, all_deps) {
        full_name = get_label_info(d, "label_no_toolchain")
        public_deps += [ "${full_name}_js" ]
      }
    }

    group(js_data_deps_target_name) {
      deps = []
      if (sources_list != []) {
        data = []
        foreach(base_path, output_file_base_paths) {
          data += [
            "$root_gen_dir/${base_path}.js",
            "$root_gen_dir/${base_path}.m.js",
            "$root_gen_dir/${base_path}-lite.js",
          ]
        }
        deps += [ ":$generator_js_target_name" ]
      }

      if (defined(invoker.disallow_native_types) &&
          invoker.disallow_native_types) {
        data_deps = []
      } else {
        data_deps = [ "//mojo/public/js:bindings_module" ]
      }
      foreach(d, all_deps) {
        full_name = get_label_info(d, "label_no_toolchain")
        data_deps += [ "${full_name}_js_data_deps" ]
      }
    }
  }

  # js_library() closure compiler targets, primarily used on ChromeOS. Only
  # generate these targets if the mojom target is not C++ only and is not using
  # TypeScript.
  if (generate_mojom_closure_libraries &&
      (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {
    js_library_for_compile_target_name = "${target_name}_js_library_for_compile"
    if (sources_list != []) {
      js_library(js_library_for_compile_target_name) {
        extra_public_deps = [ ":$generator_js_target_name" ]
        sources = []
        foreach(base_path, output_file_base_paths) {
          sources += [ "$root_gen_dir/${base_path}-lite-for-compile.js" ]
        }
        externs_list = [
          "${externs_path}/mojo_core.js",
          "${externs_path}/pending.js",
        ]
        deps = []
        if (!defined(invoker.disallow_native_types)) {
          deps += [ "//mojo/public/js:bindings_lite_sources" ]
        }
        foreach(d, all_deps) {
          full_name = get_label_info(d, "label_no_toolchain")
          deps += [ "${full_name}_js_library_for_compile" ]
        }
      }
    } else {
      group(js_library_for_compile_target_name) {
      }
    }

    # WebUI specific closure targets, not needed by targets that are generating
    # TypeScript WebUI bindings or by legacy-only targets.
    if (defined(invoker.webui_module_path) && !use_typescript_for_target) {
      webui_js_target_name = "${target_name}_webui_js"
      if (sources_list != []) {
        js_library(webui_js_target_name) {
          extra_public_deps = [ ":$generator_js_target_name" ]
          sources = []
          foreach(base_path, output_file_base_paths) {
            sources += [ "$root_gen_dir/mojom-webui/${base_path}-webui.js" ]
          }
          externs_list = [
            "${externs_path}/mojo_core.js",
            "${externs_path}/pending.js",
          ]
          if (defined(invoker.disallow_native_types) &&
              invoker.disallow_native_types) {
            deps = []
          } else {
            deps = [ "//mojo/public/js:bindings_uncompiled" ]
          }
          foreach(d, all_deps) {
            full_name = get_label_info(d, "label_no_toolchain")
            deps += [ "${full_name}_webui_js" ]
          }
        }
      } else {
        group(webui_js_target_name) {
        }
      }

      webui_grdp_target_name = "${target_name}_webui_grdp"
      out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
      grd_prefix = "${target_name}_webui"
      generate_grd(webui_grdp_target_name) {
        grd_prefix = grd_prefix
        out_grd = out_grd

        deps = [ ":$webui_js_target_name" ]

        input_files = []
        foreach(base_path, output_file_base_paths) {
          input_files += [ "${base_path}-webui.js" ]
        }

        input_files_base_dir =
            rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
      }
    }
  }
  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
       !invoker.cpp_only) && use_typescript_for_target) {
    if (sources_list != []) {
      source_filelist = []
      foreach(source, sources_list) {
        source_filelist += [ rebase_path(source, root_build_dir) ]
      }

      # Generate Typescript bindings.
      generator_ts_target_name = "${target_name}_ts__generator"

      action(generator_ts_target_name) {
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources = sources_list
        deps = [
          ":$parser_target_name",
          "//mojo/public/tools/bindings:precompile_templates",
        ]

        outputs = []
        foreach(base_path, output_file_base_paths) {
          outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
        }
        args = common_generator_args
        response_file_contents = source_filelist

        args += [
          "--filelist={{response_file_name}}",
          "-g",
          "typescript",
        ]

        if (!defined(invoker.scramble_message_ids) ||
            invoker.scramble_message_ids) {
          inputs += message_scrambling_inputs
          args += message_scrambling_args
        }

        if (defined(invoker.js_generate_struct_deserializers) &&
            invoker.js_generate_struct_deserializers) {
          args += [ "--js_generate_struct_deserializers" ]
        }

        # TODO(crbug.com/1007587): Support scramble_message_ids if above is
        # insufficient.
        # TODO(crbug.com/1007591): Support generate_fuzzing.
      }
    }
  }
}

# A helper for the mojom() template above when component libraries are desired
# for generated C++ bindings units. Supports all the same arguments as mojom()
# except for the optional |component_output_prefix| and |component_macro_prefix|
# arguments. These are instead shortened to |output_prefix| and |macro_prefix|
# and are *required*.
template("mojom_component") {
  assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix))

  mojom(target_name) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "output_prefix",
                             "macro_prefix",
                           ])
    component_output_prefix = invoker.output_prefix
    component_macro_prefix = invoker.macro_prefix
  }
}
