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

import("//build/config/jumbo.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/nacl/config.gni")
import("//components/nacl/features.gni")
import("//third_party/jinja2/jinja2.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. Set to |false| to disable message ID
  # scrambling on all platforms.
  enable_mojom_message_id_scrambling = true
}

# 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.
enable_scrambled_message_ids =
    enable_mojom_message_id_scrambling &&
    (is_mac || is_win || (is_linux && !is_chromeos) ||
     ((enable_nacl || is_nacl || is_nacl_nonsfi) && target_os != "chromeos"))

mojom_generator_root = "//mojo/public/tools/bindings"
mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
mojom_generator_sources = [
  "$mojom_generator_root/generators/mojom_cpp_generator.py",
  "$mojom_generator_root/generators/mojom_js_generator.py",
  "$mojom_generator_root/generators/mojom_java_generator.py",
  "$mojom_generator_root/pylib/mojom/__init__.py",
  "$mojom_generator_root/pylib/mojom/error.py",
  "$mojom_generator_root/pylib/mojom/generate/__init__.py",
  "$mojom_generator_root/pylib/mojom/generate/constant_resolver.py",
  "$mojom_generator_root/pylib/mojom/generate/generator.py",
  "$mojom_generator_root/pylib/mojom/generate/module.py",
  "$mojom_generator_root/pylib/mojom/generate/pack.py",
  "$mojom_generator_root/pylib/mojom/generate/template_expander.py",
  "$mojom_generator_root/pylib/mojom/generate/translate.py",
  "$mojom_generator_root/pylib/mojom/parse/__init__.py",
  "$mojom_generator_root/pylib/mojom/parse/ast.py",
  "$mojom_generator_root/pylib/mojom/parse/lexer.py",
  "$mojom_generator_root/pylib/mojom/parse/parser.py",
  "$mojom_generator_script",
]

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"

    # The path to a file whose contents will be concatenated to the contents of
    # the file at |mojom_message_id_salt_path| to form a complete salt for
    # message ID scrambling. May be the empty string, in which case the contents
    # of the above file alone are used as the complete salt.
    if (is_chrome_branded) {
      mojom_message_id_salt_suffix_path =
          "//mojo/internal/chrome-message-id-salt-suffix"
    } else {
      mojom_message_id_salt_suffix_path = ""
    }
  }

  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 ]

  if (mojom_message_id_salt_suffix_path != "") {
    message_scrambling_args += [
      "--scrambled_message_id_salt_path",
      rebase_path(mojom_message_id_salt_suffix_path, root_build_dir),
    ]
    message_scrambling_inputs += [ mojom_message_id_salt_suffix_path ]
  }
} else {
  message_scrambling_args = []
  message_scrambling_inputs = []
}

if (enable_mojom_typemapping) {
  if (!is_ios) {
    _bindings_configuration_files = [
      "//mojo/public/tools/bindings/chromium_bindings_configuration.gni",
      "//mojo/public/tools/bindings/blink_bindings_configuration.gni",
    ]
  } else {
    _bindings_configuration_files =
        [ "//mojo/public/tools/bindings/chromium_bindings_configuration.gni" ]
  }
  _bindings_configurations = []
  foreach(config_file, _bindings_configuration_files) {
    _bindings_configurations += [ read_file(config_file, "scope") ]
  }
  foreach(configuration, _bindings_configurations) {
    # Check that the mojom field of each typemap refers to a mojom that exists.
    foreach(typemap, configuration.typemaps) {
      _typemap_config = {
      }
      _typemap_config = typemap.config
      read_file(_typemap_config.mojom, "")
    }
  }
} else {
  _bindings_configuration_files = []
  _bindings_configurations = [
    {
      typemaps = []
      component_macro_suffix = ""
    },
    {
      variant = "blink"
      component_macro_suffix = "_BLINK"
      for_blink = true
      typemaps = []
    },
  ]
}

