# 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.

# This file has rules for making Dart packages and snapshots.

import("//build/compiled_action.gni")
import("//build/module_args/dart.gni")
import("//flutter/common/config.gni")
import("//third_party/dart/build/dart/dart_action.gni")
import("//third_party/dart/sdk_args.gni")

frontend_server_files =
    exec_script("//third_party/dart/tools/list_dart_files.py",
                [
                  "absolute",
                  rebase_path("//flutter/flutter_frontend_server"),
                ],
                "list lines")

frontend_server_files +=
    exec_script("//third_party/dart/tools/list_dart_files.py",
                [
                  "absolute",
                  rebase_path("//third_party/dart/pkg"),
                ],
                "list lines")

# Creates a dart kernel (dill) file suitable for use with gen_snapshot, as well
# as the app-jit, aot-elf, or aot-assembly snapshot for targeting Flutter on
# Android or iOS.
#
# Invoker must supply dart_main and package_config. Invoker may optionally
# supply aot as a boolean and product as a boolean.
#
# On Android, the invoker may provide output_aot_lib as a string to override
# the default filename for the aot-elf snapshot.
template("flutter_snapshot") {
  assert(!is_fuchsia)
  assert(defined(invoker.main_dart), "main_dart is a required parameter.")
  assert(defined(invoker.package_config),
         "package_config is a required parameter.")

  kernel_target = "_${target_name}_kernel"
  snapshot_target = "_${target_name}_snapshot"
  is_aot =
      flutter_runtime_mode == "profile" || flutter_runtime_mode == "release"

  kernel_output = "$target_gen_dir/kernel_blob.bin"

  prebuilt_dart_action(kernel_target) {
    script = "//flutter/flutter_frontend_server/bin/starter.dart"

    main_dart = rebase_path(invoker.main_dart)
    package_config = rebase_path(invoker.package_config)
    flutter_patched_sdk = rebase_path("$root_out_dir/flutter_patched_sdk")

    deps = [ "//flutter/lib/snapshot:strong_platform" ]

    inputs = [
               main_dart,
               package_config,
             ] + frontend_server_files

    outputs = [ kernel_output ]

    depfile = "$kernel_output.d"
    abs_depfile = rebase_path(depfile)
    vm_args = [ "--disable-dart-dev" ]

    args = [
      "--depfile=$abs_depfile",
      "--packages=" + rebase_path(package_config),
      "--target=flutter",
      "--sdk-root=" + flutter_patched_sdk,
      "--output-dill=" + rebase_path(kernel_output, root_build_dir),
    ]

    if (is_aot) {
      args += [
        "--aot",
        "--tfa",
      ]
    } else {
      # --no-link-platform is only valid when --aot isn't specified
      args += [ "--no-link-platform" ]
    }

    if (defined(invoker.product) && invoker.product) {
      # Setting this flag in a non-product release build for AOT (a "profile"
      # build) causes the vm service isolate code to be tree-shaken from an app.
      # See the pragma on the entrypoint here:
      #
      # https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/vm/bin/vmservice_io.dart#L240
      #
      # Also, this define excludes debugging and profiling code from Flutter.
      args += [ "-Ddart.vm.product=true" ]
    } else {
      if (flutter_runtime_mode == "profile") {
        # The following define excludes debugging code from Flutter.
        args += [ "-Ddart.vm.profile=true" ]
      }
    }

    args += [ rebase_path(main_dart) ]
  }

  compiled_action(snapshot_target) {
    if (target_cpu == "x86" && host_os == "linux") {
      # By default Dart will create a 32-bit gen_snapshot host binary if the target
      # platform is 32-bit.  Override this to create a 64-bit gen_snapshot for x86
      # targets because some host platforms may not support 32-bit binaries.
      tool = "//third_party/dart/runtime/bin:gen_snapshot_host_targeting_host"
      toolchain = "//build/toolchain/$host_os:clang_x64"
    } else {
      tool = "//third_party/dart/runtime/bin:gen_snapshot"
    }

    inputs = [ kernel_output ]
    deps = [ ":$kernel_target" ]
    outputs = []

    args = []

    if (is_debug && flutter_runtime_mode != "profile" &&
        flutter_runtime_mode != "release" &&
        flutter_runtime_mode != "jit_release") {
      args += [ "--enable_asserts" ]
    }

    if (is_aot) {
      args += [ "--deterministic" ]
      if (is_ios) {
        snapshot_assembly = "$target_gen_dir/ios/snapshot_assembly.S"
        outputs += [ snapshot_assembly ]
        args += [
          "--snapshot_kind=app-aot-assembly",
          "--assembly=" + rebase_path(snapshot_assembly),
        ]
      } else if (is_android) {
        if (defined(invoker.output_aot_lib)) {
          output_aot_lib = invoker.output_aot_lib
        } else {
          output_aot_lib = "libapp.so"
        }
        libapp = "$target_gen_dir/android/libs/$android_app_abi/$output_aot_lib"
        outputs += [ libapp ]
        args += [
          "--snapshot_kind=app-aot-elf",
          "--elf=" + rebase_path(libapp),
        ]
      } else {
        assert(false)
      }
    } else {
      deps += [ "//flutter/lib/snapshot:generate_snapshot_bin" ]
      vm_snapshot_data =
          "$root_gen_dir/flutter/lib/snapshot/vm_isolate_snapshot.bin"
      snapshot_data = "$root_gen_dir/flutter/lib/snapshot/isolate_snapshot.bin"
      isolate_snapshot_data = "$target_gen_dir/isolate_snapshot_data"
      isolate_snapshot_instructions = "$target_gen_dir/isolate_snapshot_instr"

      inputs += [
        vm_snapshot_data,
        snapshot_data,
      ]

      outputs += [
        isolate_snapshot_data,
        isolate_snapshot_instructions,
      ]
      args += [
        "--snapshot_kind=app-jit",
        "--load_vm_snapshot_data=" + rebase_path(vm_snapshot_data),
        "--load_isolate_snapshot_data=" + rebase_path(snapshot_data),
        "--isolate_snapshot_data=" + rebase_path(isolate_snapshot_data),
        "--isolate_snapshot_instructions=" +
            rebase_path(isolate_snapshot_instructions),
      ]
    }

    args += [ rebase_path(kernel_output) ]
  }

  group(target_name) {
    public_deps = [
      ":$kernel_target",
      ":$snapshot_target",
    ]
  }
}

