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

# Copyright 2022 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("configc.gni")
import("fidl_library.gni")

# Defines a configuration value file for a Fuchsia component.
#
# A config value file is produced from a component manifest that contains a schema
# and a JSON5 file with concrete configuration values.
#
# For example, if a component manifest defines the `enable_foo` flag:
#
# ```
# // ./meta/my_component.cml
# {
#   // ...
#   config: {
#     enable_foo: { type: "bool" }
#   }
# }
# ```
#
# The definition file will need to contain an entry for it and any other fields
# in its manifest:
#
# ```
# // ./config/my_component.json5
# {
#   enable_foo: true
# }
# ```
#
# Building the config value file requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
#   component = "my_component"
#   manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
#   cm_label = ":my_component_manifest"
#   deps = [ ... ]
# }
#
# fuchsia_structured_config_values("my_component_config") {
#   cm_label = ":my_component_manifest"
#   values = "config/my_component.json5"
# }
# ```
#
# Finally, the package must include the value file alongside the manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_package("my_package") {
#   deps = [
#     ":my_component",
#     ":my_component_config",
#   ]
# }
# ```
#
# Parameters
#
#   cm_label (required)
#     The label of the fuchsia_component_manifest target for which the file will be generated.
#     Type: GN label, e.g. `:my_component_manifest`
#
#   values_source -or- values (required)
#     values_source: The JSON5 file containing the concrete values for the generated file.
#     values: A GN scope containing literal values for the generated file.
#     TODO(https://fxbug.dev/87988) document this format properly.
#     Type: path or scope
#
#   data_deps (optional)
#   deps (optional)
#   testonly (optional)
#     Standard GN meaning.
template("fuchsia_structured_config_values") {
  assert(
      defined(invoker.cm_label),
      "must provide a component manifest label with a configuration declaration")

  _source_defined = defined(invoker.values_source)
  _values_defined = defined(invoker.values)
  assert(
      (_source_defined || _values_defined) &&
          !(_source_defined && _values_defined),
      "must provide either `values_source` (path to JSON5 file) or `values` (GN scope with literal values)")

  if (_values_defined) {
    _generated_values_label = "${target_name}_generated_values"
    _value_file_deps = [ ":$_generated_values_label" ]
    _value_file = "$target_gen_dir/${target_name}_values_from_literal.json"
    generated_file(_generated_values_label) {
      output_conversion = "json"
      contents = invoker.values
      outputs = [ _value_file ]
    }
  } else {
    _value_file_deps = []
    _value_file = invoker.values_source
  }

  # make sure invoker.cm_label is in the same module, i.e. starts with ":". get_target_outputs depends on this condition
  segments = string_split(invoker.cm_label, ":")
  assert(segments[0] == "", "cm_label cannot be in a different module")

  # now we can call get_target_outputs without creating unactionable error messages
  component_outputs = get_target_outputs(invoker.cm_label)
  compiled_manifest = component_outputs[0]

  # compile the value file
  cvf(target_name) {
    forward_variables_from(invoker,
                           [
                             "data_deps",
                             "deps",
                             "testonly",
                             "visibility",
                           ])
    cm = compiled_manifest
    value_file = _value_file
    if (!defined(deps)) {
      deps = []
    }
    deps += [ "${invoker.cm_label}" ] + _value_file_deps
  }
}

