# Copyright 2015 The Chromium 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("//build/config/mac/base_rules.gni")

# Generates Info.plist files for Mac apps and frameworks.
#
# Arguments
#
#     info_plist:
#         string, the path to an plist file that will be included in the final
#         Info.plist generated.
#
#     executable_name:
#         string, name of the generated target used for the product
#         and executable name as specified in the output Info.plist.
#
#     extra_substitutions:
#         (optional) string array, 'key=value' pairs for extra fields which are
#         specified in a source Info.plist template.
template("ios_info_plist") {
  info_plist(target_name) {
    format = "binary1"
    extra_substitutions = []
    if (defined(invoker.extra_substitutions)) {
      extra_substitutions = invoker.extra_substitutions
    }
    extra_substitutions += [
      "IOS_DEPLOYMENT_TARGET=$ios_deployment_target",
      "IOS_PLATFORM_BUILD=$ios_platform_build",
      "IOS_PLATFORM_NAME=$ios_sdk_name",
      "IOS_PLATFORM_VERSION=$ios_sdk_version",
      "IOS_SDK_BUILD=$ios_sdk_build",
      "IOS_SDK_NAME=$ios_sdk_name$ios_sdk_version",
      "IOS_SUPPORTED_PLATFORM=$ios_sdk_platform",
    ]
    plist_templates = [
      "//build/config/ios/BuildInfo.plist",
      invoker.info_plist,
    ]
    forward_variables_from(invoker,
                           [
                             "executable_name",
                             "visibility",
                           ])
  }
}

# TODO(crbug.com/297668): refactor this template to extract common behaviour
# between OS X and iOS bundle generation, then create a generic "app" template
# that forward to "executable" on all platform except iOS/OS X.

# Template to build an application bundle for iOS.
#
# This should be used instead of "executable" built-in target type on iOS.
# As the template forward the generation of the application executable to
# an "executable" target, all arguments supported by "executable" targets
# are also supported by this template.
#
# Arguments
#
#   output_name:
#       (optional) string, name of the generated application, if omitted,
#       defaults to the target_name.
#
#   extra_substitutions:
#       (optional) list of string in "key=value" format, each value will
#       be used as an additional variable substitution rule when generating
#       the application Info.plist
#
#   info_plist:
#       path to the template to use to generate the application Info.plist
#       by performing variable substitutions.
#
# For more information, see "gn help executable".
template("ios_app_bundle") {
  assert(defined(invoker.info_plist),
         "info_plist must be specified for target $target_name")

  _output_name = target_name
  _target_name = target_name
  if (defined(invoker.output_name)) {
    _output_name = invoker.output_name
  }

  _generate_info_plist = target_name + "_generate_info_plist"
  _bundle_data_info_plist = target_name + "_bundle_data_info_plist"

  ios_info_plist(_generate_info_plist) {
    executable_name = _output_name
    forward_variables_from(invoker,
                           [
                             "extra_substitutions",
                             "info_plist",
                           ])
  }

  bundle_data(_bundle_data_info_plist) {
    forward_variables_from(invoker, [ "testonly" ])
    visibility = [ ":$_target_name" ]
    sources = get_target_outputs(":$_generate_info_plist")
    outputs = [
      "{{bundle_root_dir}}/Info.plist",
    ]
    public_deps = [
      ":$_generate_info_plist",
    ]
  }

  _generate_executable = target_name + "_generate_executable"
  _bundle_data_executable = target_name + "_bundle_data_executable"

  executable(_generate_executable) {
    visibility = [ ":$_bundle_data_executable" ]
    forward_variables_from(invoker,
                           "*",
                           [
                             "assert_no_deps",
                             "code_signing_identity",
                             "data_deps",
                             "entitlements_path",
                             "info_plist",
                             "output_name",
                             "visibility",
                           ])

    output_name = rebase_path("$target_gen_dir/$_output_name", root_out_dir)
    if (!defined(libs)) {
      libs = []
    }
    libs += [ "UIKit.framework" ]
  }

  bundle_data(_bundle_data_executable) {
    forward_variables_from(invoker, [ "testonly" ])
    visibility = [ ":$_target_name" ]
    sources = [
      "$target_gen_dir/$_output_name",
    ]
    outputs = [
      "{{bundle_executable_dir}}/$_output_name",
    ]
    public_deps = [
      ":$_generate_executable",
    ]
  }

  create_bundle(target_name) {
    forward_variables_from(invoker,
                           [
                             "data_deps",
                             "deps",
                             "public_deps",
                             "testonly",
                             "visibility",
                           ])

    if (!defined(deps)) {
      deps = []
    }
    deps += [
      ":$_bundle_data_executable",
      ":$_bundle_data_info_plist",
    ]

    if (use_ios_simulator) {
      if (!defined(data_deps)) {
        data_deps = []
      }
      data_deps += [ "//testing/iossim" ]
    }

    product_type = "com.apple.product-type.application"
    bundle_root_dir = "$root_out_dir/$_output_name.app"
    bundle_resources_dir = bundle_root_dir
    bundle_executable_dir = bundle_root_dir
    bundle_plugins_dir = "$bundle_root_dir/Plugins"
  }

  # TODO(crbug.com/297668):
  # - add support for codesigning,
  # - find a way to make "ninja -C out/Default base_unittests.app" work as
  #   an alias to "ninja -C out/Default base_unittests" (for convenience
  #   and compatibility with gyp),
}

