chromium/build/config/ios/rules.gni

# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/apple/apple_info_plist.gni")
import("//build/config/apple/symbols.gni")
import("//build/config/compiler/compiler.gni")
import("//build/config/ios/ios_sdk.gni")
import("//build/config/zip.gni")
import("//build/toolchain/rbe.gni")
import("//build/toolchain/siso.gni")
import("//build/toolchain/toolchain.gni")
import("//build_overrides/build.gni")

# Constants corresponding to the bundle type identifiers use application,
# application extension, XCTest and XCUITest targets respectively.
_ios_xcode_app_bundle_id = "com.apple.product-type.application"
_ios_xcode_appex_bundle_id = "com.apple.product-type.app-extension"
_ios_xcode_xctest_bundle_id = "com.apple.product-type.bundle.unit-test"
_ios_xcode_xcuitest_bundle_id = "com.apple.product-type.bundle.ui-testing"

# Wrapper around create_bundle taking care of code signature settings.
#
# Arguments
#
#   product_type
#       string, product type for the generated Xcode project.
#
#   bundle_gen_dir
#       (optional) directory where the bundle is generated; must be below
#       root_out_dir and defaults to root_out_dir if omitted.
#
#   bundle_deps
#       (optional) list of additional dependencies.
#
#   bundle_deps_filter
#       (optional) list of dependencies to filter (for more information
#       see "gn help bundle_deps_filter").
#
#   bundle_extension
#       string, extension of the bundle, used to generate bundle name.
#
#   bundle_binary_target
#       (optional) string, label of the target generating the bundle main
#       binary. This target and bundle_binary_path are mutually exclusive.
#
#   bundle_binary_output
#       (optional) string, base name of the binary generated by the
#       bundle_binary_target target, defaults to the target name.
#
#   bundle_binary_path
#       (optional) string, path to the bundle main binary. This target and
#       bundle_binary_target are mutually exclusive.
#
#   output_name:
#       (optional) string, name of the generated application, if omitted,
#       defaults to the target_name.
#
#   extra_system_frameworks
#       (optional) list of system framework to copy to the bundle.
#
#   enable_code_signing
#       (optional) boolean, control whether code signing is enabled or not,
#       default to ios_enable_code_signing if not defined.
#
#   entitlements_path:
#       (optional) path to the template to use to generate the application
#       entitlements by performing variable substitutions, defaults to
#       //build/config/ios/entitlements.plist.
#
#   entitlements_target:
#       (optional) label of the target generating the application
#       entitlements (must generate a single file as output); cannot be
#       defined if entitlements_path is set.
#
#   has_public_headers:
#       (optional) boolean, defaults to false; only meaningful if the bundle
#       is a framework bundle; if true, then the frameworks includes public
#       headers
#
#   disable_entitlements
#       (optional, defaults to false) boolean, control whether entitlements willi
#       be embedded in the application during signature. If false and no
#       entitlements are provided, default empty entitlements will be used.
#
#   disable_embedded_mobileprovision
#       (optional, default to false) boolean, control whether mobile provisions
#       will be embedded in the bundle. If true, the existing
#       embedded.mobileprovision will be deleted.
#
#   xcode_extra_attributes
#       (optional) scope, extra attributes for Xcode projects.
#
#   xcode_test_application_name:
#       (optional) string, name of the test application for Xcode unit or ui
#       test target.
#
#   xcode_product_bundle_id:
#       (optional) string, the bundle ID that will be added in the XCode
#       attributes to enable some features when debugging (e.g. MetricKit).
#
#   primary_info_plist:
#       (optional) path to Info.plist to merge with the $partial_info_plist
#       generated by the compilation of the asset catalog.
#
#   partial_info_plist:
#       (optional) path to the partial Info.plist generated by the asset
#       catalog compiler; if defined $primary_info_plist must also be defined.
#
#   transparent
#       (optional) boolean, whether the bundle is "transparent"; defaults to
#       "false" if omitted; a bundle is considered "transparent" if it does
#       not package the "bundle_data" deps but forward them to all targets
#       the depend on it (unless the "bundle_data" target sets "product_type"
#       to the same value as the "create_signed_bundle" target).
#
template("create_signed_bundle") {
  assert(defined(invoker.product_type),
         "product_type must be defined for $target_name")
  assert(defined(invoker.bundle_extension),
         "bundle_extension must be defined for $target_name")
  assert(defined(invoker.bundle_binary_target) !=
             defined(invoker.bundle_binary_path),
         "Only one of bundle_binary_target or bundle_binary_path may be " +
             "specified for $target_name")
  assert(!defined(invoker.partial_info_plist) ||
             defined(invoker.primary_info_plist),
         "primary_info_plist must be defined when partial_info_plist is " +
             "defined for $target_name")

  if (defined(invoker.xcode_test_application_name)) {
    assert(
        invoker.product_type == _ios_xcode_xctest_bundle_id ||
            invoker.product_type == _ios_xcode_xcuitest_bundle_id,
        "xcode_test_application_name can be only defined for Xcode unit or ui test target.")
  }

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

  if (defined(invoker.bundle_binary_path)) {
    _bundle_binary_path = invoker.bundle_binary_path
  } else {
    _bundle_binary_target = invoker.bundle_binary_target
    _bundle_binary_output = get_label_info(_bundle_binary_target, "name")
    if (defined(invoker.bundle_binary_output)) {
      _bundle_binary_output = invoker.bundle_binary_output
    }
    _bundle_binary_path =
        get_label_info(_bundle_binary_target, "target_out_dir") +
        "/$_bundle_binary_output"
  }

  _bundle_gen_dir = root_out_dir
  if (defined(invoker.bundle_gen_dir)) {
    _bundle_gen_dir = invoker.bundle_gen_dir
  }

  _bundle_extension = invoker.bundle_extension

  _enable_embedded_mobileprovision = true
  if (defined(invoker.disable_embedded_mobileprovision)) {
    _enable_embedded_mobileprovision = !invoker.disable_embedded_mobileprovision
  }

  if (target_environment == "catalyst") {
    _enable_embedded_mobileprovision = false
  }

  _enable_entitlements = true
  if (defined(invoker.disable_entitlements)) {
    _enable_entitlements = !invoker.disable_entitlements
  }

  if (_enable_entitlements) {
    if (!defined(invoker.entitlements_target)) {
      _entitlements_path = "//build/config/ios/entitlements.plist"
      if (defined(invoker.entitlements_path)) {
        _entitlements_path = invoker.entitlements_path
      }
    } else {
      assert(!defined(invoker.entitlements_path),
             "Cannot define both entitlements_path and entitlements_target " +
                 "for $target_name")

      _entitlements_target_outputs =
          get_target_outputs(invoker.entitlements_target)
      _entitlements_path = _entitlements_target_outputs[0]
    }
  }

  _enable_code_signing = ios_enable_code_signing
  if (defined(invoker.enable_code_signing)) {
    _enable_code_signing = invoker.enable_code_signing
  }

  create_bundle(_target_name) {
    forward_variables_from(invoker,
                           [
                             "bundle_deps_filter",
                             "data_deps",
                             "deps",
                             "partial_info_plist",
                             "product_type",
                             "public_configs",
                             "public_deps",
                             "testonly",
                             "transparent",
                             "visibility",
                             "xcode_test_application_name",
                           ])

    bundle_root_dir = "$_bundle_gen_dir/$_output_name$_bundle_extension"
    if (target_environment == "simulator" || target_environment == "device") {
      bundle_contents_dir = bundle_root_dir
      bundle_resources_dir = bundle_contents_dir
      bundle_executable_dir = bundle_contents_dir
    } else if (target_environment == "catalyst") {
      if (_bundle_extension != ".framework") {
        bundle_contents_dir = "$bundle_root_dir/Contents"
        bundle_resources_dir = "$bundle_contents_dir/Resources"
        bundle_executable_dir = "$bundle_contents_dir/MacOS"
      } else {
        bundle_contents_dir = "$bundle_root_dir/Versions/A"
        bundle_resources_dir = "$bundle_contents_dir/Resources"
        bundle_executable_dir = bundle_contents_dir
      }
    }

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

    _bundle_identifier = ""
    if (defined(invoker.xcode_product_bundle_id)) {
      _bundle_identifier = invoker.xcode_product_bundle_id
      assert(_bundle_identifier == string_replace(_bundle_identifier, "_", "-"),
             "$target_name: bundle_identifier does not respect rfc1034: " +
                 _bundle_identifier)
    }

    xcode_extra_attributes = {
      IPHONEOS_DEPLOYMENT_TARGET = ios_deployment_target
      PRODUCT_BUNDLE_IDENTIFIER = _bundle_identifier
      CODE_SIGNING_REQUIRED = "NO"
      CODE_SIGNING_ALLOWED = "NO"
      CODE_SIGN_IDENTITY = ""
      DONT_GENERATE_INFOPLIST_FILE = "YES"

      # If invoker has defined extra attributes, they override the defaults.
      if (defined(invoker.xcode_extra_attributes)) {
        forward_variables_from(invoker.xcode_extra_attributes, "*")
      }
    }

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

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

    post_processing_script = "//build/config/apple/codesign.py"
    post_processing_sources = [ _bundle_binary_path ]
    if (_enable_entitlements) {
      if (defined(invoker.entitlements_target)) {
        deps += [ invoker.entitlements_target ]
      }
      post_processing_sources += [ _entitlements_path ]
    }
    post_processing_outputs = [ "$bundle_executable_dir/$_output_name" ]
    if (_enable_code_signing) {
      post_processing_outputs +=
          [ "$bundle_contents_dir/_CodeSignature/CodeResources" ]
    }
    if (ios_code_signing_identity != "" && target_environment == "device" &&
        _enable_embedded_mobileprovision) {
      post_processing_outputs +=
          [ "$bundle_contents_dir/embedded.mobileprovision" ]
    }
    if (_bundle_extension == ".framework") {
      if (target_environment == "catalyst") {
        post_processing_outputs += [
          "$bundle_root_dir/Versions/Current",
          "$bundle_root_dir/$_output_name",
        ]

        if (defined(invoker.has_public_headers) && invoker.has_public_headers) {
          post_processing_outputs += [
            "$bundle_root_dir/Headers",
            "$bundle_root_dir/Modules",
          ]
        }
      } else {
        not_needed(invoker, [ "has_public_headers" ])
      }
    }

    if (defined(invoker.extra_system_frameworks)) {
      foreach(_framework, invoker.extra_system_frameworks) {
        post_processing_outputs += [ "$bundle_contents_dir/Frameworks/" +
                                     get_path_info(_framework, "file") ]
      }
    }

    post_processing_args = [
      "code-sign-bundle",
      "-t=" + ios_sdk_name,
      "-i=" + ios_code_signing_identity,
      "-b=" + rebase_path(_bundle_binary_path, root_build_dir),
    ]
    foreach(mobileprovision, ios_mobileprovision_files) {
      post_processing_args +=
          [ "-m=" + rebase_path(mobileprovision, root_build_dir) ]
    }
    post_processing_sources += ios_mobileprovision_files
    if (_enable_entitlements) {
      post_processing_args +=
          [ "-e=" + rebase_path(_entitlements_path, root_build_dir) ]
    }
    if (!_enable_embedded_mobileprovision) {
      post_processing_args += [ "--disable-embedded-mobileprovision" ]
    }
    post_processing_args += [ rebase_path(bundle_root_dir, root_build_dir) ]
    if (!_enable_code_signing) {
      post_processing_args += [ "--disable-code-signature" ]
    }
    if (defined(invoker.extra_system_frameworks)) {
      # All framework in extra_system_frameworks are expected to be system
      # framework and the path to be already system absolute so do not use
      # rebase_path here unless using RBE and system Xcode (as in that
      # case the system framework are found via a symlink in root_build_dir).
      foreach(_framework, invoker.extra_system_frameworks) {
        if (use_system_xcode && use_remoteexec) {
          _framework_path = rebase_path(_framework, root_build_dir)
        } else {
          _framework_path = _framework
        }
        post_processing_args += [ "-F=$_framework_path" ]
      }
    }
    if (defined(invoker.partial_info_plist)) {
      _partial_info_plists = [
        invoker.primary_info_plist,
        invoker.partial_info_plist,
      ]

      _plist_compiler_path = "//build/apple/plist_util.py"

      post_processing_sources += _partial_info_plists
      post_processing_sources += [ _plist_compiler_path ]
      if (target_environment != "catalyst" ||
          _bundle_extension != ".framework") {
        post_processing_outputs += [ "$bundle_contents_dir/Info.plist" ]
      } else {
        post_processing_outputs += [ "$bundle_resources_dir/Info.plist" ]
      }

      post_processing_args +=
          [ "-P=" + rebase_path(_plist_compiler_path, root_build_dir) ]
      foreach(_partial_info_plist, _partial_info_plists) {
        post_processing_args +=
            [ "-p=" + rebase_path(_partial_info_plist, root_build_dir) ]
      }
    }
  }
}

