# Copyright 2013 The Flutter 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("cmc.gni")

declare_args() {
  # Enable code coverage for Fuchsia components. Only applies to v1 components.
  fuchsia_code_coverage = false
}

# Defines a Fuchsia component.
# See: https://fuchsia.dev/fuchsia-src/glossary#component
#
# A component is defined by a component manifest.
# A component is a unit of executable software on Fuchsia. When the componet is executed, there are usually many files
# needed at runtime, such as the executable itself, shared libraries, and possibly other data resources. These files are
# specified by using the GN concept of "data_deps".
#
# Components are distributed using a fuchsia package which can contain multiple components. The component manifest and
# the runtime dependencies of the component are packaged using the fuchsia_package rule.
#
# By default, the runtime depenencies of the component are included in the package using relative paths calcualted from
# the "closest" root of: $root_gen_dir, $root_dir, $out_dir. If a specific path is desired in the package, the "resources" parameter can
# be used to explicitly specify the path of a specific file.
#
# A component is launched by a URL which identifies the package and the component manifest.
# See: https://fuchsia.dev/fuchsia-src/glossary#component_url
#
# Example
#
# ```
#   fuchsia_component("my_component") {
#      manifest = "meta/component-manifest.cmx"
#      resources = [
#        {
#          path = "testdata/use_case1.json"
#          dest = "data/testdata/use_case1.json"
#        },
#      ]
#      data_deps = [ ":hello_world_executable"]
#   }
# ```
#
# Parameters
#
#   manifest
#     The source manifest file for this component. This can be either v1 (.cmx)
#     or v2 (.cml) manifest.
#
#     Type: string (filepath)
#
#   data_deps
#     The labels of targets that generate the files needed at runtime by the component.
#     At minimum, this should include the label of the binary to include in the component.
#
#     Type: list of labels
#
#   manifest_output_name [optional]
#     The name of the manifest file contained in the distribution that this
#     component is included in. The output name should have no extension or
#     prefixes. The resulting filename will have the extension correct for the
#     manifest version. For example, if `manifest` is "foo.cmx"
#     and `manifest_output_name` is "bar", the filename will be "bar.cmx". If
#     `manifest` is "foo.cml" (a v2 manifest), the filename will be "bar.cm".
#
#     Type: string
#     Default: The base file name of `manifest` without an extension.
#
#   resources [optional]
#     Lists additional files to include for runtime access by the component.
#     Defines the resources in a package containing this component. A resource
#     is a data file that may be produced by the build system, checked in to a
#     source repository, or produced by another system that runs before the
#     build. Resources are placed in the `data/` directory of the assembled
#     package.
#
#     Type: list of scopes.  Entries in a scope in the resources list:
#       path (required)
#             Location of resource in source or build directory. If the
#             resource is checked in, this will typically be specified
#             as a path relative to the BUILD.gn file containing the
#             `package()` target. If the resource is generated, this will
#             typically be specified relative to `$target_gen_dir`.
#
#        dest (required) string (path) Location the resource will be placed within `data/`.
#
#   Standard parameters:
#     deps
#     testonly
#     visibility
#
#   Metadata
#     contents - list of scopes describing files for this component. This metadata is consumed by
#       The fuchsia_package rule, which describes the entries used.
#       Entries in scope:
#         type: the usage type of the element, manifest or resource.
#                Each type can have specific properties included in the scope.
#         source [type == manifest || resource] The source file to include in
#           the package for this component. In the case of v2 components,
#           this is the compiled manifest.
#         output_name [type == manifest] The basename of the manifest as it should appear in the package.
#         manifest_version [type == manifest]
#       dest: [type == resource] The package relative path of the resource.

template("fuchsia_component") {
  forward_variables_from(invoker,
                         [
                           "manifest",
                           "manifest_output_name",
                         ])

  assert(defined(manifest), "manifest file required for this component")

  if (!defined(manifest_output_name)) {
    manifest_output_name = get_path_info(manifest, "name")
  }

  # Determine manifest_version from the `manifest` file extension.
  _manifest_extension = get_path_info(manifest, "extension")
  assert(_manifest_extension == "cmx" || _manifest_extension == "cml",
         "Manifest file extension must be .cmx or .cml")
  if (_manifest_extension == "cmx") {
    _manifest_version = "v1"
  } else if (_manifest_extension == "cml") {
    _manifest_version = "v2"
  }

  if (fuchsia_code_coverage) {
    if (_manifest_version == "v1") {
      merged_manifest = "${target_name}-coverage.cmx"
      cmc_merge(merged_manifest) {
        forward_variables_from(invoker,
                               [
                                 "deps",
                                 "testonly",
                               ])
        sources = [
          "${fuchsia_sdk}/build/enable_coverage_data.cmx",
          manifest,
        ]
        output_name = merged_manifest
      }
      manifest = "${target_out_dir}/${merged_manifest}"
    }
  }

  # The component manifest validated using cmx_validation for v1,
  # or cmc_compile for v2.
  _cm_validation_target =
      "${target_name}_validate_" + get_path_info(manifest, "file")

  if (_manifest_version == "v1") {
    cmc_validate(_cm_validation_target) {
      forward_variables_from(invoker, [ "testonly" ])
      manifest = manifest
      deps = []
      if (defined(invoker.deps)) {
        deps += invoker.deps
      }
      if (fuchsia_code_coverage) {
        deps += [ ":${merged_manifest}" ]
      }
    }

    _manifest_source = manifest
  } else if (_manifest_version == "v2") {
    cmc_compile(_cm_validation_target) {
      forward_variables_from(invoker,
                             [
                               "deps",
                               "testonly",
                             ])
      manifest = rebase_path(manifest)
    }

    _compiled_manifest_outputs = get_target_outputs(":$_cm_validation_target")
    _manifest_source = _compiled_manifest_outputs[0]
  }

  group(target_name) {
    forward_variables_from(invoker,
                           [
                             "data",
                             "deps",
                             "data_deps",
                             "resources",
                             "resources_in_json_files",
                             "testonly",
                             "visibility",
                           ])

    # Data is used for adding files as runtime dependencies. Files used as
    # sources in the resources parameter are added as data to make sure
    # non-generated files listed are captured as dependencies.
    if (!defined(data)) {
      data = []
    }

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

    # TODO(richkadel): Changed from the Fuchsia GN SDK to add
    # `resources_in_json_files, so additional resources could be computed at
    # compile time (in this case, compiled Dart libraries).
    if (!defined(resources_in_json_files)) {
      resources_in_json_files = []
    }

    if (!defined(deps)) {
      deps = []
    }
    deps += [ ":$_cm_validation_target" ]

    # Create the component metadata entries. These capture the manifest information and
    # the resources parameter contents.  The metadata is intended for the fuchsia_package rule.
    component_contents = [
      {
        type = "manifest"
        source = rebase_path(get_path_info(_manifest_source, "abspath"))
        output_name = manifest_output_name
        manifest_version = _manifest_version
      },
    ]
    foreach(resource, resources) {
      data += [ resource.path ]
      component_contents += [
        {
          type = "resource"
          source = rebase_path(resource.path)
          dest = resource.dest
        },
      ]
    }
    foreach(file, resources_in_json_files) {
      data += [ file ]
      component_contents += [
        {
          type = "json_of_resources"
          source = file
        },
      ]
    }

    # The contents of the component are enumerated in the
    # metadata.
    metadata = {
      contents = [ component_contents ]
    }
  }
}