# Compile a xib or storyboard file and add it to a bundle_data so that it is
# available at runtime in the bundle.
#
# Arguments
#
#   source:
#       string, path of the xib or storyboard to compile.
#
# Forwards all variables to the bundle_data target.
template("bundle_data_xib") {
  assert(defined(invoker.source), "source needs to be defined for $target_name")

  _source_extension = get_path_info(invoker.source, "extension")
  assert(_source_extension == "xib" || _source_extension == "storyboard",
         "source must be a .xib or .storyboard for $target_name")

  _target_name = target_name
  _compile_xib = target_name + "_compile_xib"

  compile_xibs(_compile_xib) {
    sources = [
      invoker.source,
    ]
    visibility = [ ":$_target_name" ]
    ibtool_flags = [
      "--minimum-deployment-target",
      ios_deployment_target,
      "--auto-activate-custom-fonts",
      "--target-device",
      "iphone",
      "--target-device",
      "ipad",
    ]
  }

  bundle_data(_target_name) {
    forward_variables_from(invoker, "*", [ "source" ])

    if (!defined(public_deps)) {
      public_deps = []
    }
    public_deps += [ ":$_compile_xib" ]

    sources = get_target_outputs(":$_compile_xib")

    outputs = [
      "{{bundle_resources_dir}}/{{source_file_part}}",
    ]
  }
}