# Generates Info.plist files for iOS apps and frameworks.
#
# Arguments
#
#     info_plist:
#         (optional) string, path to the Info.plist file that will be used for
#         the bundle.
#
#     info_plist_target:
#         (optional) string, if the info_plist is generated from an action,
#         rather than a regular source file, specify the target name in lieu
#         of info_plist. The two arguments are mutually exclusive.
#
#     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") {
  assert(defined(invoker.info_plist) != defined(invoker.info_plist_target),
         "Only one of info_plist or info_plist_target may be specified in " +
             target_name)

  if (defined(invoker.info_plist)) {
    _info_plist = invoker.info_plist
  } else {
    _info_plist_target_output = get_target_outputs(invoker.info_plist_target)
    _info_plist = _info_plist_target_output[0]
  }

  apple_info_plist(target_name) {
    format = "binary1"
    extra_substitutions = [
      "IOS_BUNDLE_ID_PREFIX=$ios_app_bundle_id_prefix",
      "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",
      "BUILD_MACHINE_OS_BUILD=$machine_os_build",
      "IOS_DEPLOYMENT_TARGET=$ios_deployment_target",
      "XCODE_BUILD=$xcode_build",
      "XCODE_VERSION=$xcode_version",
    ]
    if (defined(invoker.extra_substitutions)) {
      extra_substitutions += invoker.extra_substitutions
    }
    plist_templates = [
      "//build/config/ios/BuildInfo.plist",
      _info_plist,
    ]
    if (defined(invoker.info_plist_target)) {
      deps = [ invoker.info_plist_target ]
    }
    forward_variables_from(invoker,
                           [
                             "executable_name",
                             "output_name",
                             "visibility",
                             "testonly",
                           ])
  }
}