# Creates an app-jit snapshot for a command-line Dart program based on a
# training run.
#
# Parameters:
#  main_dart (required):
#    The entrypoint to the Dart application.
#
#  training_args (required):
#    Arguments to pass to the Dart application for the training run.
#
#  vm_args (optional):
#    Additional arguments to the Dart VM.
#
#  deps (optional):
#    Any build dependencies.
#
#  package_config (required):
#    The .packages file for the app. Defaults to the $_dart_root/.packages.
#
#  output (optional):
#    Overrides the full output path.
#
#  snapshot_kind (optional)
#    Either an "app-jit" snapshot (default) or a "kernel" snapshot
template("application_snapshot") {
  assert(defined(invoker.main_dart), "Must specify 'main_dart'")
  assert(defined(invoker.training_args), "Must specify 'training_args'")
  assert(defined(invoker.package_config), "Must specify 'package_config'")

  main_dart = invoker.main_dart
  training_args = invoker.training_args
  package_config = rebase_path(invoker.package_config)
  name = target_name

  extra_deps = []
  if (defined(invoker.deps)) {
    extra_deps += invoker.deps
  }
  extra_inputs = [ main_dart ]
  if (defined(invoker.inputs)) {
    extra_inputs += invoker.inputs
  }
  output = "$root_gen_dir/$name.dart.snapshot"
  if (defined(invoker.output)) {
    output = invoker.output
  }

  depfile = output + ".d"
  abs_depfile = rebase_path(depfile)
  abs_output = rebase_path(output)
  rel_output = rebase_path(output, root_build_dir)
  snapshot_vm_args = [
    "--disable-dart-dev",
    "--deterministic",
    "--packages=$package_config",
    "--snapshot=$abs_output",
    "--snapshot-depfile=$abs_depfile",
    "--depfile-output-filename=$rel_output",
  ]
  if (defined(invoker.vm_args)) {
    snapshot_vm_args += invoker.vm_args
  }

  snapshot_kind = "app-jit"
  if (target_cpu != host_cpu) {
    snapshot_kind = "kernel"
  }
  if (defined(invoker.snapshot_kind)) {
    snapshot_kind = invoker.snapshot_kind
  }
  if (snapshot_kind == "kernel") {
    snapshot_vm_args += [ "--snapshot-kind=kernel" ]
  } else if (snapshot_kind == "app-jit") {
    snapshot_vm_args += [ "--snapshot-kind=app-jit" ]
  } else {
    assert(false, "Bad snapshot_kind: '$snapshot_kind'")
  }

  # Ensure the compiled appliation (e.g. frontend-server, ...) will use this
  # Dart SDK hash when consuming/producing kernel.
  #
  # (Instead of ensuring every user of the "application_snapshot" passes it's
  # own)
  snapshot_vm_args += [ "-Dsdk_hash=$sdk_hash" ]

  if (flutter_prebuilt_dart_sdk) {
    action(target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])
      deps = extra_deps
      script = "//build/gn_run_binary.py"
      inputs = extra_inputs
      outputs = [ output ]
      depfile = depfile
      pool = "//flutter/build/dart:dart_pool"

      ext = ""
      if (is_win) {
        ext = ".exe"
      }
      dart = rebase_path("$host_prebuilt_dart_sdk/bin/dart$ext", root_build_dir)

      args = [ dart ]
      args += snapshot_vm_args
      args += [ rebase_path(main_dart) ]
      args += training_args
    }
  } else {
    dart_action(target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])
      script = main_dart
      pool = "//flutter/build/dart:dart_pool"
      deps = extra_deps
      inputs = extra_inputs
      outputs = [ output ]
      depfile = depfile
      vm_args = snapshot_vm_args
      args = training_args
    }
  }
}

