chromium/third_party/protobuf/proto_library.gni

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

# Compile a protocol buffer.
#
# Protobuf parameters:
#
#   proto_in_dir (optional)
#       Specifies the path relative to the current BUILD.gn file where
#       proto files are located and the directory structure of
#       this proto library starts.
#
#       This option can be calculated automatically but it will raise an
#       assertion error if any nested directories are found.
#
#   proto_out_dir (optional)
#       Specifies the path suffix that output files are generated under.
#       This path will be appended to |root_gen_dir|, but for python stubs
#       it will be appended to |root_build_dir|/pyproto.
#
#   generate_python (optional, default true)
#       Generate Python protobuf stubs.
#
#   generate_cc (optional, default true)
#       Generate C++ protobuf stubs.
#
#   generate_javascript (optional, default false)
#       Generate Javascript protobuf stubs.
#
#   generate_library (optional, default true)
#       Generate a "static_library" target for linking with the generated code.
#
#   generate_py_runtime (optional, default false)
#       Generates a "_py_runtime"-suffixed target for test targets that need the
#       Python stubs available at runtime.
#
#   cc_generator_options (optional)
#       List of extra flags passed to the protocol compiler.  If you need to
#       add an EXPORT macro to a protobuf's C++ header, set the
#       'cc_generator_options' variable with the value:
#       'dllexport_decl=FOO_EXPORT:' (note trailing colon).
#
#       It is likely you also need to #include a file for the above EXPORT
#       macro to work (see cc_include) and set
#       component_build_force_source_set = true.
#
#   cc_include (optional)
#       String listing an extra include that should be passed.
#       Example: cc_include = "foo/bar.h"
#
#   generator_plugin_label (optional)
#       GN label for plugin executable which generates custom cc stubs.
#       Don't specify a toolchain, host toolchain is assumed.
#
#   generator_plugin_script (optional)
#       Path to plugin script. Mutually exclusive with |generator_plugin_label|.
#
#   generator_plugin_script_deps (optional)
#       List of additional files required for generator plugin script.
#
#   generator_plugin_suffix[es] (required if using a plugin)
#       Suffix (before extension) for generated .cc and .h files
#       or list of suffixes for all files (with extensions).
#
#   generator_plugin_options (optional)
#       Extra flags passed to the plugin. See cc_generator_options.
#
#   deps (optional)
#       DEPRECATED: use proto_deps or link_deps instead (crbug.com/938011).
#       Additional dependencies.
#
#   link_deps (optional)
#       Additional dependencies linked to library.
#
#   proto_deps (optional)
#       Additional dependencies required before running protoc.
#       e.g. proto file generating action.
#
#   proto_data_sources (optional)
#       Additional proto sources that will only be used for references
#       and will not be compiled.
#       This should be used in conjunction with import_dirs.
#
#   use_protobuf_full (optional)
#       If adding protobuf library would be required, adds protobuf_full to deps
#       instead of protobuf_lite.
#
#   import_dirs (optional)
#       A list of extra import directories to be passed to protoc compiler.
#       WARNING: This circumvents proto checkdeps, and should only be used when
#       needed, typically when proto files cannot cleanly import through
#       absolute paths, such as for third_party or generated .proto files.
#       http://crbug.com/691451 tracks fixing this.
#
# Parameters for compiling the generated code:
#
#   force_source_set (Default=false)
#       When set true the generated code will be compiled as a source set.
#       This can be useful if you need to export the generated symbols from a
#       shared library. You should use this carefully, as you probably only
#       want this if your dependencies are *always* shared libraries. Most
#       of the time, you probably want `component_build_force_source_set`
#       instead (see the next option).
#   component_build_force_source_set (Default=false)
#       When set true the generated code will be compiled as a source set in
#       the component build. This does not affect static builds.  If you are
#       exporting symbols from a component, this is required to prevent those
#       symbols from being stripped. If you're not using dllexports in
#       cc_generator_options, it's usually best to leave this false.
#
#   defines (optional)
#       Defines to supply to the source set that compiles the generated source
#       code.
#
#   extra_configs (optional)
#       A list of config labels that will be appended to the configs applying
#       to the source set.
#
#   remove_configs (optional)
#       A list of config labels that will be removed from the configs apllying
#       to the source set.
#
#   propagate_imports_configs (optional)
#       A boolean value (defaults to true) that specifies whether the config
#       generated for the library's import directories will be propagated to
#       dependents as one of the library target's public_configs. See
#       crbug.com/1043279#c11 and crbug.com/gn/142 for why this option exists.
#       WARNING: If set to false, the embedder target is responsible for
#       propagating a suitable config, so that any dependent headers can resolve
#       includes generated by proto imports.
#
# Example:
#  proto_library("mylib") {
#    sources = [
#      "foo.proto",
#    ]
#  }