# 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:
#       (optional) string, path to the Info.plist file that will be used for
#       the bundle.
#
#   info_plist_target:
#       (optional) string, if the info_plist is generated from an action,
#       rather than a regular source file, specify the target name in lieu
#       of info_plist. The two arguments are mutually exclusive.
#
#   entitlements_path:
#       (optional) path to the template to use to generate the application
#       entitlements by performing variable substitutions, defaults to
#       //build/config/ios/entitlements.plist.
#
#   entitlements_target:
#       (optional) label of the target generating the application
#       entitlements (must generate a single file as output); cannot be
#       defined if entitlements_path is set.
#
#   product_type
#       (optional) string, product type for the generated Xcode project,
#       default to "com.apple.product-type.application". Should only be
#       overriden when building application extension.
#
#   enable_code_signing
#       (optional) boolean, control whether code signing is enabled or not,
#       default to ios_enable_code_signing if not defined.
#
#   variants
#       (optional) list of scopes, each scope needs to define the attributes
#       "name" and "bundle_deps"; if defined and non-empty, then one bundle
#       named $target_out_dir/$variant/$output_name will be created for each
#       variant with the same binary but the correct bundle_deps, the bundle
#       at $target_out_dir/$output_name will be a copy of the first variant.
#
#   bundle_identifier:
#       (optional) string, value of CFBundleIdentifier in the application
#       Info.plist, defaults to "$ios_app_bundle_id_prefix.$output_name"
#       if omitted. Will be used to set BUNDLE_IDENTIFIER when generating
#       the application Info.plist
#
#   orderfile_path:
#       (optional) string, path to an orderfile passed to the linker in order
#       to improve application launch performance.
#
#   intents_target:
#       (optional) string, label of the target defining the intents for the
#       application. If defined, it must corresponds to a `swift_source_set`
#       target configured with `generate_intents = true`.
#
#   transparent
#       (optional) boolean, whether the bundle is "transparent"; defaults to
#       "false" if omitted; a bundle is considered "transparent" if it does
#       not package the "bundle_data" deps but forward them to all targets
#       the depend on it (unless the "bundle_data" target sets "product_type"
#       to the same value as the "create_signed_bundle" target).
#
# For more information, see "gn help executable".
template("ios_app_bundle") {
  _output_name = target_name
  _target_name = target_name
  if (defined(invoker.output_name)) {
    _output_name = invoker.output_name
  }

  assert(
      !defined(invoker.bundle_extension),
      "bundle_extension must not be set for ios_app_bundle template for $target_name")

  # Whether the intents metadata should be extracted (note that they are
  # disabled when building for the catalyst environment)
  _extract_intents_metadata = false
  if (defined(invoker.intents_target)) {
    _extract_intents_metadata =
        invoker.intents_target != "" && target_environment != "catalyst"
  }

  if (defined(invoker.bundle_identifier)) {
    _bundle_identifier = invoker.bundle_identifier
    assert(_bundle_identifier == string_replace(_bundle_identifier, "_", "-"),
           "$target_name: bundle_identifier does not respect rfc1034: " +
               _bundle_identifier)
  } else {
    # Bundle identifier should respect rfc1034, so replace "_" with "-".
    _bundle_identifier =
        "$ios_app_bundle_id_prefix." + string_replace(_output_name, "_", "-")
  }

  if (defined(invoker.variants) && invoker.variants != []) {
    _variants = []

    foreach(_variant, invoker.variants) {
      assert(defined(_variant.name) && _variant.name != "",
             "name must be defined for all $target_name variants")

      assert(defined(_variant.bundle_deps),
             "bundle_deps must be defined for all $target_name variants")

      _variants += [
        {
          name = _variant.name
          bundle_deps = _variant.bundle_deps
          target_name = "${_target_name}_variants_${_variant.name}"
          bundle_gen_dir = "$root_out_dir/variants/${_variant.name}"
        },
      ]
    }
  } else {
    # If no variants are passed to the template, use a fake variant with
    # no name to avoid duplicating code. As no variant can have an empty
    # name except this fake variant, it is possible to know if a variant
    # is fake or not.
    _variants = [
      {
        name = ""
        bundle_deps = []
        target_name = _target_name
        bundle_gen_dir = root_out_dir
      },
    ]
  }

  _default_variant = _variants[0]

  _executable_target = _target_name + "_executable"
  _generate_entitlements_target = _target_name + "_gen_entitlements"
  _generate_entitlements_output =
      get_label_info(":$_generate_entitlements_target", "target_out_dir") +
      "/$_output_name.xcent"

  _product_type = _ios_xcode_app_bundle_id
  if (defined(invoker.product_type)) {
    _product_type = invoker.product_type
  }

  if (_product_type == _ios_xcode_app_bundle_id) {
    _bundle_extension = ".app"
  } else if (_product_type == _ios_xcode_appex_bundle_id) {
    _bundle_extension = ".appex"
  } else {
    assert(false, "unknown product_type \"$product_type\" for $_target_name")
  }

  _is_app_bundle = _product_type == _ios_xcode_app_bundle_id

  if (_extract_intents_metadata) {
    _metadata_extraction = _target_name + "_metadata_extraction"
    _metadata_bundledata = _target_name + "_metadata_bundledata"
  }

  executable(_executable_target) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "bundle_deps",
                             "bundle_deps_filter",
                             "bundle_extension",
                             "enable_code_signing",
                             "entitlements_path",
                             "entitlements_target",
                             "extra_substitutions",
                             "extra_system_frameworks",
                             "info_plist",
                             "info_plist_target",
                             "output_name",
                             "product_type",
                             "transparent",
                             "visibility",
                             "xcode_extra_attributes",
                           ])

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

    visibility = []
    foreach(_variant, _variants) {
      visibility += [ ":${_variant.target_name}" ]
    }
    if (_extract_intents_metadata) {
      visibility += [ ":$_metadata_extraction" ]
      deps += [ invoker.intents_target ]
    }

    if (defined(invoker.orderfile_path)) {
      orderfile_path = invoker.orderfile_path
      if (!defined(ldflags)) {
        ldflags = []
      }
      ldflags += [
        "-Wl,-order_file",
        "-Wl," + rebase_path(orderfile_path, root_build_dir),
      ]

      if (!defined(inputs)) {
        inputs = []
      }
      inputs += [ orderfile_path ]
    }

    if (target_environment == "simulator") {
      deps += [ ":$_generate_entitlements_target" ]

      if (!defined(inputs)) {
        inputs = []
      }
      inputs += [ _generate_entitlements_output ]

      if (!defined(ldflags)) {
        ldflags = []
      }
      ldflags += [ "-Wl,-sectcreate,__TEXT,__entitlements," +
                   rebase_path(_generate_entitlements_output, root_build_dir) ]
    }

    output_name = _output_name
    output_prefix_override = true
    output_dir = target_out_dir
  }

  if (_extract_intents_metadata) {
    _module_info_path =
        get_label_info(invoker.intents_target, "target_out_dir") + "/" +
        get_label_info(invoker.intents_target, "name") + ".module_info.json"

    action(_metadata_extraction) {
      _output_dir = "$target_out_dir/$target_name"
      _binary_path = "$target_out_dir/$_output_name"

      visibility = [ ":$_metadata_bundledata" ]
      script = "//build/config/ios/extract_metadata.py"
      sources = [
        _binary_path,
        _module_info_path,
      ]
      outputs = [
        "$_output_dir/Metadata.appintents/extract.actionsdata",
        "$_output_dir/Metadata.appintents/version.json",
      ]
      deps = [
        ":$_executable_target",
        invoker.intents_target,
      ]
      depfile = "$target_out_dir/$target_name.d"
      args = [
        "--toolchain-dir",
        rebase_path(ios_toolchains_path, root_build_dir),
        "--sdk-root",
        rebase_path(ios_sdk_path, root_build_dir),
        "--deployment-target",
        ios_deployment_target,
        "--target-cpu",
        current_cpu,
        "--target-environment",
        target_environment,
        "--depfile",
        rebase_path(depfile, root_build_dir),
        "--output",
        rebase_path(_output_dir, root_build_dir),
        "--binary-file",
        rebase_path(_binary_path, root_build_dir),
        "--module-info-path",
        rebase_path(_module_info_path, root_build_dir),
      ]

      # Starting with Xcode 15.3, appintentsmetadataprocessor requires to be
      # passed --xcode-version as parameter (with ${xcode_build} as value),
      # while previous versions did not recognize the parameter. So check
      # the version before deciding whether to set the parameter or not.
      if (xcode_version_int >= 1530) {
        args += [
          "--xcode-version",
          xcode_build,
        ]
      }
    }

    bundle_data(_metadata_bundledata) {
      public_deps = [ ":$_metadata_extraction" ]
      sources = get_target_outputs(":$_metadata_extraction")
      outputs = [ "{{bundle_resources_dir}}/" +
                  "Metadata.appintents/{{source_file_part}}" ]
    }
  }

  _generate_info_plist = target_name + "_generate_info_plist"
  ios_info_plist(_generate_info_plist) {
    forward_variables_from(invoker,
                           [
                             "info_plist",
                             "info_plist_target",
                           ])

    executable_name = _output_name

    extra_substitutions = [ "BUNDLE_IDENTIFIER=$_bundle_identifier" ]
    if (defined(invoker.extra_substitutions)) {
      extra_substitutions += invoker.extra_substitutions
    }
  }

  if (!defined(invoker.entitlements_target)) {
    _entitlements_path = "//build/config/ios/entitlements.plist"
    if (defined(invoker.entitlements_path)) {
      _entitlements_path = invoker.entitlements_path
    }
  } else {
    assert(!defined(invoker.entitlements_path),
           "Cannot define both entitlements_path and entitlements_target" +
               "for $_target_name")

    _entitlements_target_outputs =
        get_target_outputs(invoker.entitlements_target)
    _entitlements_path = _entitlements_target_outputs[0]
  }

  action(_generate_entitlements_target) {
    _gen_info_plist_outputs = get_target_outputs(":$_generate_info_plist")
    _info_plist_path = _gen_info_plist_outputs[0]

    script = "//build/config/apple/codesign.py"
    deps = [ ":$_generate_info_plist" ]
    if (defined(invoker.entitlements_target)) {
      deps += [ invoker.entitlements_target ]
    }
    sources = [
      _entitlements_path,
      _info_plist_path,
    ]
    sources += ios_mobileprovision_files

    outputs = [ _generate_entitlements_output ]

    args = [
      "generate-entitlements",
      "-e=" + rebase_path(_entitlements_path, root_build_dir),
      "-p=" + rebase_path(_info_plist_path, root_build_dir),
    ]
    foreach(mobileprovision, ios_mobileprovision_files) {
      args += [ "-m=" + rebase_path(mobileprovision, root_build_dir) ]
    }
    args += rebase_path(outputs, root_build_dir)
  }

  # Only write PkgInfo for real application, not application extension.
  if (_is_app_bundle) {
    _create_pkg_info = target_name + "_pkg_info"
    action(_create_pkg_info) {
      forward_variables_from(invoker, [ "testonly" ])
      script = "//build/apple/write_pkg_info.py"
      inputs = [ "//build/apple/plist_util.py" ]
      sources = get_target_outputs(":$_generate_info_plist")
      outputs = [
        # Cannot name the output PkgInfo as the name will not be unique if
        # multiple ios_app_bundle are defined in the same BUILD.gn file. The
        # file is renamed in the bundle_data outputs to the correct name.
        "$target_gen_dir/$target_name",
      ]
      args = [ "--plist" ] + rebase_path(sources, root_build_dir) +
             [ "--output" ] + rebase_path(outputs, root_build_dir)
      deps = [ ":$_generate_info_plist" ]
    }

    _bundle_data_pkg_info = target_name + "_bundle_data_pkg_info"
    bundle_data(_bundle_data_pkg_info) {
      forward_variables_from(invoker, [ "testonly" ])
      sources = get_target_outputs(":$_create_pkg_info")
      outputs = [ "{{bundle_resources_dir}}/PkgInfo" ]
      public_deps = [ ":$_create_pkg_info" ]
    }
  }

  foreach(_variant, _variants) {
    create_signed_bundle(_variant.target_name) {
      forward_variables_from(invoker,
                             [
                               "bundle_deps",
                               "bundle_deps_filter",
                               "data_deps",
                               "deps",
                               "enable_code_signing",
                               "entitlements_path",
                               "entitlements_target",
                               "extra_system_frameworks",
                               "public_configs",
                               "public_deps",
                               "testonly",
                               "transparent",
                               "visibility",
                               "xcode_extra_attributes",
                             ])

      output_name = _output_name
      bundle_gen_dir = _variant.bundle_gen_dir
      bundle_binary_target = ":$_executable_target"
      bundle_binary_output = _output_name
      bundle_extension = _bundle_extension
      product_type = _product_type
      xcode_product_bundle_id = _bundle_identifier

      _generate_info_plist_outputs =
          get_target_outputs(":$_generate_info_plist")
      primary_info_plist = _generate_info_plist_outputs[0]
      partial_info_plist =
          "$target_gen_dir/${_variant.target_name}_partial_info.plist"

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

      if (!defined(bundle_deps)) {
        bundle_deps = []
      }
      if (_is_app_bundle) {
        bundle_deps += [ ":$_bundle_data_pkg_info" ]
      }
      bundle_deps += _variant.bundle_deps
      if (_extract_intents_metadata) {
        bundle_deps += [ ":$_metadata_bundledata" ]
      }

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

  if (_default_variant.name != "") {
    _bundle_short_name = "$_output_name$_bundle_extension"
    action(_target_name) {
      forward_variables_from(invoker, [ "testonly" ])

      script = "//build/config/ios/hardlink.py"
      public_deps = []
      foreach(_variant, _variants) {
        public_deps += [ ":${_variant.target_name}" ]
      }

      sources = [ "${_default_variant.bundle_gen_dir}/$_bundle_short_name" ]
      outputs = [ "$root_out_dir/$_bundle_short_name" ]

      args = [
               "--output-dir",
               rebase_path(root_out_dir, root_build_dir),
               "--relative-to",
               rebase_path(_default_variant.bundle_gen_dir, root_build_dir),
             ] + rebase_path(sources, root_build_dir)
    }
  }
}

set_defaults("ios_app_bundle") {
  configs = default_executable_configs
}

# Template to build an application extension 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:
#       (optional) string, path to the Info.plist file that will be used for
#       the bundle.
#
#   info_plist_target:
#       (optional) string, if the info_plist is generated from an action,
#       rather than a regular source file, specify the target name in lieu
#       of info_plist. The two arguments are mutually exclusive.
#
# For more information, see "gn help executable".
template("ios_appex_bundle") {
  assert(ios_is_app_extension,
         "$target_name needs to be defined in app extension toolchain context")
  ios_app_bundle(target_name) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "bundle_extension",
                             "product_type",
                           ])
    product_type = _ios_xcode_appex_bundle_id
  }
}