template("_dart_pkg_helper") {
  assert(defined(invoker.package_name))
  package_name = invoker.package_name
  pkg_directory = rebase_path("$root_gen_dir/dart-pkg")
  package_root = rebase_path("$root_gen_dir/dart-pkg/packages")
  stamp_file = "$root_gen_dir/dart-pkg/${package_name}.stamp"
  entries_file = "$root_gen_dir/dart-pkg/${package_name}.entries"

  assert(defined(invoker.sources) || defined(invoker.apps) ||
         defined(invoker.libs) || defined(invoker.pkg_dir))

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

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

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

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

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

    script = rebase_path("//flutter/build/dart/tools/dart_pkg.py", ".", "//")

    entrypoints = []
    if (defined(invoker.apps)) {
      foreach(app, invoker.apps) {
        entrypoints += [ app[1] ]
      }
    }
    if (defined(invoker.libs)) {
      entrypoints += invoker.libs
    }

    sources = entrypoints
    extra_flags = []
    if (defined(invoker.sources)) {
      sources += invoker.sources
    } else if (defined(invoker.pkg_dir)) {
      list_script = rebase_path("//flutter/build/dart/tools/ls.py", ".", "//")
      extra_flags += [ "--read_only" ]
      ls_sources = exec_script(list_script,
                               [
                                 "--target-directory",
                                 rebase_path(invoker.pkg_dir),
                               ],
                               "list lines")
      sources += ls_sources
    }

    # We have to use foreach to set up outputs instead of rebase_path because
    # GN doesn't like assignments to outputs that aren't obviously under
    # $root_gen_dir somewhere.
    outputs = [
      "$root_gen_dir/dart-pkg/${package_name}",
      "$root_gen_dir/dart-pkg/${package_name}/pubspec.yaml",
      "$root_gen_dir/dart-pkg/packages/${package_name}",
      stamp_file,
    ]

    inputs = [ script ] + rebase_path(sources)

    args = [
             "--package-name",
             package_name,
             "--dart-sdk",
             rebase_path(dart_sdk_root),
             "--pkg-directory",
             pkg_directory,
             "--package-root",
             package_root,
             "--stamp-file",
             rebase_path(stamp_file),
             "--entries-file",
             rebase_path(entries_file),
             "--package-sources",
           ] + rebase_path(sources) + [ "--package-entrypoints" ] +
           rebase_path(entrypoints) + [ "--sdk-ext-directories" ] +
           rebase_path(sdk_ext_directory) + [ "--sdk-ext-files" ] +
           rebase_path(sdk_ext_files) + [ "--sdk-ext-mappings" ] +
           sdk_ext_mappings + extra_flags
  }
}