# 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.
#
#   import_dirs (optional)
#       List of import directories that will get added when processing sources.
#
#   testonly (optional)
#
#   visibility (optional)
#
#   visibility_blink (optional)
#       The value to use for visibility for the blink variant. If unset,
#       |visibility| is used.
#
#   use_once_callback (optional)
#       If set to true, generated classes will use base::OnceCallback instead of
#       base::RepeatingCallback.
#       Default value is true.
#       TODO(dcheng):
#           - Convert everything to use OnceCallback.
#           - Remove support for the old mode.
#
#   cpp_only (optional)
#       If set to true, only the C++ bindings targets will be generated.
#
#   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.
#
# 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 are mutually
# exclusive to |component_macro_prefix|, but |component_output_prefix| may still
# be used to uniqueify the generated invariant (i.e. shared) output component.
#   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.
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))
    assert(defined(invoker.export_header))
    assert(!defined(invoker.component_macro_prefix))
  }
  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))
    assert(defined(invoker.export_header_blink))
    assert(!defined(invoker.component_macro_prefix))
  }
  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))
  }

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

  all_deps = []
  if (defined(invoker.deps)) {
    all_deps += invoker.deps
  }
  if (defined(invoker.public_deps)) {
    all_deps += invoker.public_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})" ]
    }
  }

  target_sources_list = "$target_gen_dir/$target_name.sources_list"
  sources_list = []
  if (defined(invoker.sources)) {
    sources_list = invoker.sources
  }
  write_file(target_sources_list, sources_list)

  # a target implicitly depends on its own sources
  deps_sources = [ rebase_path(target_sources_list, root_build_dir) ]
  foreach(d, all_deps) {
    dep_dir = get_label_info("$d", "target_gen_dir")
    dep_short_name = get_label_info("$d", "name")
    deps_sources +=
        [ rebase_path("$dep_dir/$dep_short_name.sources_list", root_build_dir) ]
  }

  write_file("$target_gen_dir/$target_name.deps_sources_list", deps_sources)

  if (defined(invoker.sources)) {
    parser_target_name = "${target_name}__parser"
    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) {
      enabled_features += [ "is_chromeos" ]
    } 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" ]
    }

    action(parser_target_name) {
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources = invoker.sources
      outputs = []
      filelist = []
      foreach(source, invoker.sources) {
        filename = get_path_info("$source", "name")
        dirname = get_path_info("$source", "gen_dir")
        outputs += [ "$dirname/$filename.p" ]
        filelist += [ rebase_path("$source", root_build_dir) ]
      }

      response_file_contents = filelist

      args = [
        "parse",
        "--filelist={{response_file_name}}",
        "-o",
        rebase_path(root_gen_dir, root_build_dir),
        "-d",
        rebase_path("//", root_build_dir),
      ]
      foreach(enabled_feature, enabled_features) {
        args += [
          "--enable_feature",
          enabled_feature,
        ]
      }
    }
  }

  parsed_target_name = "${target_name}__parsed"
  group(parsed_target_name) {
    public_deps = []
    if (defined(invoker.sources)) {
      public_deps += [ ":$parser_target_name" ]
    }
    foreach(d, all_deps) {
      # Resolve the name, so that a target //mojo/something becomes
      # //mojo/something:something and we can append the parsed
      # suffix to get the mojom dependency name.
      full_name = get_label_info("$d", "label_no_toolchain")
      public_deps += [ "${full_name}__parsed" ]
    }
  }

  if (defined(invoker.sources)) {
    verify_deps_target_names = []
    if (!defined(invoker.skip_deps_check) || !invoker.skip_deps_check) {
      verify_deps_target_name = "${target_name}__verify_deps"
      verify_deps_target_names += [ ":$verify_deps_target_name" ]
      source_file_name = target_name

      action(verify_deps_target_name) {
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources = invoker.sources
        deps = [
          ":$parsed_target_name",
        ]
        outputs = []
        filelist = []
        foreach(source, invoker.sources) {
          filename = get_path_info("$source", "name")
          dirname = get_path_info("$source", "gen_dir")
          outputs += [ "$dirname/$filename.v" ]
          filelist += [ rebase_path("$source", root_build_dir) ]
        }

        response_file_contents = filelist

        args = [
          "verify",
          "--filelist={{response_file_name}}",
          "-f",
          rebase_path("$target_gen_dir/$source_file_name.deps_sources_list",
                      root_build_dir),
          "--gen_dir",
          rebase_path(root_gen_dir, root_build_dir),
          "--depth",
          rebase_path("//", root_build_dir),
        ]
      }
    }
  }

  generator_cpp_message_ids_target_name = "${target_name}__generate_message_ids"

  # Generate code that is shared by different variants.
  if (defined(invoker.sources)) {
    common_generator_args = [
      "--use_bundled_pylibs",
      "generate",
      "-d",
      rebase_path("//", root_build_dir),
      "-I",
      rebase_path("//", root_build_dir),
      "-o",
      rebase_path(root_gen_dir, root_build_dir),
      "--bytecode_path",
      rebase_path("$root_gen_dir/mojo/public/tools/bindings", 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) {
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources = invoker.sources
      deps = [
        ":$parsed_target_name",
        "//mojo/public/tools/bindings:precompile_templates",
      ]
      outputs = []
      args = common_generator_args
      filelist = []
      foreach(source, invoker.sources) {
        outputs += [ "$target_gen_dir/$source-shared-message-ids.h" ]
        filelist += [ rebase_path("$source", root_build_dir) ]
      }

      response_file_contents = filelist

      args += [
        "--filelist={{response_file_name}}",
        "--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_cpp_outputs = [
      "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
      "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc",
      "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.h",
    ]
    generator_shared_target_name = "${target_name}_shared__generator"
    action(generator_shared_target_name) {
      script = mojom_generator_script
      inputs = mojom_generator_sources + jinja2_sources
      sources = invoker.sources
      deps = [
               ":$parsed_target_name",
               "//mojo/public/tools/bindings:precompile_templates",
             ] + verify_deps_target_names

      outputs = []
      args = common_generator_args
      filelist = []
      foreach(source, invoker.sources) {
        filelist += [ rebase_path("$source", root_build_dir) ]
        outputs += [
          "$target_gen_dir/$source-shared-internal.h",
          "$target_gen_dir/$source-shared.cc",
          "$target_gen_dir/$source-shared.h",
        ]
      }

      response_file_contents = filelist

      args += [
        "--filelist={{response_file_name}}",
        "--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",
        ]
      }
    }
  } else {
    group(generator_cpp_message_ids_target_name) {
    }
  }

  shared_cpp_sources_target_name = "${target_name}_shared_cpp_sources"
  jumbo_source_set(shared_cpp_sources_target_name) {
    if (defined(invoker.testonly)) {
      testonly = invoker.testonly
    }
    deps = []
    if (defined(invoker.sources)) {
      sources =
          process_file_template(invoker.sources, generator_shared_cpp_outputs)
      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",
      ]
    }
  }

  # Generate code for variants.
  if (!defined(invoker.disable_variants) || !invoker.disable_variants) {
    enabled_configurations = _bindings_configurations
  } else {
    first_config = _bindings_configurations[0]
    assert(!defined(first_config.variant))
    enabled_configurations = [ first_config ]
  }
  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
    }
    type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings"
    type_mappings_path =
        "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
    active_typemaps = []
    enabled_sources = []
    if (defined(invoker.sources)) {
      generator_cpp_outputs = []
      variant_dash_suffix = ""
      if (defined(variant)) {
        variant_dash_suffix = "-${variant}"
      }
      generator_cpp_outputs += [
        "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc",
        "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.h",
      ]
      enabled_sources = []
      if (defined(bindings_configuration.blacklist)) {
        foreach(source, invoker.sources) {
          blacklisted = false
          foreach(blacklisted_source, bindings_configuration.blacklist) {
            if (get_path_info(source, "abspath") == blacklisted_source) {
              blacklisted = true
            }
          }
          if (!blacklisted) {
            enabled_sources += [ source ]
          }
        }
      } else {
        enabled_sources = invoker.sources
      }
      foreach(source, enabled_sources) {
        # TODO(sammc): Use a map instead of a linear scan when GN supports maps.
        foreach(typemap, bindings_configuration.typemaps) {
          _typemap_config = {
          }
          _typemap_config = typemap.config
          if (get_path_info(source, "abspath") == _typemap_config.mojom) {
            enabled = false
            if (!defined(_typemap_config.os_whitelist)) {
              enabled = true
            } else {
              foreach(os, _typemap_config.os_whitelist) {
                if (os == "android" && is_android) {
                  enabled = true
                } else if (os == "chromeos" && is_chromeos) {
                  enabled = true
                } else if (os == "fuchsia" && is_fuchsia) {
                  enabled = true
                } else if (os == "ios" && is_ios) {
                  enabled = true
                } else if (os == "linux" && is_linux) {
                  enabled = true
                } else if (os == "mac" && is_mac) {
                  enabled = true
                } else if (os == "posix" && is_posix) {
                  enabled = true
                } else if (os == "win" && is_win) {
                  enabled = true
                }
              }
            }
            if (enabled) {
              active_typemaps += [ typemap ]
            }
          }
        }
      }

      generator_target_name = "${target_name}${variant_suffix}__generator"
      action(generator_target_name) {
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources = invoker.sources
        deps = [
                 ":$parsed_target_name",
                 ":$type_mappings_target_name",
                 "//mojo/public/tools/bindings:precompile_templates",
               ] + verify_deps_target_names
        outputs = []
        args = common_generator_args
        filelist = []
        foreach(source, invoker.sources) {
          filelist += [ rebase_path("$source", root_build_dir) ]
          outputs += [
            "$target_gen_dir/${source}${variant_dash_suffix}.cc",
            "$target_gen_dir/${source}${variant_dash_suffix}.h",
          ]
        }

        response_file_contents = filelist

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

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

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

        if (defined(invoker.component_macro_prefix)) {
          args += [
            "--export_attribute",
            "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
                "${bindings_configuration.component_macro_suffix})",
            "--export_header",
            "base/component_export.h",
          ]
        }

        if (defined(bindings_configuration.for_blink) &&
            bindings_configuration.for_blink) {
          args += [ "--for_blink" ]
          if (defined(invoker.export_class_attribute_blink)) {
            args += [
              "--export_attribute",
              invoker.export_class_attribute_blink,
              "--export_header",
              invoker.export_header_blink,
            ]
          }
        } else {
          if (defined(invoker.export_class_attribute)) {
            args += [
              "--export_attribute",
              invoker.export_class_attribute,
              "--export_header",
              invoker.export_header,
            ]
          }
        }

        if (!defined(invoker.use_once_callback) || invoker.use_once_callback) {
          args += [ "--use_once_callback" ]
        }

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

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

      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 =
            rebase_path("$dependency_output_dir/${dependency_name}",
                        root_build_dir)
        args += [
          "--dependency",
          dependency_path,
        ]
      }

      if (enabled_sources != []) {
        # TODO(sammc): Pass the typemap description in a file to avoid command
        # line length limitations.
        typemap_description = []
        foreach(typemap, active_typemaps) {
          _typemap_config = {
          }
          _typemap_config = typemap.config
          typemap_description += [ "--start-typemap" ]
          if (defined(_typemap_config.public_headers)) {
            foreach(value, _typemap_config.public_headers) {
              typemap_description += [ "public_headers=$value" ]
            }
          }
          if (defined(_typemap_config.traits_headers)) {
            foreach(value, _typemap_config.traits_headers) {
              typemap_description += [ "traits_headers=$value" ]
            }
          }
          foreach(value, _typemap_config.type_mappings) {
            typemap_description += [ "type_mappings=$value" ]
          }

          # The typemap configuration files are not actually used as inputs here
          # but this establishes a necessary build dependency to ensure that
          # typemap changes force a rebuild of affected targets.
          inputs += [ typemap.filename ]
        }
        args += typemap_description
      }
    }

    if (defined(invoker.component_macro_prefix)) {
      output_target_type = "component"
    } else {
      output_target_type = "source_set"
    }

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

    target("jumbo_" + output_target_type, "${target_name}${variant_suffix}") {
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink &&
          defined(invoker.visibility_blink)) {
        visibility = invoker.visibility_blink
      } else if (defined(invoker.visibility)) {
        visibility = invoker.visibility
      }
      if (defined(invoker.testonly)) {
        testonly = invoker.testonly
      }
      defines = []
      if (defined(invoker.export_define)) {
        defines += [ invoker.export_define ]
      }
      if (defined(invoker.export_define_blink)) {
        defines += [ invoker.export_define_blink ]
      }
      if (enabled_sources != []) {
        sources = process_file_template(enabled_sources, generator_cpp_outputs)
      }
      deps = [
        ":$generator_cpp_message_ids_target_name",
        "//mojo/public/cpp/bindings:struct_traits",
        "//mojo/public/interfaces/bindings:bindings__generator",
        "//mojo/public/interfaces/bindings:bindings_shared__generator",
      ]
      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 (enabled_sources != []) {
        public_deps += [ ":$generator_target_name" ]
      }
      if (defined(invoker.component_macro_prefix)) {
        output_name = "${invoker.component_output_prefix}${variant_suffix}"
        defines += [ "IS_${invoker.component_macro_prefix}" +
                     "${bindings_configuration.component_macro_suffix}_IMPL" ]
      }
      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}${variant_suffix}" ]
      }
      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(typemap, active_typemaps) {
        _typemap_config = {
        }
        _typemap_config = typemap.config
        if (defined(_typemap_config.sources)) {
          sources += _typemap_config.sources
        }
        if (defined(_typemap_config.public_deps)) {
          public_deps += _typemap_config.public_deps
        }
        if (defined(_typemap_config.deps)) {
          deps += _typemap_config.deps
        }
      }
      if (defined(invoker.export_header)) {
        sources += [ "//" + invoker.export_header ]
      }
      if (defined(bindings_configuration.for_blink) &&
          bindings_configuration.for_blink) {
        public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
      }

      if (enable_ipc_fuzzer) {
        # Generate JS bindings by default if IPC fuzzer is enabled.
        public_deps += [ ":$js_data_deps_target_name" ]
      }
    }

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

      java_generator_target_name = target_name + "_java__generator"
      if (enabled_sources != []) {
        generator_java_outputs =
            [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ]
        action(java_generator_target_name) {
          script = mojom_generator_script
          inputs = mojom_generator_sources + jinja2_sources
          sources = enabled_sources
          deps = [
                   ":$parsed_target_name",
                   ":$type_mappings_target_name",
                   "//mojo/public/tools/bindings:precompile_templates",
                 ] + verify_deps_target_names
          outputs = []
          args = common_generator_args
          filelist = []
          foreach(source, invoker.sources) {
            filelist += [ rebase_path("$source", root_build_dir) ]
            outputs += [ "$target_gen_dir/$source.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 = "//mojo/public/tools/gn/zip.py"
        inputs = []
        if (enabled_sources != []) {
          inputs =
              process_file_template(enabled_sources, generator_java_outputs)
        }
        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 = [
          "--zip-inputs=$rebase_inputs",
          "--output=$rebase_output",
        ]
        deps = []
        if (enabled_sources != []) {
          deps = [
            ":$java_generator_target_name",
          ]
        }
      }

      java_target_name = target_name + "_java"
      android_library(java_target_name) {
        deps = [
          "//base:base_java",
          "//mojo/public/java:bindings_java",
          "//mojo/public/java:system_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 (enable_ipc_fuzzer || !defined(invoker.cpp_only) || !invoker.cpp_only) {
    if (defined(invoker.sources)) {
      generator_js_target_name = "${target_name}_js__generator"
      generator_js_outputs = [
        "{{source_gen_dir}}/{{source_name_part}}.mojom.js",
        "{{source_gen_dir}}/{{source_name_part}}.mojom.externs.js",
      ]
      action(generator_js_target_name) {
        script = mojom_generator_script
        inputs = mojom_generator_sources + jinja2_sources
        sources = []
        if (defined(invoker.sources)) {
          sources += invoker.sources
        }
        deps = [
                 ":$parsed_target_name",
                 "//mojo/public/tools/bindings:precompile_templates",
               ] + verify_deps_target_names
        outputs = []
        args = common_generator_args
        filelist = []
        foreach(source, invoker.sources) {
          filelist += [ rebase_path("$source", root_build_dir) ]
          outputs += [
            "$target_gen_dir/$source.js",
            "$target_gen_dir/$source.externs.js",
          ]
        }

        response_file_contents = filelist

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

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

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

    js_target_name = target_name + "_js"
    group(js_target_name) {
      public_deps = []
      if (defined(invoker.sources)) {
        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 (defined(invoker.sources)) {
        data = process_file_template(invoker.sources, generator_js_outputs)
        deps += [ ":$generator_js_target_name" ]
      }

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

# 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
  }
}