set_defaults("ios_appex_bundle") {
  configs = [ "//build/config/ios:ios_extension_executable_flags" ]
}

# Template to compile .xib and .storyboard files.
#
# Arguments
#
#     sources:
#         list of string, sources to compile
#
#     ibtool_flags:
#         (optional) list of string, additional flags to pass to the ibtool
template("compile_ib_files") {
  action_foreach(target_name) {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    assert(defined(invoker.sources),
           "sources must be specified for $target_name")
    assert(defined(invoker.output_extension),
           "output_extension must be specified for $target_name")

    ibtool_flags = []
    if (defined(invoker.ibtool_flags)) {
      ibtool_flags = invoker.ibtool_flags
    }

    _output_extension = invoker.output_extension

    script = "//build/config/ios/compile_ib_files.py"
    sources = invoker.sources
    outputs = [
      "$target_gen_dir/$target_name/{{source_name_part}}.$_output_extension",
    ]
    args = [
      "--input",
      "{{source}}",
      "--output",
      rebase_path(
          "$target_gen_dir/$target_name/{{source_name_part}}.$_output_extension",
          root_build_dir),
    ]
    args += ibtool_flags
  }
}

# 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_ib_file") {
  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
  if (_source_extension == "xib") {
    _compile_ib_file = target_name + "_compile_xib"
    _output_extension = "nib"
  } else {
    _compile_ib_file = target_name + "_compile_storyboard"
    _output_extension = "storyboardc"
  }

  compile_ib_files(_compile_ib_file) {
    sources = [ invoker.source ]
    output_extension = _output_extension
    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_ib_file" ]

    sources = get_target_outputs(":$_compile_ib_file")

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

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

  _source_extension = get_path_info(invoker.source, "extension")
  assert(_source_extension == "strings",
         "source must be a .strings for $target_name")

  _target_name = target_name
  _convert_target = target_name + "_compile_strings"

  convert_plist(_convert_target) {
    visibility = [ ":$_target_name" ]
    source = invoker.source
    output =
        "$target_gen_dir/$_target_name/" + get_path_info(invoker.source, "file")
    format = "binary1"
  }

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

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

    sources = get_target_outputs(":$_convert_target")

    outputs = [ invoker.output ]
  }
}