import("//build/config/cronet/config.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/kythe.gni")

declare_args() {
  # Allows subprojects to omit javascript dependencies (e.g.) closure_compiler
  # and google-closure-library.
  enable_js_protobuf = !is_cronet_build
}

if (enable_js_protobuf) {
  import("//third_party/closure_compiler/compile_js.gni")
}

if (host_os == "win") {
  _host_executable_suffix = ".exe"
} else {
  _host_executable_suffix = ""
}

# TODO(https://crbug.com/337736622): V8 shares this dependency and stores
# it in a different location. Hence, all references to this folder should
# use this variable instead of hard-coding //third_party/protobuf.
# This can be switched back to //third_party/protobuf in M129, or earlier in
# case crbug.com/338008085 is resolved.
_this_dir = get_path_info(".", "abspath")

_protoc_label = "$_this_dir:protoc($host_toolchain)"
_protoc_path = get_label_info(_protoc_label, "root_out_dir") + "/protoc" +
               _host_executable_suffix
_protoc_gen_js_label =
    "//third_party/protobuf-javascript:protoc-gen-js($host_toolchain)"
_protoc_gen_js_path = get_label_info(_protoc_gen_js_label, "root_out_dir") +
                      "/protoc-gen-js" + _host_executable_suffix

template("proto_library") {
  assert(defined(invoker.sources), "Need sources for proto_library")
  proto_sources = invoker.sources

  if (defined(invoker.generate_cc)) {
    generate_cc = invoker.generate_cc
  } else {
    generate_cc = true
  }

  if (defined(invoker.generate_python)) {
    generate_python = invoker.generate_python
  } else {
    generate_python = true
  }

  if (defined(invoker.generate_javascript)) {
    generate_javascript = invoker.generate_javascript
  } else {
    generate_javascript = false
  }

  if (defined(invoker.generate_descriptor)) {
    generate_descriptor = invoker.generate_descriptor
  } else {
    generate_descriptor = ""
  }

  if (defined(invoker.generate_py_runtime)) {
    generate_py_runtime = invoker.generate_py_runtime
  } else {
    generate_py_runtime = false
  }
  if (generate_py_runtime) {
    generate_python = true
  }

  # exclude_imports is only used for generating the descriptor. Therefore, the
  # check needs to be here to avoid complaints from GN about the unused
  # variable.
  if (generate_descriptor != "") {
    if (defined(invoker.exclude_imports)) {
      exclude_imports = invoker.exclude_imports
    } else {
      exclude_imports = false
    }
  }

  if (defined(invoker.generator_plugin_label)) {
    # Straightforward way to get the name of executable doesn't work because
    # |root_out_dir| and |root_build_dir| may differ in cross-compilation and
    # also Windows executables have .exe at the end.

    plugin_host_label = invoker.generator_plugin_label + "($host_toolchain)"
    plugin_path =
        get_label_info(plugin_host_label, "root_out_dir") + "/" +
        get_label_info(plugin_host_label, "name") + _host_executable_suffix
    generate_with_plugin = true
  } else if (defined(invoker.generator_plugin_script)) {
    plugin_path = invoker.generator_plugin_script
    generate_with_plugin = true
  } else {
    generate_with_plugin = false
  }

  if (generate_with_plugin) {
    if (defined(invoker.generator_plugin_suffix)) {
      generator_plugin_suffixes = [
        "${invoker.generator_plugin_suffix}.h",
        "${invoker.generator_plugin_suffix}.cc",
      ]
    } else {
      generator_plugin_suffixes = invoker.generator_plugin_suffixes
    }
  }

  if (defined(invoker.proto_in_dir)) {
    proto_in_dir = invoker.proto_in_dir
    has_nested_dirs = false
    foreach(proto_source, proto_sources) {
      if (get_path_info(proto_source, "dir") != proto_in_dir) {
        has_nested_dirs = true
      }
    }
  } else {
    proto_in_dir = get_path_info(proto_sources[0], "dir")
    has_nested_dirs = false

    # Sanity check, |proto_in_dir| should be defined to allow sub-directories.
    foreach(proto_source, proto_sources) {
      assert(get_path_info(proto_source, "dir") == proto_in_dir,
             "Please define |proto_in_dir| to allow nested directories.")
    }
  }

  # Avoid absolute path because of the assumption that |proto_in_dir| is
  # relative to the directory of current BUILD.gn file.
  proto_in_dir = rebase_path(proto_in_dir, ".")

  if (defined(invoker.proto_out_dir)) {
    proto_out_dir = invoker.proto_out_dir
  } else {
    # Absolute path to the directory of current BUILD.gn file excluding "//".
    proto_out_dir = rebase_path(".", "//")
    if (proto_in_dir != ".") {
      proto_out_dir += "/$proto_in_dir"
    }
  }

  # We need both absolute path to use in GN statements and a relative one
  # to pass to external script.
  if (generate_cc || generate_with_plugin) {
    cc_out_dir = "$root_gen_dir/" + proto_out_dir
    rel_cc_out_dir = rebase_path(cc_out_dir, root_build_dir)
  }
  if (generate_python) {
    py_out_dir = "$root_out_dir/pyproto/" + proto_out_dir
    rel_py_out_dir = rebase_path(py_out_dir, root_build_dir)
  }
  if (generate_javascript) {
    js_out_dir = "$root_out_dir/jsproto/" + proto_out_dir
    rel_js_out_dir = rebase_path(js_out_dir, root_build_dir)
  }
  if (generate_descriptor != "") {
    descriptor_out =
        "$root_gen_dir/" + proto_out_dir + "/" + generate_descriptor
    rel_descriptor_out = rebase_path(descriptor_out, root_build_dir)
  }

  protos = rebase_path(invoker.sources, proto_in_dir)
  protogens = []
  protogens_py = []
  protogens_cc = []
  protogens_js = []

  # Whether source code bindings should be generated.
  generate_sources = generate_cc || generate_python || generate_with_plugin ||
                     generate_javascript

  # Whether library should be generated.
  # Library is not needed when proto_library is used to generate binary descriptor, in which case
  # corresponding library target should be omitted entirely.
  if (defined(invoker.generate_library)) {
    generate_library = invoker.generate_library
  } else {
    generate_library = generate_sources
  }

  # List output files.
  if (generate_sources) {
    foreach(proto, protos) {
      proto_dir = get_path_info(proto, "dir")
      proto_name = get_path_info(proto, "name")
      proto_path = proto_dir + "/" + proto_name

      if (generate_cc) {
        protogens_cc += [
          "$cc_out_dir/$proto_path.pb.h",
          "$cc_out_dir/$proto_path.pb.cc",
        ]
      }
      if (generate_python) {
        protogens_py += [ "$py_out_dir/${proto_path}_pb2.py" ]
      }
      if (generate_with_plugin) {
        foreach(suffix, generator_plugin_suffixes) {
          protogens_cc += [ "$cc_out_dir/${proto_path}${suffix}" ]
        }
      }
      if (generate_javascript) {
        protogens_js += [ "$js_out_dir/${proto_path}.js" ]
      }
    }
  }

  # If descriptor needs to be generated, it should be added to list of outputs once.
  if (generate_descriptor != "") {
    protogens += [ descriptor_out ]
  }

  action_name = "${target_name}_gen"
  source_set_name = target_name
  javascript_name = "${target_name}_js"
  py_runtime_name = "${target_name}_py_runtime"

  # Generate protobuf stubs.
  action(action_name) {
    visibility = [
      ":$javascript_name",
      ":$py_runtime_name",
      ":$source_set_name",
    ]
    script = "//tools/protoc_wrapper/protoc_wrapper.py"
    args = protos

    sources = proto_sources
    outputs =
        get_path_info(protogens + protogens_cc + protogens_js + protogens_py,
                      "abspath")

    if (defined(invoker.testonly)) {
      testonly = invoker.testonly
    }

    args += [
      # Wrapper should never pick a system protoc.
      # Path should be rebased because |root_build_dir| for current toolchain
      # may be different from |root_out_dir| of protoc built on host toolchain.
      "--protoc",
      "./" + rebase_path(_protoc_path, root_build_dir),
      "--proto-in-dir",
      rebase_path(proto_in_dir, root_build_dir),
    ]

    if (generate_cc) {
      args += [
        "--cc-out-dir",
        rel_cc_out_dir,
      ]
      if (enable_kythe_annotations) {
        args += [ "--enable-kythe-annotation" ]
      }
      if (defined(invoker.cc_generator_options)) {
        args += [
          "--cc-options",
          invoker.cc_generator_options,
        ]
      }
      if (defined(invoker.cc_include)) {
        args += [
          "--include",
          invoker.cc_include,
        ]
      }
    }

    if (generate_python) {
      args += [
        "--py-out-dir",
        rel_py_out_dir,
      ]
    }

    if (generate_javascript) {
      args += [
        "--js-out-dir",
        rel_js_out_dir,
        "--protoc-gen-js",
        "./" + rebase_path(_protoc_gen_js_path, root_build_dir),
      ]
    }

    if (generate_with_plugin) {
      args += [
        "--plugin",
        rebase_path(plugin_path, root_build_dir),
        "--plugin-out-dir",
        rel_cc_out_dir,
      ]
      if (defined(invoker.generator_plugin_options)) {
        args += [
          "--plugin-options",
          invoker.generator_plugin_options,
        ]
      }
    }

    if (generate_descriptor != "") {
      depfile =
          "$root_gen_dir/" + proto_out_dir + "/" + generate_descriptor + ".d"
      rel_depfile = rebase_path(depfile, root_build_dir)

      if (exclude_imports) {
        args += [ "--exclude-imports" ]
      }

      args += [
        "--descriptor-set-out",
        rel_descriptor_out,
        "--descriptor-set-dependency-file",
        rel_depfile,
      ]
    }
    inputs = []
    if (defined(invoker.proto_data_sources)) {
      inputs += invoker.proto_data_sources
    }

    if (defined(invoker.import_dirs)) {
      foreach(path, invoker.import_dirs) {
        args += [ "--import-dir=" + rebase_path(path, root_build_dir) ]
      }
    }

    # System protoc is not used so it's necessary to build a chromium one.
    inputs += [ _protoc_path ]
    deps = [ _protoc_label ]

    if (enable_js_protobuf) {
      inputs += [ _protoc_gen_js_path ]
      deps += [ _protoc_gen_js_label ]
    }

    if (generate_with_plugin) {
      inputs += [ plugin_path ]
      if (defined(invoker.generator_plugin_script_deps)) {
        # Additional scripts for plugin.
        inputs += invoker.generator_plugin_script_deps
      }
      if (defined(plugin_host_label)) {
        # Action depends on native generator plugin but for host toolchain only.
        deps += [ plugin_host_label ]
      }
    }

    # The deps may have steps that have to run before running protoc.
    if (defined(invoker.proto_deps)) {
      deps += invoker.proto_deps
    }
    if (defined(invoker.deps)) {
      deps += invoker.deps
    }
  }

  if (!generate_library) {
    # If only descriptor is required, just generate a group wrapper for action output.
    link_target_type = "group"
  } else if ((defined(invoker.force_source_set) && invoker.force_source_set) ||
             (defined(invoker.component_build_force_source_set) &&
              invoker.component_build_force_source_set && is_component_build)) {
    # Option to disable building a library in component build.
    link_target_type = "source_set"
  } else {
    link_target_type = "static_library"
  }

  # Generated files may include other generated headers. These includes always
  # use relative paths starting at |cc_out_dir|.
  # However there is no necessity to add an additional directory, if all protos
  # are located in the same directory which is in the search path by default.
  config_name = "${target_name}_config"
  config(config_name) {
    include_dirs = []
    if (has_nested_dirs && generate_cc) {
      include_dirs += [ cc_out_dir ]
    }
    if (defined(invoker.import_dirs)) {
      foreach(path, invoker.import_dirs) {
        include_dirs += [ "$root_gen_dir/" + rebase_path(path, "//") ]
      }
    }
  }

  # Build generated javascript stubs.
  if (generate_javascript) {
    js_library(javascript_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])

      sources = protogens_js

      deps = [ "$_this_dir:js_proto" ]

      extra_deps = [ ":$action_name" ]
    }
  }

  # Build generated protobuf stubs as libary or source set.
  target(link_target_type, target_name) {
    forward_variables_from(invoker,
                           [
                             "defines",
                             "testonly",
                             "visibility",
                           ])

    if (generate_library) {
      sources = get_path_info(protogens_cc, "abspath")

      if (defined(invoker.remove_configs)) {
        configs -= invoker.remove_configs
      }

      if (defined(invoker.extra_configs)) {
        configs += invoker.extra_configs
      }

      # Remove Sanitizer and coverage instrumentation for a performance boost when
      # fuzzing, since the only fuzzers that use protobuf are libprotobuf-mutator
      # based fuzzers, and they don't actually target protobuf code.
      configs -= not_fuzzed_remove_configs
      configs += [ "//build/config/sanitizers:not_fuzzed" ]
    }

    public_configs = [
      "$_this_dir:using_proto",
      "$_this_dir:allow_deprecated_proto_fields",
    ]
    public_deps = []

    if (generate_cc || generate_with_plugin) {
      # Not necessary if all protos are located in the same directory.
      if (has_nested_dirs || defined(invoker.import_dirs)) {
        # By default, propagate the config for |include_dirs| to dependent
        # targets, so that public imports can be resolved to corresponding
        # header files. In some cases, the embedder target handles include
        # directory propagation itself, e.g. via a common config.
        propagate_imports_configs =
            !defined(invoker.propagate_imports_configs) ||
            invoker.propagate_imports_configs
        if (propagate_imports_configs) {
          public_configs += [ ":$config_name" ]
        } else {
          # Embedder handles include directory propagation to dependents.
          configs += [ ":$config_name" ]
        }
      }

      # If using built-in cc generator, the resulting headers reference headers
      # within protobuf_lite. Hence, dependencies require those headers too.
      # If using generator plugin, extra deps should be resolved by the invoker.
      if (generate_cc) {
        if (defined(invoker.use_protobuf_full) &&
            invoker.use_protobuf_full == true) {
          public_deps += [ "$_this_dir:protobuf_full" ]
        } else {
          public_deps += [ "$_this_dir:protobuf_lite" ]
        }

        if (is_win) {
          cflags = [
            # disable: C4125 decimal digit terminates octal escape sequence
            # Protoc generates such sequences frequently, there's no obvious
            # superior replacement behavior. Since this code is autogenerated,
            # the warning would never catch a legitimate bug.
            "/wd4125",
          ]
        }
      }
    }

    public_deps += [ ":$action_name" ]
    deps = []

    # This will link any libraries in the deps (the use of invoker.deps in the
    # action won't link it).
    if (defined(invoker.deps)) {
      deps += invoker.deps
    }
    if (defined(invoker.link_deps)) {
      deps += invoker.link_deps
    }
  }

  if (generate_py_runtime) {
    group(py_runtime_name) {
      data = protogens_py
      deps = [
        ":$action_name",
        "$_this_dir:py_proto_runtime",
      ]
    }
  }
}

