# 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("//flutter/tools/executable_action.gni")
import("//flutter/tools/fuchsia/dart/config.gni")
import("//flutter/tools/fuchsia/dart/dart.gni")
import("//flutter/tools/fuchsia/dart/dart_package_config.gni")
import("//flutter/tools/fuchsia/dart/kernel/dart_kernel.gni")
import("//flutter/tools/fuchsia/gn-sdk/cmc.gni")
import("//flutter/tools/fuchsia/gn-sdk/component.gni")

# Defines a component which will run in a flutter_runner or dart_runner
#
# This template is not intended to be used directly. Users should use the
# flutter_component and dart_component actions instead.
#
# Parameters
#
#   manifest (required)
#     The component manifest
#     Type: path
#
#   main_package (required)
#     The name of the package containing the main_dart
#     Type: string
#
#   component_name (optional)
#     The name of the component.
#     Type: string
#     Default: target_name
#
#   build_cfg (required)
#    A description of how to build this component. This object needs
#    to contain the following variables:
#      runtime_meta: a path to the partial cmx file containing the runner
#      platform_name: either 'dart_runner' or 'flutter_runner'
#      is_aot: a boolean indicating if this is an AOT build
#      is_product: a boolean indicating if this is a product build
#      enable_asserts: whether we should enable asserts when compiling
#
#   main_dart (required)
#     File containing the main function of the component.
#     Type: string
#
#   generate_asset_manifest (optional)
#     If true, will generate an asset manifest for this component
#     Type: boolean
#     Default: false
#
#  deps
#  testonly
#  visibility
template("flutter_dart_component") {
  assert(defined(invoker.manifest), "must specify a manifest file")
  assert(defined(invoker.build_cfg), "must specify build_cfg")
  assert(defined(invoker.main_dart), "Must specify main_dart")
  assert(defined(invoker.main_package), "Must specify main_package")

  build_cfg = invoker.build_cfg

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

  if (defined(invoker.component_name)) {
    _component_name = invoker.component_name
  } else {
    _component_name = target_name
  }

  _resources = []
  if (defined(invoker.resources)) {
    _resources = invoker.resources
  }

  # Flutter and Dart components need to run inside the runner which matches how
  # they were compiled, for example, a JIT component must run in the JIT runner.
  # We need to be able to dynamically convert the manifest files to include the
  # runner requirements so that we can switch based on build modes.
  _manifest_extension = get_path_info(invoker.manifest, "extension")
  if (_manifest_extension == "cmx") {
    # This is a v1 component, merge with the runtime_meta
    _merged_target_name = "${target_name}_merged.cmx"

    cmc_merge(_merged_target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])
      output_name = _merged_target_name
      sources = [
        invoker.manifest,
        rebase_path(build_cfg.runtime_meta, "."),
      ]
    }

    _merged_outputs = []
    _merged_outputs += get_target_outputs(":$_merged_target_name")
    _manifest = _merged_outputs[0]
    _manifest_output_name = "meta/" + _component_name + ".cmx"

    _component_deps += [ ":$_merged_target_name" ]
  } else {
    # This is a v2 component.
    # The v2 runners have not been implemented yet so we just leave the manifest
    # as is to avoid breaking the build.
    _manifest = invoker.manifest
    _manifest_output_name = "meta/" + _component_name + ".cm"
  }

  _dart_package_config_target_name = "${target_name}_dart_package"
  dart_package_config(_dart_package_config_target_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])

    deps = _component_deps
  }

  _package_config_output = []
  _package_config_output =
      get_target_outputs(":$_dart_package_config_target_name")
  _packages_path = _package_config_output[0]

  _kernel_target_name = _component_name + "_kernel"
  _kernel_target_dep_name = _kernel_target_name + "_gen_file"
  _kernel_path = "$target_gen_dir/__untraced_dart_kernel__/${target_name}.dil"

  dart_kernel(_kernel_target_name) {
    kernel_path = _kernel_path

    # establishes a dependency chain for the snapshot since
    # the kernel is wrapped in a group
    kernel_target_name = _kernel_target_dep_name

    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                             "main_dart",
                             "main_package",
                           ])

    deps = [ ":$_dart_package_config_target_name" ]

    packages_path = _packages_path
    args = [
      "--component-name",
      _component_name,
    ]

    # always generate a manifest for fuchsia builds. If this is an aot build
    # the kernel will ignore this variable.
    generate_manifest = true

    platform_name = build_cfg.platform_name
    product = build_cfg.is_product
    is_aot = build_cfg.is_aot
  }

  _component_deps += [ ":$_kernel_target_name" ]

  if (build_cfg.is_aot) {
    _snapshot_path = "$target_gen_dir/${_component_name}_snapshot.so"
    _snapshot_target_name = target_name + "_snapshot"

    _stats_json_path =
        "$target_gen_dir/${_component_name}/stats/symbol_sizes.json"

    if (build_cfg.is_product) {
      _gen_snapshot_script_target = gen_snapshot_product
    } else {
      _gen_snapshot_script_target = gen_snapshot
    }

    executable_action(_snapshot_target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])

      deps = [ ":$_kernel_target_dep_name" ]
      inputs = [ _kernel_path ]
      outputs = [
        _snapshot_path,
        _stats_json_path,
      ]

      if (defined(invoker.toolchain)) {
        toolchain = invoker.toolchain
      } else {
        toolchain = host_toolchain
      }

      # Construct the host toolchain version of the tool.
      # host_tool = invoker.tool + "($toolchain)"
      host_tool = _gen_snapshot_script_target + "($toolchain)"

      # Get the path to the executable. Currently, this assumes that the tool
      # does not specify output_name so that the target name is the name to use.
      # If that's not the case, we'll need another argument to the script to
      # specify this, since we can't know what the output name is (it might be in
      # another file not processed yet).
      host_executable =
          get_label_info(host_tool, "root_out_dir") + "/" +
          get_label_info(host_tool, "name") + host_executable_suffix

      # Add the executable itself as an input.
      inputs += [ host_executable ]

      deps += [ host_tool ]

      tool = host_executable

      args = [
        "--deterministic",
        "--snapshot_kind=app-aot-elf",
        "--elf=" + rebase_path(_snapshot_path, root_build_dir),
        "--print-instructions-sizes-to=" +
            rebase_path(_stats_json_path, root_build_dir),
      ]

      # No asserts in debug or release product.
      # No asserts in non-product release
      # Yes asserts in non-product debug.
      if (build_cfg.enable_asserts) {
        args += [ "--enable_asserts" ]
      }
      args += [ rebase_path(_kernel_path, root_build_dir) ]
    }

    _component_deps += [ ":$_snapshot_target_name" ]
  }

  fuchsia_component(_component_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    deps = _component_deps
    manifest = _manifest
    manifest_output_name = _manifest_output_name

    if (build_cfg.is_aot) {
      _resources += [
        {
          path = _snapshot_path
          dest = "data/${_component_name}/app_aot_snapshot.so"
        },
      ]
    } else {
      _convert_kernel_target_name =
          "${_kernel_target_name}_convert_kernel_manifest"
      _convert_kernel_manifest_file =
          # TODO(richkadel): This is prefixed by "dartlang/", which is not found.
          # Is the current toolchain set incorrectly somewhere?
          string_replace(
              "${target_gen_dir}/${_convert_kernel_target_name}_kernel_manifest.json",
              "dartlang/",
              "")

      # TODO(richkadel): Adds the json resource names in the manifest to the
      # collection of `json_of_resources` files, read by
      # `prepare_package_inputs.py`. These resources are computed (only known)
      # at some point during the build/compile phase.
      resources_in_json_files = [ rebase_path(_convert_kernel_manifest_file) ]
    }

    resources = _resources
  }

  group(target_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    deps = [ ":" + _component_name ]
  }
}