# This template declares a bundle_data target that reference an assets
# catalog so that is is compiled to the asset catalog of the generated
# bundle.
#
# The target will ensure that only the files explicitly listed will be
# compiled into the final application (i.e. it allow listing some of
# the assets catalog content conditionally).
#
# The target requires that the files are located in a .xcassets bundle
# in the repository (or generated via a script). This ensures that the
# assets catalog is correctly visible in Xcode (though as usual, using
# Xcode to make change to the .xcassets bundle will not be reflected in
# the final build unless the target is updated in the gn configuration).
#
# Arguments
#
#     sources:
#       required, list of strings, path to the files contained in the
#       .xcassets bundle; this may contains a sub-set of the files on
#       disk if some assets are only compiled conditionally
#
#     catalog:
#       required, string, path to the .xcassets bundle; all path in
#       sources must be relative to this path or the compilation will
#       fail
#
# Example
#
#     bundle_data_xcassets("assets") {
#       catalog = "Assets.xcassets"
#       sources = [
#         "Assets.xcassets/Color.colorset/Contents.json",
#         "Assets.xcassets/Contents.json",
#       ]
#       if (includes_images) {
#         sources += [
#           "Assets.xcassets/Image.imageset/Contents.json",
#           "Assets.xcassets/Image.imageset/Image.svg",
#         ]
#       }
#     }
template("bundle_data_xcassets") {
  assert(defined(invoker.sources), "sources must be defined for $target_name")
  assert(defined(invoker.catalog), "catalog must be defined for $target_name")

  _target_name = target_name
  _target_zip = target_name + "_zip"

  zip(_target_zip) {
    _catalog_name = get_path_info(invoker.catalog, "file")
    _catalog_path = get_path_info(invoker.catalog, "dir")

    inputs = invoker.sources
    output = "$target_out_dir/$target_name/$_catalog_name"
    base_dir = _catalog_path
  }

  bundle_data(_target_name) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "sources",
                             "deps",
                             "public_deps",
                           ])

    public_deps = [ ":$_target_zip" ]
    sources = get_target_outputs(":$_target_zip")
    outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
  }
}