template("dart_pkg") {
  if (defined(invoker.pkg_dir)) {
    pubspec_yaml_path = rebase_path("pubspec.yaml", "", invoker.pkg_dir)
  } else {
    pubspec_yaml_path = rebase_path("pubspec.yaml")
  }
  dart_package_name_script =
      rebase_path("//flutter/build/dart/tools/dart_package_name.py", ".", "//")
  dart_package_name = exec_script(dart_package_name_script,
                                  [
                                    "--pubspec",
                                    pubspec_yaml_path,
                                  ],
                                  "trim string",
                                  [ pubspec_yaml_path ])

  dart_pkg_target_name = "${target_name}_pkg_helper"
  _dart_pkg_helper(dart_pkg_target_name) {
    package_name = dart_package_name
    if (defined(invoker.sources)) {
      sources = invoker.sources
    }
    if (defined(invoker.apps)) {
      apps = invoker.apps
    }
    if (defined(invoker.libs)) {
      libs = invoker.libs
    }
    if (defined(invoker.pkg_dir)) {
      pkg_dir = invoker.pkg_dir
    }
    if (defined(invoker.deps)) {
      deps = invoker.deps
    }
    if (defined(invoker.datadeps)) {
      datadeps = invoker.datadeps
    }
    if (defined(invoker.sdk_ext_directory)) {
      sdk_ext_directory = invoker.sdk_ext_directory
    }
    if (defined(invoker.sdk_ext_files)) {
      sdk_ext_files = invoker.sdk_ext_files
    }
    if (defined(invoker.sdk_ext_mappings)) {
      sdk_ext_mappings = invoker.sdk_ext_mappings
    }
  }

  if (defined(invoker.apps)) {
    pkg_helper_output_dir = "$root_gen_dir/dart-pkg/${dart_package_name}"
    foreach(app, invoker.apps) {
      app_name = app[0]
      app_entrypoint = app[1]
      dartx_output_name = app_name
      dartx_application("${app_name}_dart_app") {
        output_name = dartx_output_name
        main_dart = rebase_path(app_entrypoint, "", pkg_helper_output_dir)
        sources = [ "$root_gen_dir/dart-pkg/${dart_package_name}.stamp" ]
        deps = [ ":$dart_pkg_target_name" ]
        deps += invoker.deps
        if (defined(invoker.strict)) {
          strict = invoker.strict
        }
      }
    }
  }

  group(target_name) {
    public_deps = [ ":$dart_pkg_target_name" ]
    if (defined(invoker.apps)) {
      foreach(app, invoker.apps) {
        app_name = app[0]
        dartx_target_name = "${app_name}_dart_app"
        deps += [ ":$dartx_target_name" ]
      }
    }
  }
}