# Convert a protocol buffer between text and binary formats.
# This can be used to run protoc with the --encode or --decode options.
# Parameters:
#
#   sources: list of string
#       The sources to loop over and run protoc on
#
#   inputs: list of string
#       The file dependencies for the action. This should be the list of .proto
#       files involved in the conversion operation.
#
#   output_pattern: string
#       A path pattern with source expansion variables (like source_name_part)
#       for where the result of conversion should be placed.
#
#   deps: (optional) list of label
#       Additional dependencies for the target.
#
#   args: list of string
#       Arguments to pass to the protoc tool. This could include -I for include
#       paths, as well as the name of the proto file.
#
#
# Example to convert a .textproto to a .binarybp:
#   protoc_convert("convert_foo") {
#     sources = [
#       "test/data/example1.textproto",
#       "test/data/example2.textproto",
#     ]
#     inputs = [
#       "//component/core/foo.proto",
#     ]
#     output_pattern = "$target_gen_dir/foo_data/{{source_name_part}}.binarypb"
#     args = [
#       "--encode=foo.FooMessage",
#       "-I",
#       rebase_path("//"),
#       "component/core/foo.proto",
#     ]
#   }
template("protoc_convert") {
  action_foreach(target_name) {
    script = "//tools/protoc_wrapper/protoc_convert.py"

    sources = invoker.sources

    inputs = invoker.inputs

    deps = [ _protoc_label ]
    if (defined(invoker.deps)) {
      deps += invoker.deps
    }

    if (defined(invoker.testonly)) {
      testonly = invoker.testonly
    }

    outputs = [ invoker.output_pattern ]

    args = [
             "--protoc",
             "./" + rebase_path(_protoc_path, root_build_dir),
             "--infile",
             "{{source}}",
             "--outfile",
             rebase_path(invoker.output_pattern),
           ] + invoker.args
  }
}