# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import("../perfetto.gni")

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

template("proto_library") {
  assert(defined(invoker.sources))

  # This is used in chromium build.
  not_needed(invoker,
             [
               "generator_visibility",
               "proto_deps",
             ])

  proto_sources = invoker.sources

  # All the proto imports should be relative to the project root.
  proto_in_dir = "//"
  if (defined(invoker.proto_in_dir)) {
    proto_in_dir = invoker.proto_in_dir
  }
  assert(defined(invoker.proto_out_dir),
         "proto_out_dir must be explicitly defined")
  proto_out_dir = invoker.proto_out_dir

  # We don't support generate_python in the standalone build, but still must
  # check that the caller sets this to false. This is because when building in
  # the chromium tree, chromium's proto_library.gni in chrome (!= this) defaults
  # generate_python = true.
  assert(defined(invoker.generate_python) && !invoker.generate_python)

  import_dirs = []
  if (defined(invoker.import_dirs)) {
    import_dirs = invoker.import_dirs
  }

  # If false will not generate the default .pb.{cc,h} files. Used for custom
  # codegen plugins.
  generate_cc = true
  if (defined(invoker.generate_cc)) {
    generate_cc = invoker.generate_cc
  }

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

    # Include imports to descriptor by default, but use exclude_imports to omit
    # them if needed.
    if (defined(invoker.exclude_imports)) {
      exclude_imports = invoker.exclude_imports
    } else {
      exclude_imports = false
    }
  }

  if (defined(invoker.generator_plugin_label)) {
    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
    }
  }

  out_dir = "$root_gen_dir/" + proto_out_dir
  rel_out_dir = rebase_path(out_dir, root_build_dir)

  # Prevent unused errors when generating descriptor only.
  if (generate_descriptor != "") {
    not_needed([ "rel_out_dir" ])
  }

  protos = rebase_path(proto_sources, proto_in_dir)
  protogens = []

  if (generate_descriptor != "") {
    protogens += [ "$out_dir/${generate_descriptor}" ]
  }

  foreach(proto, protos) {
    proto_dir = get_path_info(proto, "dir")
    proto_name = get_path_info(proto, "name")
    proto_path = proto_dir + "/" + proto_name

    # Prevent unused errors when generating descriptor only.
    if (generate_descriptor != "") {
      not_needed([ "proto_path" ])
    }

    if (generate_cc) {
      protogens += [
        "$out_dir/$proto_path.pb.h",
        "$out_dir/$proto_path.pb.cc",
      ]
    }
    if (generate_with_plugin) {
      foreach(suffix, generator_plugin_suffixes) {
        protogens += [ "$out_dir/${proto_path}${suffix}" ]
      }
    }
  }

  config_name = "${target_name}_config"
  if (generate_descriptor == "") {
    action_name = "${target_name}_gen"
    source_set_name = target_name
  } else {
    action_name = target_name
  }

  config(config_name) {
    include_dirs = [ out_dir ]
  }

  # The XXX_gen action that generates the .pb.{cc,h} files.
  action(action_name) {
    forward_variables_from(invoker, [ "testonly" ])
    if (generate_descriptor == "") {
      visibility = [ ":$source_set_name" ]
    }
    sources = proto_sources
    outputs = get_path_info(protogens, "abspath")

    if (perfetto_use_system_protobuf) {
      protoc_rebased_path = "protoc" + _host_executable_suffix  # from PATH
    } else {
      protoc_label = "//gn:protoc($host_toolchain)"
      protoc_path = get_label_info(protoc_label, "root_out_dir") + "/protoc" +
                    _host_executable_suffix
      protoc_rebased_path = "./" + rebase_path(protoc_path, root_build_dir)
    }
    script = "//gn/run_binary.py"
    args = [
      # 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_rebased_path,
      "--proto_path",
      rebase_path(proto_in_dir, root_build_dir),
    ]

    foreach(path, import_dirs) {
      args += [
        "--proto_path",
        rebase_path(path, root_build_dir),
      ]
    }

    metadata = {
      proto_import_dirs = import_dirs
    }

    if (generate_cc) {
      cc_generator_options_ = ""
      if (defined(invoker.cc_generator_options)) {
        cc_generator_options_ = invoker.cc_generator_options
      }
      args += [
        "--cpp_out",
        cc_generator_options_ + rel_out_dir,
      ]
    }
    if (generate_descriptor != "") {
      depfile = "$root_gen_dir/$generate_descriptor.d"

      if (!exclude_imports) {
        args += [ "--include_imports" ]
      }
      args += [
        "--descriptor_set_out",
        rebase_path("$root_gen_dir/$generate_descriptor", root_build_dir),
        "--dependency_out",
        rebase_path(depfile, root_build_dir),
      ]
    }

    if (generate_with_plugin) {
      plugin_path_rebased = rebase_path(plugin_path, root_build_dir)
      plugin_out_args = ""
      if (defined(invoker.generator_plugin_options)) {
        plugin_out_args += invoker.generator_plugin_options
      }
      plugin_out_args += ":$rel_out_dir"

      args += [
        "--plugin=protoc-gen-plugin=$plugin_path_rebased",
        "--plugin_out=$plugin_out_args",
      ]
    }

    args += rebase_path(proto_sources, root_build_dir)

    if (!perfetto_use_system_protobuf) {
      inputs = [ protoc_path ]
      deps = [ protoc_label ]
    } else {
      inputs = []
      deps = []
    }

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

    if (generate_descriptor != "") {
      deps += invoker.deps
    }
    if (defined(invoker.link_deps)) {
      deps += invoker.link_deps
    }
  }  # action(action_name)

  # The source_set that builds the generated .pb.cc files.
  if (generate_descriptor == "") {
    source_set(source_set_name) {
      forward_variables_from(invoker,
                             [
                               "defines",
                               "include_dirs",
                               "public_configs",
                               "testonly",
                               "visibility",
                             ])

      sources = get_target_outputs(":$action_name")

      configs -= [ "//gn/standalone:extra_warnings" ]
      if (defined(invoker.extra_configs)) {
        configs += invoker.extra_configs
      }

      if (!defined(invoker.public_configs)) {
        public_configs = []
      }

      public_configs += [ ":$config_name" ]

      # Only include the protobuf_gen_config when generating .pb.cc files.
      # Note that |generate_cc| is false for .{pbzero,ipc,gen etc.}.cc
      if (generate_cc) {
        public_configs += [ "//gn:protobuf_gen_config" ]
      }

      # 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 {
        configs += [ ":$config_name" ]
      }

      if (generate_cc) {
        public_deps = [ "//gn:protobuf_lite" ]
      }

      deps = [ ":$action_name" ]
      if (defined(invoker.deps)) {
        deps += invoker.deps
      }
      if (defined(invoker.link_deps)) {
        deps += invoker.link_deps
      }
    }  # source_set(source_set_name)
  }
}  # template