# Defines a C++ configuration client library for a Fuchsia ELF component.
#
# A config client library is produced from a component manifest that contains a schema.
#
# For example, if a component manifest defines the `enable_foo` flag:
#
# ```
# // ./meta/my_component.cml
# {
#   // ...
#   config: {
#     enable_foo: { type: "bool" }
#   }
# }
# ```
#
# Building the config client library requires the compiled manifest:
#
# ```
# # ./BUILD.gn
# fuchsia_component_manifest("my_component_manifest") {
#   component = "my_component"
#   manifest = "meta/my_component.cml"
# }
#
# fuchsia_component("my_component") {
#   cm_label = ":my_component_manifest"
#   deps = [ ... ]
# }
#
# fuchsia_structured_config_cpp_elf_lib("my_component_config_lib") {
#   cm_label = ":my_component_manifest"
# }
# ```
#
# Finally, a C++ binary can import this library for use
#
# ```
# # ./BUILD.gn
# executable("my_binary") {
#   ...
#   deps = [
#     ":my_component_config_lib",
#     ...
#   ]
# }
# ```
#
# And in my_binary source, it can be used like this
#
# ```
# #include "path/to/target/dir/my_component_config_lib.h"
#
# int main(int argc, void** argv) {
#   auto config = my_component_config_lib::Config::TakeFromStartupHandle();
#   DLOG(INFO) << "Is foo enabled = " << config.enable_foo();
# }
# ```
#
# Parameters
#
#   cm_label (required)
#     The label of the fuchsia_component_manifest target for which the file will be generated.
#     Type: GN label, e.g. `:my_component_manifest`
#
#   cpp_namespace (optional)
#     Namespace used by the generated C++ library. If not specified, the target name is used.
#     Type: string
#
#   fidl_library_name (optional)
#     Name for the generated FIDL library. If not specified, the default (cf.sc.internal) is used.
#     Type: string
#
#   testonly (optional)
#     Standard GN meaning.
template("fuchsia_structured_config_cpp_elf_lib") {
  if (!defined(invoker.cpp_namespace)) {
    cpp_namespace = target_name
  } else {
    cpp_namespace = invoker.cpp_namespace
  }

  # The library name is a string that is also used internally by configc when generating
  # FIDL and C++ source files. It is not visible to the end user.
  if (defined(invoker.fidl_library_name)) {
    fidl_library_name = invoker.fidl_library_name
  } else {
    fidl_library_name = "cf.sc.internal"
  }

  cpp_namespace = string_replace(cpp_namespace, ".", "_")
  cpp_namespace = string_replace(cpp_namespace, "-", "_")

  fidl_source_target = "${target_name}_fidl_config_lib_source"
  cpp_elf_source_target = "${target_name}_cpp_elf_config_lib_source"

  assert(defined(invoker.cm_label), "must provide a component manifest label")

  manifest_outputs = get_target_outputs(invoker.cm_label)
  compiled_manifest = manifest_outputs[0]

  # generate the client library FIDL source
  fidl_config_client_lib_source(fidl_source_target) {
    forward_variables_from(invoker, [ "testonly" ])
    name = fidl_library_name
    compiled_manifest = compiled_manifest
    deps = [ invoker.cm_label ]
  }

  # generate the C++ source
  cpp_config_client_lib_source(cpp_elf_source_target) {
    forward_variables_from(invoker, [ "testonly" ])
    namespace = cpp_namespace
    fidl_library_name = fidl_library_name
    compiled_manifest = compiled_manifest
    flavor = "elf-hlcpp"
    deps = [ invoker.cm_label ]
  }

  # generate the FIDL library
  fidl_library_target = "${target_name}_fidl_internal"

  fidl_library(fidl_library_target) {
    library_name = fidl_library_name
    forward_variables_from(invoker, [ "testonly" ])
    sources = get_target_outputs(":${fidl_source_target}")
    non_fidl_deps = [ ":${fidl_source_target}" ]
  }

  # generate the wrapper C++ library
  source_set(target_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    sources = get_target_outputs(":${cpp_elf_source_target}")
    deps = [
      ":${fidl_library_target}",
      "${fuchsia_sdk}/pkg/fidl",
      "${fuchsia_sdk}/pkg/inspect_service_cpp",
    ]

    public_deps = [
      ":${cpp_elf_source_target}($default_toolchain)",
      "${fuchsia_sdk}/pkg/fidl",
      "${fuchsia_sdk}/pkg/fidl_cpp",
      "${fuchsia_sdk}/pkg/inspect_service_cpp",
    ]

    # prevent manifest from getting into package this way
    metadata = {
      distribution_entries_barrier = []
    }
  }
}