# Template to package a shared library into an iOS framework bundle.
#
# This template provides two targets to control whether the framework is
# merely built when targets depend on it, or whether it is linked as well:
# "$target_name" and "$target_name+link".
#
# See the //build/config/mac/base_rules.gni:framework_bundle for a discussion
# and examples.
#
# Arguments
#
#     output_name:
#         (optional) string, name of the generated framework without the
#         .framework suffix. If omitted, defaults to target_name.
#
#     framework_version:
#         (optional) string, version of the framework. Typically this is a
#         single letter, like "A". If omitted, the Versions/ subdirectory
#         structure will not be created, and build output will go directly
#         into the framework subdirectory.
#
#     public_headers:
#         (optional) list of paths to header file that needs to be copied
#         into the framework bundle Headers subdirectory. If omitted or
#         empty then the Headers subdirectory is not created.
#
#     sources
#         (optional) list of files. Needs to be defined and non-empty if
#         public_headers is defined and non-empty.
#
# See "gn help shared_library" for more information on arguments supported
# by shared library target.
template("ios_framework_bundle") {
  _target_name = target_name
  _output_name = target_name
  if (defined(invoker.output_name)) {
    _output_name = invoker.output_name
  }
  _framework_target = _target_name

  if (defined(invoker.public_headers) && invoker.public_headers != []) {
    _public_headers = invoker.public_headers
    _framework_name = _output_name + ".framework"
    _framework_root = "$root_out_dir/$_framework_name"
    _framework_target = target_name + "_internal"

    _header_map_filename = "$target_gen_dir/$_output_name.headers.hmap"
    _framework_headers_target = _target_name + "_framework_headers"

    _compile_headers_map_target = _target_name + "_compile_headers_map"
    action(_compile_headers_map_target) {
      visibility = [ ":$_framework_headers_target" ]
      script = "$root_out_dir/gyp-mac-tool"
      outputs = [
        _header_map_filename,
      ]

      # The header map generation only wants the list of headers, not all of
      # sources, so filter any non-header source files from "sources". It is
      # less error prone that having the developer duplicate the list of all
      # headers in addition to "sources".
      set_sources_assignment_filter([
                                      "*.c",
                                      "*.cc",
                                      "*.cpp",
                                      "*.m",
                                      "*.mm",
                                    ])
      sources = invoker.sources
      set_sources_assignment_filter([])

      args = [
               "compile-ios-framework-header-map",
               rebase_path(_header_map_filename),
               rebase_path(_framework_root, root_out_dir),
             ] + rebase_path(sources, root_out_dir)
    }

    _create_module_map_target = _target_name + "_module_map"
    action(_create_module_map_target) {
      visibility = [ ":$_framework_headers_target" ]
      script = "$root_out_dir/gyp-mac-tool"
      outputs = [
        "$_framework_root/Modules/module.modulemap",
      ]
      args = [
        "package-ios-framework",
        rebase_path("$_framework_root", root_out_dir),
      ]
    }

    _copy_public_headers_target = _target_name + "_copy_public_headers"
    copy(_copy_public_headers_target) {
      visibility = [ ":$_framework_headers_target" ]
      sources = _public_headers
      outputs = [
        "$_framework_root/Headers/{{source_file_part}}",
      ]
    }

    _headers_map_config = _target_name + "_headers_map"
    config(_headers_map_config) {
      visibility = [ ":$_target_name" ]
      include_dirs = [ _header_map_filename ]
      ldflags = [
        "-install_name",
        "@rpath/$_framework_name/$_output_name",
      ]
    }

    group(_framework_headers_target) {
      deps = [
        ":$_compile_headers_map_target",
        ":$_copy_public_headers_target",
        ":$_create_module_map_target",
      ]
    }
  }

  _framework_public_config = _target_name + "_ios_public_config"
  config(_framework_public_config) {
    visibility = [ ":$_framework_public_config" ]
    if (defined(_public_headers)) {
      common_flags = [ "-F" + rebase_path("$root_out_dir/.", root_out_dir) ]
      cflags_objc = common_flags
      cflags_objcc = common_flags
    }

    # The link settings are inherited from the framework_bundle config.
  }

  framework_bundle(_framework_target) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "output_name",
                             "public_headers",
                             "visibility",
                           ])
    output_name = _output_name

    if (!defined(public_configs)) {
      public_configs = []
    }
    public_configs += [ ":$_framework_public_config" ]

    if (defined(_public_headers)) {
      visibility = [
        ":$_target_name",
        ":$_target_name+link",
      ]
      configs += [ ":$_headers_map_config" ]

      if (!defined(deps)) {
        deps = []
      }
      deps += [ ":$_framework_headers_target" ]
    } else {
      if (defined(invoker.visibility)) {
        visibility = invoker.visibility
        visibility += [ ":$_target_name+link" ]
      }
    }
  }

  if (defined(_public_headers)) {
    group(_target_name) {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "public_configs",
                             ])

      if (defined(invoker.visibility)) {
        visibility = invoker.visibility
        visibility += [ ":$_target_name+link" ]
      }

      public_deps = [
        ":$_framework_target",
      ]
    }

    group(_target_name + "+link") {
      forward_variables_from(invoker,
                             [
                               "testonly",
                               "visibility",
                             ])
      public_deps = [
        ":$_framework_target+link",
      ]
    }
  }
}