chromium/third_party/fuchsia-gn-sdk/src/fidl_library.gni

# Copyright 2019 The Fuchsia 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("gn_configs.gni")
import("gn_sdk_configs.gni")

# Defines a base fidl library with no bindings.
#
# FIDL library dependencies under `public_deps` will manifest as corresponding
# library target dependencies.
#
# Parameters
#
#  library_name (required)
#    Name of the FIDL library.
#
#  sources (required)
#    List of fidl files to include in the library.
#
#  json_representation (required)
#    filename to use for the json representation of the library
#
template("fidl_base_library") {
  forward_variables_from(invoker,
                         [
                           "library_name",
                           "sources",
                           "json_representation",
                         ])
  _response_file = "${target_gen_dir}/${target_name}.rsp"

  action("${target_name}_response_file") {
    script = gn_sdk_root + "/gen_fidl_response_file.py"

    forward_variables_from(invoker,
                           [
                             "deps",
                             "public_deps",
                             "testonly",
                           ])

    _libraries_file = "$target_gen_dir/${invoker.target_name}.fidl_libraries"

    outputs = [
      _response_file,
      _libraries_file,
    ]

    args = [
             "--out-response-file",
             rebase_path(_response_file, root_build_dir),
             "--out-libraries",
             rebase_path(_libraries_file, root_build_dir),
             "--json",
             rebase_path(json_representation, root_build_dir),
             "--name",
             library_name,
             "--sources",
           ] + rebase_path(sources, root_build_dir)

    # Pass the target API level if it has been specified.
    assert(fuchsia_target_api_level >= 1)
    args += [
      "--target-api-level",
      "${fuchsia_target_api_level}",
    ]

    if (defined(invoker.experimental_flags)) {
      foreach(flag, invoker.experimental_flags) {
        args += [
          "--experimental",
          flag,
        ]
      }
    }

    deps = []

    if (defined(invoker.deps) || defined(invoker.public_deps)) {
      merged_deps = []

      if (defined(invoker.deps)) {
        merged_deps += invoker.deps
      }

      if (defined(invoker.public_deps)) {
        merged_deps += invoker.public_deps
      }

      dep_libraries = []

      foreach(dep, merged_deps) {
        gen_dir = get_label_info(dep, "target_gen_dir")
        dep_toolchain = get_label_info(dep, "toolchain")
        name = get_label_info(dep, "name")
        dep_libraries += [ "${gen_dir}/${name}.fidl_libraries" ]
        dep_dir =
            get_label_info(get_label_info(dep, "label_no_toolchain"), "dir")
        deps += [ "${dep_dir}:${name}_response_file($dep_toolchain)" ]
      }

      inputs = dep_libraries

      args += [ "--dep-libraries" ] + rebase_path(dep_libraries, root_build_dir)
    }

    if (defined(invoker.non_fidl_deps)) {
      deps += invoker.non_fidl_deps
    }
  }

  action("${target_name}_ir") {
    forward_variables_from(invoker, [ "testonly" ])

    visibility = [ ":*" ]

    deps = [ ":${invoker.target_name}_response_file" ]

    script = gn_sdk_root + "/gn_run_binary.py"

    inputs = [
      # Depend on the SDK hash, to ensure rebuild if the SDK tools change.
      fuchsia_sdk_manifest_file,
      _response_file,
    ]

    outputs = [ json_representation ]

    rebased_response_file = rebase_path(_response_file, root_build_dir)
    args = [
      rebase_path("${fuchsia_tool_dir}/fidlc", root_build_dir),
      "@${rebased_response_file}",
    ]
  }
}

# Generates C++ code for a given FIDL library
#
# Parameters
#
#  fidl_gen_dir (required)
#    The directory into which bindings should be generated.
#
#  bindings_flavor (required)
#    Identifies which kind of bindings (hlcpp, cpp, ...).
#    This should be the same flavor as the one used when instantiating
#    the fidl_cpp_library template.
#
#  generated_sources (optional)
#    A list of source files that will be generated for this binding, relative
#    to the target-specific generation directory.
#
#  generated_headers (optional)
#    A list of header files that will be generated for this binding, relative
#    to the target-specific generation directory.
#
#  fidlgen_tool (required)
#    The code generation tool to use, as an absolute GN label.
#
#  additional_visibility (optional)
#    A list of labels which are allowed to depend on the generated code.
#
template("fidl_cpp_codegen") {
  root = "${invoker.fidl_gen_dir}/${invoker.bindings_flavor}"
  generation_target_name = "${target_name}_gen"

  generation_visibility = [ ":${target_name}" ]
  if (defined(invoker.additional_visibility)) {
    generation_visibility += invoker.additional_visibility
  }

  action(generation_target_name) {
    visibility = generation_visibility
    forward_variables_from(invoker, [ "testonly" ])

    deps = [ invoker.fidl_ir_target ]

    inputs = [
      # Depend on the SDK hash, to ensure rebuild if the SDK tools change.
      fuchsia_sdk_manifest_file,
      invoker.json_representation,
    ]

    outputs = []
    if (defined(invoker.generated_sources)) {
      foreach(output, invoker.generated_sources) {
        outputs += [ "${root}/${output}" ]
      }
    }
    if (defined(invoker.generated_headers)) {
      foreach(output, invoker.generated_headers) {
        outputs += [ "${root}/${output}" ]
      }
    }

    script = gn_sdk_root + "/gn_run_binary.py"
    args = [
      rebase_path(invoker.fidlgen_tool, root_build_dir),
      "--json",
      rebase_path(invoker.json_representation, root_build_dir),
      "--root",
      rebase_path(root, root_build_dir),
    ]
  }
}