# Template to package a shared library into an iOS framework bundle.
#
# By default, the bundle target this template generates does not link the
# resulting framework into anything that depends on it. If a dependency wants
# a link-time (as well as build-time) dependency on the framework bundle,
# depend against "$target_name+link". If only the build-time dependency is
# required (e.g., for copying into another bundle), then use "$target_name".
#
# Arguments
#
#     output_name:
#         (optional) string, name of the generated framework without the
#         .framework suffix. If omitted, defaults to target_name.
#
#     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.
#
#   enable_code_signing
#       (optional) boolean, control whether code signing is enabled or not,
#       default to ios_enable_code_signing if not defined.
#
#   transparent
#       (optional) boolean, whether the bundle is "transparent"; defaults to
#       "false" if omitted; a bundle is considered "transparent" if it does
#       not package the "bundle_data" deps but forward them to all targets
#       the depend on it (unless the "bundle_data" target sets "product_type"
#       to "com.apple.product-type.framework").
#
# This template provides two targets for the resulting framework bundle. The
# link-time behavior varies depending on which of the two targets below is
# added as a dependency:
#   - $target_name only adds a build-time dependency. Targets that depend on
#     it will not link against the framework.
#   - $target_name+link adds a build-time and link-time dependency. Targets
#     that depend on it will link against the framework.
#
# The build-time-only dependency is used for when a target needs to use the
# framework either only for resources, or because the target loads it at run-
# time, via dlopen() or NSBundle. The link-time dependency will cause the
# dependee to have the framework loaded by dyld at launch.
#
# Example of build-time only dependency:
#
#     framework_bundle("CoreTeleportation") {
#       sources = [ ... ]
#     }
#
#     bundle_data("core_teleportation_bundle_data") {
#       deps = [ ":CoreTeleportation" ]
#       sources = [ "$root_out_dir/CoreTeleportation.framework" ]
#       outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
#     }
#
#     app_bundle("GoatTeleporter") {
#       sources = [ ... ]
#       deps = [
#         ":core_teleportation_bundle_data",
#       ]
#     }
#
# The GoatTeleporter.app will not directly link against
# CoreTeleportation.framework, but it will be included in the bundle's
# Frameworks directory.
#
# Example of link-time dependency:
#
#     framework_bundle("CoreTeleportation") {
#       sources = [ ... ]
#       ldflags = [
#         "-install_name",
#         "@executable_path/../Frameworks/$target_name.framework"
#       ]
#     }
#
#     bundle_data("core_teleportation_bundle_data") {
#       deps = [ ":CoreTeleportation+link" ]
#       sources = [ "$root_out_dir/CoreTeleportation.framework" ]
#       outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
#     }
#
#     app_bundle("GoatTeleporter") {
#       sources = [ ... ]
#       deps = [
#         ":core_teleportation_bundle_data",
#       ]
#     }
#
# Note that the framework is still copied to the app's bundle, but dyld will
# load this library when the app is launched because it uses the "+link"
# target as a dependency. This also requires that the framework set its
# install_name so that dyld can locate it.
#
# 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
  }

  _product_type = "com.apple.product-type.framework"
  _has_public_headers =
      defined(invoker.public_headers) && invoker.public_headers != []

  _shared_library_target = _target_name + "_shared_library"
  _link_target_name = _target_name + "+link"

  if (_has_public_headers) {
    _default_toolchain_target_gen_dir =
        get_label_info("$_target_name", "target_gen_dir")

    _framework_headers_target = _target_name + "_framework_headers"

    _headers_map_config = _target_name + "_headers_map"
    _header_map_filename =
        "$_default_toolchain_target_gen_dir/$_output_name.headers.hmap"
    config(_headers_map_config) {
      visibility = [
        ":${_shared_library_target}",
        ":${_target_name}_signed_bundle",
      ]
      include_dirs = [ _header_map_filename ]
    }
  }

  _framework_headers_config = _target_name + "_framework_headers_config"
  config(_framework_headers_config) {
    framework_dirs = [ root_out_dir ]
  }

  _framework_public_config = _target_name + "_public_config"
  config(_framework_public_config) {
    configs = [ ":$_framework_headers_config" ]
    frameworks = [ "$_output_name.framework" ]
  }

  shared_library(_shared_library_target) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "bundle_deps",
                             "bundle_deps_filter",
                             "data_deps",
                             "enable_code_signing",
                             "extra_substitutions",
                             "info_plist",
                             "info_plist_target",
                             "output_name",
                             "public_configs",
                             "transparent",
                             "visibility",
                           ])

    visibility = [ ":${_target_name}_signed_bundle" ]

    if (!defined(ldflags)) {
      ldflags = []
    }
    ldflags +=
        [ "-Wl,-install_name,@rpath/$_output_name.framework/$_output_name" ]

    if (_has_public_headers) {
      configs += [ ":$_headers_map_config" ]

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

    output_extension = ""
    output_name = _output_name
    output_prefix_override = true
    output_dir = target_out_dir
  }

  if (_has_public_headers) {
    _public_headers = invoker.public_headers

    _framework_root_dir = "$root_out_dir/$_output_name.framework"
    if (target_environment == "simulator" || target_environment == "device") {
      _framework_contents_dir = _framework_root_dir
    } else if (target_environment == "catalyst") {
      _framework_contents_dir = "$_framework_root_dir/Versions/A"
    }

    _compile_headers_map_target = _target_name + "_compile_headers_map"
    action(_compile_headers_map_target) {
      visibility = [ ":$_framework_headers_target" ]
      forward_variables_from(invoker,
                             [
                               "deps",
                               "public_deps",
                               "testonly",
                             ])
      script = "//build/config/apple/write_framework_hmap.py"
      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".
      sources = []
      if (defined(invoker.sources)) {
        foreach(_source, invoker.sources) {
          if (get_path_info(_source, "extension") == "h") {
            sources += [ _source ]
          }
        }
      }

      args = [
               rebase_path(_header_map_filename, root_build_dir),
               rebase_path(_framework_root_dir, root_build_dir),
             ] + rebase_path(sources, root_build_dir)
    }

    _create_module_map_target = _target_name + "_module_map"
    action(_create_module_map_target) {
      visibility = [ ":$_framework_headers_target" ]
      script = "//build/config/apple/write_framework_modulemap.py"
      outputs = [ "$_framework_contents_dir/Modules/module.modulemap" ]
      args = [
        _output_name,
        rebase_path("$_framework_contents_dir/Modules", root_build_dir),
      ]
    }

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

      # Do not use forward_variables_from for "public_deps" as
      # we do not want to forward those dependencies.
      if (defined(invoker.public_deps)) {
        if (!defined(deps)) {
          deps = []
        }
        deps += invoker.public_deps
      }
    }

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

  # Bundle identifier should respect rfc1034, so replace "_" with "-".
  _bundle_identifier =
      "$ios_app_bundle_id_prefix." + string_replace(_output_name, "_", "-")

  _info_plist_target = _target_name + "_info_plist"
  _info_plist_bundle = _target_name + "_info_plist_bundle"
  ios_info_plist(_info_plist_target) {
    visibility = [ ":$_info_plist_bundle" ]
    executable_name = _output_name
    forward_variables_from(invoker,
                           [
                             "info_plist",
                             "info_plist_target",
                           ])

    extra_substitutions = [ "BUNDLE_IDENTIFIER=$_bundle_identifier" ]
    if (defined(invoker.extra_substitutions)) {
      extra_substitutions += invoker.extra_substitutions
    }
  }

  bundle_data(_info_plist_bundle) {
    visibility = [ ":${_target_name}_signed_bundle" ]
    forward_variables_from(invoker, [ "testonly" ])
    sources = get_target_outputs(":$_info_plist_target")
    public_deps = [ ":$_info_plist_target" ]
    product_type = _product_type

    if (target_environment != "catalyst") {
      outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
    } else {
      outputs = [ "{{bundle_resources_dir}}/Info.plist" ]
    }
  }

  create_signed_bundle(_target_name + "_signed_bundle") {
    forward_variables_from(invoker,
                           [
                             "bundle_deps",
                             "bundle_deps_filter",
                             "data_deps",
                             "deps",
                             "enable_code_signing",
                             "public_configs",
                             "public_deps",
                             "testonly",
                             "transparent",
                             "visibility",
                           ])

    product_type = _product_type
    bundle_extension = ".framework"

    output_name = _output_name
    bundle_binary_target = ":$_shared_library_target"
    bundle_binary_output = _output_name

    has_public_headers = _has_public_headers

    # Framework do not have entitlements nor mobileprovision because they use
    # the one from the bundle using them (.app or .appex) as they are just
    # dynamic library with shared code.
    disable_entitlements = true
    disable_embedded_mobileprovision = true

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

  group(_target_name) {
    forward_variables_from(invoker,
                           [
                             "public_configs",
                             "public_deps",
                             "testonly",
                             "visibility",
                           ])
    if (!defined(public_deps)) {
      public_deps = []
    }
    public_deps += [ ":${_target_name}_signed_bundle" ]

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

  group(_link_target_name) {
    forward_variables_from(invoker,
                           [
                             "public_configs",
                             "public_deps",
                             "testonly",
                             "visibility",
                           ])
    if (!defined(public_deps)) {
      public_deps = []
    }
    public_deps += [ ":$_target_name" ]

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

  bundle_data(_target_name + "+bundle") {
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    public_deps = [ ":$_target_name" ]
    sources = [ "$root_out_dir/$_output_name.framework" ]
    outputs = [ "{{bundle_contents_dir}}/Frameworks/$_output_name.framework" ]
  }
}

set_defaults("ios_framework_bundle") {
  configs = default_shared_library_configs
}

# Template to build a xctest bundle that contains a loadable module for iOS.
#
# Arguments
#
#   deps:
#       list of labels to depends on, these values are used to create the
#       loadable module.
#
#   product_type
#       string, product type for the generated Xcode project, use
#       "com.apple.product-type.bundle.unit-test" for unit test and
#       "com.apple.product-type.bundle.ui-testing" for UI testing.
#
#   host_target:
#       string, name of the target that depends on the generated bundle, this
#       value is used to restrict visibilities.
#
#   xcode_test_application_name:
#       string, name of the test application for Xcode unit or ui test target.
#
#   output_name
#       (optional) string, name of the generated application, if omitted,
#       defaults to the target_name.
#
# This template defines two targets, one named "${target_name}" is the xctest
# bundle, and the other named "${target_name}_bundle" is a bundle_data that
# wraps the xctest bundle and that only the "${host_target}" can depend on.
#
template("ios_xctest_bundle") {
  assert(defined(invoker.deps), "deps must be defined for $target_name")
  assert(defined(invoker.product_type),
         "product_type must be defined for $target_name")
  assert(invoker.product_type == _ios_xcode_xctest_bundle_id ||
             invoker.product_type == _ios_xcode_xcuitest_bundle_id,
         "product_type defined for $target_name is invalid.")
  assert(defined(invoker.host_target),
         "host_target must be defined for $target_name")
  assert(defined(invoker.xcode_test_application_name),
         "xcode_test_application_name must be defined for $target_name")

  _target_name = target_name
  _output_name = target_name

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

  _loadable_module_target = _target_name + "_loadable_module"

  loadable_module(_loadable_module_target) {
    forward_variables_from(invoker,
                           "*",
                           [
                             "bundle_deps",
                             "bundle_deps_filter",
                             "host_target",
                             "output_dir",
                             "output_extension",
                             "output_name",
                             "output_prefix_override",
                             "product_type",
                             "testonly",
                             "visibility",
                             "xcode_test_application_name",
                             "xcode_test_application_output_name",
                             "xctest_bundle_principal_class",
                           ])

    testonly = true
    visibility = [ ":$_target_name" ]

    configs += [ "//build/config/ios:xctest_config" ]

    output_dir = target_out_dir
    output_name = _output_name
    output_prefix_override = true
    output_extension = ""
  }

  _info_plist_target = _target_name + "_info_plist"
  _info_plist_bundle = _target_name + "_info_plist_bundle"

  # Bundle identifier should respect rfc1034, so replace "_" with "-".
  _bundle_identifier = "$ios_app_bundle_id_prefix.chrome." +
                       string_replace(_output_name, "_", "-")

  ios_info_plist(_info_plist_target) {
    testonly = true
    visibility = [ ":$_info_plist_bundle" ]

    info_plist = "//build/config/ios/Module-Info.plist"
    executable_name = _output_name

    if (defined(invoker.xctest_bundle_principal_class)) {
      _principal_class = invoker.xctest_bundle_principal_class
    } else {
      # Fall back to a reasonable default value.
      _principal_class = "NSObject"
    }
    extra_substitutions = [
      "XCTEST_BUNDLE_PRINCIPAL_CLASS=${_principal_class}",
      "BUNDLE_IDENTIFIER=$_bundle_identifier",
    ]
  }

  bundle_data(_info_plist_bundle) {
    testonly = true
    visibility = [ ":$_target_name" ]

    public_deps = [ ":$_info_plist_target" ]

    sources = get_target_outputs(":$_info_plist_target")
    outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
  }

  _xctest_bundle = _target_name + "_bundle"
  create_signed_bundle(_target_name) {
    forward_variables_from(invoker,
                           [
                             "bundle_deps",
                             "bundle_deps_filter",
                             "bundle_id",
                             "data_deps",
                             "enable_code_signing",
                             "product_type",
                             "transparent",
                             "xcode_test_application_name",
                           ])

    testonly = true
    visibility = [ ":$_xctest_bundle" ]

    bundle_extension = ".xctest"

    output_name = _output_name
    bundle_binary_target = ":$_loadable_module_target"
    bundle_binary_output = _output_name

    xcode_extra_attributes = {
      IPHONEOS_DEPLOYMENT_TARGET = ios_deployment_target
      PRODUCT_BUNDLE_IDENTIFIER = _bundle_identifier
      CODE_SIGNING_REQUIRED = "NO"
      CODE_SIGNING_ALLOWED = "NO"
      CODE_SIGN_IDENTITY = ""
      DONT_GENERATE_INFOPLIST_FILE = "YES"

      # For XCUITest, Xcode requires specifying the host application name
      # via the TEST_TARGET_NAME attribute.
      if (invoker.product_type == _ios_xcode_xcuitest_bundle_id) {
        TEST_TARGET_NAME = invoker.xcode_test_application_name
      }

      # For XCTest, Xcode requires specifying the host application path via
      # both BUNDLE_LOADER and TEST_HOST attributes.
      if (invoker.product_type == _ios_xcode_xctest_bundle_id) {
        _xcode_app_name = invoker.xcode_test_application_name
        if (defined(invoker.xcode_test_application_output_name)) {
          _xcode_app_name = invoker.xcode_test_application_output_name
        }

        BUNDLE_LOADER = "\$(TEST_HOST)"
        TEST_HOST = "\$(BUILT_PRODUCTS_DIR)/" +
                    "${_xcode_app_name}.app/${_xcode_app_name}"
      }
    }

    deps = [ ":$_info_plist_bundle" ]
  }

  bundle_data(_xctest_bundle) {
    forward_variables_from(invoker, [ "host_target" ])

    testonly = true
    visibility = [ ":$host_target" ]

    public_deps = [ ":$_target_name" ]
    sources = [ "$root_out_dir/$_output_name.xctest" ]
    outputs = [ "{{bundle_contents_dir}}/PlugIns/$_output_name.xctest" ]
  }
}

set_defaults("ios_xctest_bundle") {
  configs = default_shared_library_configs
}

# For Chrome on iOS we want to run XCTests for all our build configurations
# (Debug, Release, ...). In addition, the symbols visibility is configured to
# private by default. To simplify testing with those constraints, our tests are
# compiled in the TEST_HOST target instead of the .xctest bundle.
template("ios_xctest_test") {
  _target_name = target_name
  _output_name = target_name
  if (defined(invoker.output_name)) {
    _output_name = invoker.output_name
  }

  _xctest_target = _target_name + "_module"
  _xctest_output = _output_name + "_module"

  _host_target = _target_name
  _host_output = _output_name

  # Allow invokers to specify their own target for the xctest module, but
  # fall back to a default (empty) module otherwise.
  if (defined(invoker.xctest_module_target)) {
    _xctest_module_target = invoker.xctest_module_target
  } else {
    _xctest_module_target_name = _xctest_target + "shell_source"
    _xctest_module_target = ":$_xctest_module_target_name"
    source_set(_xctest_module_target_name) {
      sources = [ "//build/config/ios/xctest_shell.mm" ]

      configs += [ "//build/config/ios:xctest_config" ]
    }
  }

  ios_xctest_bundle(_xctest_target) {
    forward_variables_from(invoker, [ "data_deps" ])
    output_name = _xctest_output
    product_type = _ios_xcode_xctest_bundle_id
    host_target = _host_target

    # TODO(crbug.com/40120290) The change in output name results in a mismatch
    # between this value and the ios_app_bundle target name. To mitigate, this
    # has been modified to _host_target. output_name is set to _host_output
    # to mitigate the naming.
    xcode_test_application_name = _host_target
    xcode_test_application_output_name = _host_output

    deps = [ _xctest_module_target ]
  }

  ios_app_bundle(_host_target) {
    forward_variables_from(invoker, "*", [ "testonly" ])

    testonly = true
    output_name = _host_output
    configs += [ "//build/config/ios:xctest_config" ]

    if (!defined(invoker.info_plist) && !defined(invoker.info_plist_target)) {
      info_plist = "//build/config/ios/Host-Info.plist"
    }

    # Xcode needs the following frameworks installed in the application (and
    # signed) for the XCTest to run, so install them using
    # extra_system_frameworks.
    extra_system_frameworks = [
      "$ios_sdk_platform_path/Developer/Library/Frameworks/XCTest.framework",
      "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework",
      "$ios_sdk_platform_path/Developer/usr/lib/libXCTestBundleInject.dylib",
    ]

    # Xcode 13 now depends on XCTestCore. To keep things future proof, copy over
    # everything that Xcode copies.
    if (xcode_version_int >= 1300) {
      extra_system_frameworks += [
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestCore.framework",
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUIAutomation.framework",
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUnit.framework",
        "$ios_sdk_platform_path/Developer/usr/lib/libXCTestSwiftSupport.dylib",
      ]
    }

    # XCTestSupport framework is required as of Xcode 14.3 or later.
    if (xcode_version_int >= 1430) {
      extra_system_frameworks += [ "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestSupport.framework" ]
    }

    _xctest_bundle = _xctest_target + "_bundle"
    if (!defined(bundle_deps)) {
      bundle_deps = []
    }
    bundle_deps += [ ":$_xctest_bundle" ]
  }
}

set_defaults("ios_xctest_test") {
  configs = default_executable_configs
}

# Template to build a xcuitest test runner bundle.
#
# Xcode requires a test runner application with a copy of the XCTest dynamic
# library bundle in it for the XCUITest to run. The test runner bundle is created
# by copying the system bundle XCTRunner.app from Xcode SDK with the plist file
# being properly tweaked, and a xctest and it needs to be code signed in order
# to run on devices.
#
# Arguments
#
#   xctest_bundle
#       string, name of the dependent xctest bundle target.
#
#   output_name
#       (optional) string, name of the generated application, if omitted,
#       defaults to the target_name.
#
template("ios_xcuitest_test_runner_bundle") {
  assert(defined(invoker.xctest_bundle),
         "xctest_bundle must be defined for $target_name")

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

  # Bundle identifier should respect rfc1034, so replace "_" with "-".
  _bundle_identifier = "$ios_app_bundle_id_prefix.chrome." +
                       string_replace(_output_name, "_", "-")

  _xctrunner_path =
      "$ios_sdk_platform_path/Developer/Library/Xcode/Agents/XCTRunner.app"

  _info_plist_merge_plist = _target_name + "_info_plist_merge_plist"
  _info_plist_target = _target_name + "_info_plist"
  _info_plist_bundle = _target_name + "_info_plist_bundle"

  action(_info_plist_merge_plist) {
    testonly = true
    script = "//build/apple/plist_util.py"

    sources = [
      "$_xctrunner_path/Info.plist",

      # NOTE: The XCTRunnerAddition+Info.plist must come after the Info.plist
      # because it overrides the values under "CFBundleIdentifier" and
      # "CFBundleName".
      "//build/config/ios/resources/XCTRunnerAddition+Info.plist",
    ]

    _output_name = "$target_gen_dir/${_target_name}_merged.plist"
    outputs = [ _output_name ]
    args = [
             "merge",
             "-f=xml1",
             "-x=$xcode_version",
             "-o=" + rebase_path(_output_name, root_build_dir),
           ] + rebase_path(sources, root_build_dir)

    if (ios_use_xcode_symlinks) {
      deps = [ "//build/config/ios:copy_xctrunner_app" ]
    }
  }

  ios_info_plist(_info_plist_target) {
    testonly = true
    visibility = [ ":$_info_plist_bundle" ]

    executable_name = _output_name
    info_plist_target = ":$_info_plist_merge_plist"
    extra_substitutions = [ "BUNDLE_IDENTIFIER=$_bundle_identifier" ]
  }

  bundle_data(_info_plist_bundle) {
    testonly = true
    visibility = [ ":$_target_name" ]

    public_deps = [ ":$_info_plist_target" ]

    sources = get_target_outputs(":$_info_plist_target")
    outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
  }

  _pkginfo_bundle = _target_name + "_pkginfo_bundle"
  bundle_data(_pkginfo_bundle) {
    testonly = true
    visibility = [ ":$_target_name" ]

    sources = [ "$_xctrunner_path/PkgInfo" ]

    outputs = [ "{{bundle_contents_dir}}/PkgInfo" ]

    if (ios_use_xcode_symlinks) {
      public_deps = [ "//build/config/ios:copy_xctrunner_app" ]
    }
  }

  _xctest_bundle = invoker.xctest_bundle
  create_signed_bundle(_target_name) {
    testonly = true

    bundle_binary_target = "//build/config/ios:xctest_runner_without_arm64e"
    bundle_binary_output = "XCTRunner"
    bundle_extension = ".app"
    product_type = _ios_xcode_app_bundle_id

    output_name = _output_name

    # Xcode needs the following frameworks installed in the application
    # (and signed) for the XCUITest to run, so install them using
    # extra_system_frameworks.
    extra_system_frameworks = [
      "$ios_sdk_platform_path/Developer/Library/Frameworks/XCTest.framework",
      "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTAutomationSupport.framework",
    ]

    # Xcode 13 now depends on XCTestCore. To keep things future proof, copy over
    # everything that Xcode copies.
    if (xcode_version_int >= 1300) {
      extra_system_frameworks += [
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestCore.framework",
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUIAutomation.framework",
        "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCUnit.framework",
        "$ios_sdk_platform_path/Developer/usr/lib/libXCTestSwiftSupport.dylib",
      ]
    }

    # XCTestSupport framework is required as of Xcode 14.3 or later.
    if (xcode_version_int >= 1430) {
      extra_system_frameworks += [ "$ios_sdk_platform_path/Developer/Library/PrivateFrameworks/XCTestSupport.framework" ]
    }

    bundle_deps = []
    if (defined(invoker.bundle_deps)) {
      bundle_deps += invoker.bundle_deps
    }
    bundle_deps += [
      ":$_info_plist_bundle",
      ":$_pkginfo_bundle",
      ":$_xctest_bundle",
    ]
  }
}

# Template to build a XCUITest that consists of two parts: the test runner
# application bundle and the xctest dynamic library.
#
# Arguments
#
#   deps:
#       list of labels to depends on, these values are used to create the
#       xctest dynamic library.
#
#   xcode_test_application_name:
#       string, name of the test application for the ui test target.
#
#   runner_only_bundle_deps:
#       list of labels of bundle target to include in the runner and
#       exclude from the test module (the use case is a framework bundle
#       that is used by the test module and thus needs to be packaged in
#       the runner application bundle)
#
# This template defines two targets, one named "${target_name}_module" is the
# xctest dynamic library, and the other named "${target_name}_runner" is the
# test runner application bundle.
#
template("ios_xcuitest_test") {
  assert(defined(invoker.deps), "deps must be defined for $target_name")
  assert(defined(invoker.xcode_test_application_name),
         "xcode_test_application_name must be defined for $target_name")

  _xcuitest_target = target_name
  if (defined(invoker.output_name)) {
    _xcuitest_target = invoker.output_name
  }

  _xcuitest_runner_target = _xcuitest_target + "_runner"
  _xcuitest_module_target = _xcuitest_target + "_module"

  group(target_name) {
    testonly = true

    deps = [ ":$_xcuitest_runner_target" ]
  }

  _xcuitest_module_output = _xcuitest_target
  ios_xctest_bundle(_xcuitest_module_target) {
    forward_variables_from(invoker,
                           [
                             "bundle_deps",
                             "data_deps",
                             "deps",
                             "xcode_test_application_name",
                             "xctest_bundle_principal_class",
                           ])

    product_type = _ios_xcode_xcuitest_bundle_id
    host_target = _xcuitest_runner_target
    output_name = _xcuitest_module_output

    if (defined(invoker.runner_only_bundle_deps)) {
      bundle_deps_filter = invoker.runner_only_bundle_deps
    }
  }

  _xcuitest_runner_output = _xcuitest_target + "-Runner"
  ios_xcuitest_test_runner_bundle(_xcuitest_runner_target) {
    output_name = _xcuitest_runner_output
    xctest_bundle = _xcuitest_module_target + "_bundle"

    if (defined(invoker.runner_only_bundle_deps)) {
      if (!defined(bundle_deps)) {
        bundle_deps = []
      }
      bundle_deps += invoker.runner_only_bundle_deps
    }
  }
}

set_defaults("ios_xcuitest_test") {
  configs = default_executable_configs
}