template("_fidl_cpp_library_impl") {
  root = "${invoker.fidl_gen_dir}/${invoker.bindings_flavor}"
  generation_target_name = "${target_name}_gen"
  if (defined(invoker.generation_target_prefix)) {
    generation_target_name = "${invoker.generation_target_prefix}_gen"
  }

  config_target_name = "${target_name}_config"
  config(config_target_name) {
    visibility = [ ":${target_name}" ]
    include_dirs = [ root ]
  }

  source_set(target_name) {
    forward_variables_from(invoker,
                           [
                             "deps",
                             "public_deps",
                             "testonly",
                             "visibility",
                           ])
    if (defined(visibility)) {
      # Ensure that layers have visibility to each other.
      visibility += [ ":*" ]
    }

    sources = []
    public = []
    if (defined(invoker.generated_sources)) {
      foreach(source, invoker.generated_sources) {
        sources += [ "${root}/${source}" ]
      }
    }
    if (defined(invoker.generated_headers)) {
      foreach(header, invoker.generated_headers) {
        sources += [ "${root}/${header}" ]
        public += [ "${root}/${header}" ]
      }
    }

    if (defined(invoker.generated_headers)) {
      # Let dependencies use `#include "$file_stem.h"`.
      public_configs = [ ":$config_target_name" ]
    }

    # Metadata to allow us to query all FIDL IR files.
    metadata = {
      fidl_json = [ rebase_path(invoker.json_representation, root_build_dir) ]
    }

    if (!defined(deps)) {
      deps = []
    }
    deps += [
      ":${generation_target_name}",
      invoker.fidl_ir_target,
    ]

    if (!defined(public_deps)) {
      public_deps = []
    }

    not_needed(invoker, [ "bindings_flavor" ])
    not_needed(invoker, [ "bindings_layer" ])
    if (defined(invoker.public_deps)) {
      if (invoker.public_deps != []) {
        if (defined(invoker.bindings_layer)) {
          bindings_layer = invoker.bindings_layer
        } else {
          bindings_layer = invoker.bindings_flavor
        }
        foreach(dep, invoker.public_deps) {
          label = get_label_info(dep, "label_no_toolchain")
          if (get_label_info(label, "name") != "zx") {
            public_deps += [ "${label}_${bindings_layer}" ]
          } else {
            not_needed([ "bindings_layer" ])
          }
        }
      }
    }

    public_deps += invoker.additional_public_deps
  }
}

# Defines a C++ library target (source_set) generated from a FIDL library.
#
# FIDL library dependencies under `public_deps` will manifest as corresponding
# library target dependencies.
#
# Parameters
#
#  fidl_gen_dir (required)
#    The directory where generated files will be placed.
#
#  json_representation (required)
#    The file containing the json representation.
#
#  fidl_ir_target (required)
#    The target for the base fidl library.
#
#  bindings_flavor (required)
#    Identifies which kind of bindings (hlcpp, cpp, ...).
#    This should be the same suffix as the one used when instantiating
#    the fidl_cpp_codegen template.
#
#  bindings_layer (optional)
#    A particular binding may be decomposed into multiple layers. In that case,
#    this identifies the particular layer that makes up this library (e.g.
#    domain objects layer, messaging layer, ...). When adding dependencies,
#    a particular layer for a FIDL library depends on the corresponding
#    layers of the dependencies of that FIDL library.
#
#    Some examples of valid layers are:
#      * cpp_wire: Wire messaging over channel transport
#      * cpp_wire_types: Wire domain objects
#
#    If missing, defaults to bindings_flavor.
#
#  generated_sources (optional)
#    A list of source files that will be generated for this binding, relative
#    to the target-specific generation directory.
#
#  generated_headers (optional)
#    A list of header files that will be generated for this binding, relative
#    to the target-specific generation directory.
#
#  generation_target_prefix (optional)
#    Overrides the prefix for the generation target so that it becomes
#    `${generation_target_prefix}_gen`.
#
#  additional_public_deps (optional)
#    A list of targets to be added to public_deps.
#
template("fidl_cpp_library") {
  if (invoker.fidl_target_name == "zx") {
    # The zx FIDL library isn't generated with C++ fidlgens.
    not_needed(invoker, "*")
    group(target_name) {
    }
  } else {
    _fidl_cpp_library_impl(target_name) {
      forward_variables_from(invoker,
                             [
                               "deps",
                               "public_deps",
                               "testonly",
                               "visibility",
                               "fidl_gen_dir",
                               "fidl_ir_target",
                               "json_representation",
                               "bindings_flavor",
                               "bindings_layer",
                               "generated_sources",
                               "generated_headers",
                               "generation_target_prefix",
                               "additional_public_deps",
                             ])
    }
  }
}

# Define a Fuchsia FIDL library target.
#
# ${namespace}.${library_name} must match the the library name specified in the FIDL
# files.
#
# Parameters
#
#   sources
#     Required: List of .fidl files.
#
#   library_name
#     Optional: Name of the library. target_name is used if name
#               is not specified explicitly.
#
#   namespace
#     Optional: Namespace for the library.
#
#   deps
#     Optional: List of other fidl_library() targets that this
#               FIDL library depends on.
#
#   non_fidl_deps
#     Optional: List of other deps that aren't fidl_library targets.
#
#   experimental_flags
#     Optional: List of experimental flags to pass to fidlc.
#
template("fidl_library") {
  forward_variables_from(invoker, [ "namespace" ])

  library_basename = target_name
  if (defined(invoker.library_name)) {
    library_basename = invoker.library_name
  }

  if (defined(namespace)) {
    library_name = "${namespace}.${library_basename}"
    _namespace_path = string_replace(namespace, ".", "/")
    _library_name_slashes = "${_namespace_path}/${library_basename}"
  } else {
    library_name = library_basename
    _library_name_slashes = string_replace(library_basename, ".", "/")
  }

  json_representation = "${target_gen_dir}/${library_name}.fidl.json"
  fidl_gen_dir = "${target_gen_dir}/fidl"
  fidl_target_name = target_name
  fidl_ir_target = ":${target_name}_ir"

  fidl_base_library(target_name) {
    forward_variables_from(invoker,
                           [
                             "sources",
                             "deps",
                             "public_deps",
                             "testonly",
                           ])
  }

  # HLCPP bindings
  hlcpp_options = {
    bindings_flavor = "hlcpp"
    generated_headers = [
      "${_library_name_slashes}/cpp/fidl.h",
      "${_library_name_slashes}/cpp/fidl_test_base.h",
    ]
    generated_sources = [
      "${_library_name_slashes}/cpp/fidl.cc",
      "${_library_name_slashes}/cpp/tables.c",
    ]
  }

  fidl_cpp_codegen("${target_name}_hlcpp") {
    forward_variables_from(invoker, [ "testonly" ])
    forward_variables_from(hlcpp_options, "*")
    fidlgen_tool = "${fuchsia_tool_dir}/fidlgen_hlcpp"
  }

  fidl_cpp_library("${target_name}_hlcpp") {
    forward_variables_from(invoker,
                           [
                             "deps",
                             "public_deps",
                             "testonly",
                             "visibility",
                           ])
    additional_public_deps = [
      "${fuchsia_sdk}/pkg/fidl",
      "${fuchsia_sdk}/pkg/fidl_cpp",
    ]
    forward_variables_from(hlcpp_options, "*")
  }

  # Alias the target name to the _hlcpp target. This is for backward
  # compatibility with the the old mechanism where the base fidl library
  # and the hlcpp bindings were in a single target. Once all uses of
  # hlcpp are removed, this alias should be removed and the target that
  # is currently named ${target_name}_ir will become ${target_name}
  group(target_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    public_deps = [ ":${invoker.target_name}_hlcpp" ]
  }

  # These bindings are defined as a set of layers that build upon each other.
  # All of the layers are generated in a single fidlgen invocation but are
  # exposed as separate source_sets to user code so that it's easy to include
  # just the parts of the bindings that you need.
  llcpp_layers = [
    {
      layer = "cpp_common"
      headers = [
        "fidl/${library_name}/cpp/common_types.h",
        "fidl/${library_name}/cpp/markers.h",
      ]
      sources = [ "fidl/${library_name}/cpp/common_types.cc" ]

      public_deps = [
        "${fuchsia_sdk}/pkg/fidl",
        "${fuchsia_sdk}/pkg/fidl_cpp_wire",
        "${fuchsia_sdk}/pkg/fit",
      ]
    },

    {
      layer = "cpp_wire_types"
      headers = [ "fidl/${library_name}/cpp/wire_types.h" ]
      sources = [ "fidl/${library_name}/cpp/wire_types.cc" ]

      layer_deps = [ "cpp_common" ]
    },

    {
      layer = "cpp_wire"  # AKA: wire_channel_messaging
      headers = [
        "fidl/${library_name}/cpp/wire.h",
        "fidl/${library_name}/cpp/wire_messaging.h",
      ]
      sources = [ "fidl/${library_name}/cpp/wire_messaging.cc" ]
      layer_deps = [ "cpp_wire_types" ]
    },

    {
      layer = "cpp_wire_testing"
      headers = [ "fidl/${library_name}/cpp/wire_test_base.h" ]
      layer_deps = [ "cpp_wire" ]
      testonly = true
    },

    {
      layer = "cpp_natural_types"
      headers = [ "fidl/${library_name}/cpp/natural_types.h" ]
      sources = [ "fidl/${library_name}/cpp/natural_types.cc" ]
      public_deps = [ "${fuchsia_sdk}/pkg/fidl_cpp_base_v2" ]
      layer_deps = [ "cpp_common" ]
    },

    {
      layer = "cpp_natural_ostream"
      headers = [ "fidl/${library_name}/cpp/natural_ostream.h" ]
      sources = [ "fidl/${library_name}/cpp/natural_ostream.cc" ]
      public_deps = [ "${fuchsia_sdk}/pkg/fidl_cpp_natural_ostream" ]
      layer_deps = [ "cpp_natural_types" ]
    },

    {
      layer = "cpp_type_conversions"
      headers = [ "fidl/${library_name}/cpp/type_conversions.h" ]
      sources = [ "fidl/${library_name}/cpp/type_conversions.cc" ]

      public_deps = [ "${fuchsia_sdk}/pkg/fidl_cpp_v2" ]

      layer_deps = [
        "cpp_natural_types",

        # TODO(fxbug.dev/101525): Replace with "cpp_wire_types".
        "cpp_wire",
      ]
    },

    {
      layer = "cpp"  # AKA: unified_channel_messaging
      headers = [
        "fidl/${library_name}/cpp/fidl.h",
        "fidl/${library_name}/cpp/natural_messaging.h",
      ]
      sources = [ "fidl/${library_name}/cpp/natural_messaging.cc" ]
      layer_deps = [
        "cpp_natural_types",
        "cpp_type_conversions",
        "cpp_wire",
      ]
      public_deps = [ "${fuchsia_sdk}/pkg/fidl_cpp_v2" ]
    },

    {
      layer = "cpp_testing"
      headers = [ "fidl/${library_name}/cpp/test_base.h" ]
      layer_deps = [ "cpp" ]
      testonly = true
    },

    {
      layer = "cpp_hlcpp_conversion"
      headers = [ "fidl/${library_name}/cpp/hlcpp_conversion.h" ]
      public_deps = [
        ":${target_name}_hlcpp",
        "${fuchsia_sdk}/pkg/fidl_cpp_hlcpp_conversion",
      ]
      layer_deps = [ "cpp_natural_types" ]
    },
  ]

  # Define new C++ binding targets.
  layer_target_prefix = target_name + "_"
  fidl_cpp_codegen("${target_name}_cpp") {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "fidl_gen_dir",
                             "fidl_ir_json",
                             "fidl_ir_target",
                           ])
    bindings_flavor = "cpp"
    fidlgen_tool = "${fuchsia_tool_dir}/fidlgen_cpp"
    generated_headers = []
    generated_sources = []
    additional_visibility = []
    foreach(layer, llcpp_layers) {
      additional_visibility += [ ":${layer_target_prefix}${layer.layer}" ]
      if (defined(layer.headers)) {
        generated_headers += layer.headers
      }
      if (defined(layer.sources)) {
        generated_sources += layer.sources
      }
    }
  }

  foreach(layer, llcpp_layers) {
    layer_target_name = layer_target_prefix + layer.layer
    fidl_cpp_library(layer_target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                               "public_configs",
                               "public_deps",
                               "fidl_gen_dir",
                             ])
      generation_target_prefix = "${invoker.target_name}_cpp"
      bindings_flavor = "cpp"
      bindings_layer = layer.layer
      if (defined(layer.headers)) {
        generated_headers = layer.headers
      }
      if (defined(layer.sources)) {
        generated_sources = layer.sources
      }
      if (defined(layer.testonly)) {
        testonly = layer.testonly
      }
      additional_public_deps = []
      if (defined(layer.public_deps)) {
        additional_public_deps += layer.public_deps
      }
      if (defined(layer.layer_deps)) {
        foreach(layer_dep, layer.layer_deps) {
          additional_public_deps += [ ":${layer_target_prefix}${layer_dep}" ]
        }
      }
    }
  }
}