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

# TODO(brettw) Use "gcc_toolchain.gni" like the Linux toolchains. This requires
# some enhancements since the commands on Mac are slightly different than on
# Linux.

import("//build/config/apple/arch.gni")
import("//build/config/apple/symbols.gni")
import("//build/config/clang/clang.gni")
import("//build/config/compiler/compiler.gni")
import("//build/config/coverage/coverage.gni")
import("//build/config/rust.gni")
import("//build/toolchain/cc_wrapper.gni")
import("//build/toolchain/rbe.gni")
import("//build/toolchain/toolchain.gni")
import("//build_overrides/build.gni")

# TODO(crbug.com/40869822): This import is required to detect whether the
# build is for the catalyst environment in order to disable the hermetic
# swift compiler (as it does not include support for catalyst). Remove it
# once the support is available.
if (is_ios) {
  import("//build/config/apple/mobile_config.gni")
  import("//build/config/ios/ios_sdk.gni")
}

assert((target_os == "ios" && host_os == "mac") || host_os != "win")

declare_args() {
  # This controls whether whole module optimization is enabled when building
  # Swift modules. If enabled, the compiler will compile the module as one
  # unit, generating just one single object file. Otherwise, it will generate
  # one object file per .swift file. If unspecified, will default to "true"
  # for official builds, and "false" for all other builds.
  swift_whole_module_optimization = -1

  # If true, the intermediate build products of swift module compilation will
  # be kept after the invocation of the swiftc compiler. Otherwise they will
  # deleted between each invocation.
  swift_keep_intermediate_files = false

  # If unspecified, will use the toolchain downloaded via deps.
  swift_toolchain_path = -1
}

# TODO(crbug.com/40869822): Remove this and replace with `build_with_chromium`
# once the support for catalyst is available in the hermetic swift compiler.
_can_use_hermetic_swift =
    build_with_chromium && is_ios && target_environment != "catalyst"

if (swift_toolchain_path == -1) {
  # TODO(crbug.com/40915887) The custom swift toolchain not does currently work
  # with Xcode 15 beta 1.
  if (_can_use_hermetic_swift && !(is_ios && xcode_version_int >= 1500)) {
    # Version of the hermetic compiler. Needs to be updated when a new version
    # of the compiler is rolled to ensure that all outputs are regenerated. It
    # must be kept in sync with the `version` of `third_party/swift-toolchain`
    # in
    # //DEPS.
    swiftc_version = "swift-5.8-release"

    # Use the hermetic swift toolchain.
    swift_toolchain_path = "//third_party/swift-toolchain/"
  } else {
    swift_toolchain_path = ""
  }
}

if (swift_whole_module_optimization == -1) {
  swift_whole_module_optimization = is_official_build
}

# When implementing tools using Python scripts, a TOOL_VERSION=N env
# variable is placed in front of the command. The N should be incremented
# whenever the script is changed, so that the build system rebuilds all
# edges that utilize the script. Ideally this should be changed to use
# proper input-dirty checking, but that could be expensive. Instead, use a
# script to get the tool scripts' modification time to use as the version.
# This won't cause a re-generation of GN files when the tool script changes
# but it will cause edges to be marked as dirty if the ninja files are
# regenerated. See https://crbug.com/619083 for details. A proper fix
# would be to have inputs to tools (https://crbug.com/621119).
tool_versions = exec_script(
        "get_tool_mtime.py",
        rebase_path(
            [
              "//build/toolchain/apple/compile_xcassets.py",
              "//build/toolchain/apple/filter_libtool.py",
              "//build/toolchain/apple/linker_driver.py",
              "//build/toolchain/apple/swift_const_gather_protocols.json",
              "//build/toolchain/apple/swiftc.py",
            ],
            root_build_dir),
        "trim scope")

# Shared toolchain definition. Invocations should set current_os to set the
# build args in this definition. This is titled "single_apple_toolchain"
# because it makes exactly one toolchain. Callers will normally want to
# invoke instead "apple_toolchain" which makes an additional toolchain for
# Rust targets that are build-time artificts such as proc macros.
template("single_apple_toolchain") {
  toolchain(target_name) {
    # When invoking this toolchain not as the default one, these args will be
    # passed to the build. They are ignored when this is the default toolchain.
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")

    toolchain_args = {
      # Populate toolchain args from the invoker.
      forward_variables_from(invoker.toolchain_args, "*")

      # The host toolchain value computed by the default toolchain's setup
      # needs to be passed through unchanged to all secondary toolchains to
      # ensure that it's always the same, regardless of the values that may be
      # set on those toolchains.
      host_toolchain = host_toolchain
    }

    # bin_path is only used in some builds.
    not_needed(invoker, [ "bin_path" ])

    # When the invoker has explicitly overridden cc_wrapper in the
    # toolchain args, use those values, otherwise default to the global one.
    # This works because the only reasonable override that toolchains might
    # supply for these values are to force-disable them.
    if (defined(toolchain_args.use_reclient)) {
      toolchain_uses_reclient = toolchain_args.use_reclient
    } else {
      toolchain_uses_reclient = use_reclient
    }
    if (defined(toolchain_args.cc_wrapper)) {
      toolchain_cc_wrapper = toolchain_args.cc_wrapper
    } else {
      toolchain_cc_wrapper = cc_wrapper
    }
    assert(!(toolchain_cc_wrapper != "" && toolchain_uses_reclient),
           "re-client and cc_wrapper can't be used together.")

    if (defined(toolchain_args.use_lld)) {
      toolchain_uses_lld = toolchain_args.use_lld
    } else {
      toolchain_uses_lld = use_lld
    }

    # The value of all global variables (such as `is_component_build`) is the
    # one from the default toolchain when evaluating a secondary toolchain
    # (see https://crbug.com/gn/286). This mean that the value may change when
    # evaluating target/configs in the new toolchain if the variable default
    # value depends on variable set in `toolchain_args`.
    #
    # For this reason, "ios" needs to override `is_component_build` as its
    # default value depends on `current_os`. Use the overridden value if it
    # is set in `toolchain_args`.
    if (defined(toolchain_args.is_component_build)) {
      toolchain_is_component_build = toolchain_args.is_component_build
    } else {
      toolchain_is_component_build = is_component_build
    }

    prefix = rebase_path("$clang_base_path/bin/", root_build_dir)
    _cc = "${prefix}clang"
    _cxx = "${prefix}clang++"

    swiftmodule_switch = "-Wl,-add_ast_path,"

    # Compute the compiler prefix.
    if (toolchain_uses_reclient) {
      if (defined(toolchain_args.reclient_cc_cfg_file)) {
        toolchain_reclient_cc_cfg_file = toolchain_args.reclient_cc_cfg_file
      } else {
        toolchain_reclient_cc_cfg_file = reclient_cc_cfg_file
      }

      # C/C++ (clang) rewrapper prefix to use when use_reclient is true.
      compiler_prefix = "${reclient_bin_dir}/rewrapper -cfg=${toolchain_reclient_cc_cfg_file}${rbe_bug_326584510_missing_inputs} -exec_root=${rbe_exec_root} "
    } else if (toolchain_cc_wrapper != "") {
      compiler_prefix = toolchain_cc_wrapper + " "
    } else {
      compiler_prefix = ""
    }

    cc = compiler_prefix + _cc
    cxx = compiler_prefix + _cxx
    ld = _cxx

    # Set the explicit search path for clang++ so it uses the right linker
    # binary.
    bin_flag = ""
    if (!toolchain_uses_lld) {
      bin_flag = "-B " + invoker.bin_path
    }

    coverage_wrapper = ""
    if (defined(toolchain_args.coverage_instrumentation_input_file)) {
      toolchain_coverage_instrumentation_input_file =
          toolchain_args.coverage_instrumentation_input_file
    } else {
      toolchain_coverage_instrumentation_input_file =
          coverage_instrumentation_input_file
    }
    _use_clang_coverage_wrapper =
        toolchain_coverage_instrumentation_input_file != ""
    if (_use_clang_coverage_wrapper) {
      _coverage_wrapper =
          rebase_path("//build/toolchain/clang_code_coverage_wrapper.py",
                      root_build_dir) + " --files-to-instrument=" +
          rebase_path(toolchain_coverage_instrumentation_input_file,
                      root_build_dir) + " --target-os=" + target_os
      coverage_wrapper = "$python_path ${_coverage_wrapper} "
    }

    linker_driver_env = "TOOL_VERSION=${tool_versions.linker_driver}"
    linker_driver =
        rebase_path("//build/toolchain/apple/linker_driver.py", root_build_dir)
    linker_driver_args = "-Wcrl,driver,$ld"

    # Specify an explicit path for the strip binary.
    _strippath = "${prefix}llvm-strip"
    _installnametoolpath = "${prefix}llvm-install-name-tool"
    linker_driver_args += " -Wcrl,strippath,${_strippath} -Wcrl,installnametoolpath,${_installnametoolpath}"
    _enable_dsyms = enable_dsyms
    _save_unstripped_output = save_unstripped_output

    # Make these apply to all tools below.
    lib_switch = "-l"
    lib_dir_switch = "-L"

    # Object files go in this directory. Use label_name instead of
    # target_output_name since labels will generally have no spaces and will be
    # unique in the directory.
    object_subdir = "{{target_out_dir}}/{{label_name}}"

    # If dSYMs are enabled, this flag will be added to the link tools.
    if (_enable_dsyms) {
      dsym_switch = " -Wcrl,dsym,{{root_out_dir}} "
      dsym_switch += "-Wcrl,dsymutilpath," +
                     rebase_path("//tools/clang/dsymutil/bin/dsymutil",
                                 root_build_dir) + " "

      # All the toolchains are defined in the context of the default toolchain,
      # this means that the global variable `current_cpu` is not correct, it
      # will be the one for the default toolchain.
      #
      # This means that the code cannot use the variable `dsym_arch` defined in
      # build/config/apple/symbols.gni since it is defined from `current_cpu`.
      #
      # Instead declare another local variable `_dsym_arch` based from the
      # toolchain `toolchain_args.current_cpu` which will be used when targets
      # are evaluated in the context of the current toolchain.
      _archs_mapping_os = archs_mapping[toolchain_args.current_os]
      if (defined(_archs_mapping_os[toolchain_args.current_cpu])) {
        _archs_mapping_os_cpu = _archs_mapping_os[toolchain_args.current_cpu]
        _dsym_arch = _archs_mapping_os_cpu.dsym_arch
      } else {
        not_needed([ "_archs_mapping_os" ])
        assert("cpu unsupported ${toolchain_args.current_cpu}")
      }

      dsym_output_dir =
          "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.dSYM"
      dsym_output = [
        "$dsym_output_dir/Contents/Info.plist",
        "$dsym_output_dir/Contents/Resources/DWARF/" +
            "{{target_output_name}}{{output_extension}}",
        "$dsym_output_dir/Contents/Resources/Relocations/" +
            "$_dsym_arch/{{target_output_name}}{{output_extension}}.yml",
      ]
    } else {
      dsym_switch = ""
    }

    if (_save_unstripped_output) {
      _unstripped_output = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.unstripped"
    }

    if (system_headers_in_deps) {
      md = "-MD"
    } else {
      md = "-MMD"
    }

    if (toolchain_has_rust) {
      rustc_wrapper_path =
          rebase_path("//build/rust/gni_impl/rustc_wrapper.py", root_build_dir)
      rustc_wrapper = "\"$python_path\" \"$rustc_wrapper_path\""

      rustc_env_and_flags_path =
          "{{target_out_dir}}/{{label_name}}.rustflags.json"
      rustc_wrapper += " --dump-rustc-env-and-flags $rustc_env_and_flags_path"

      rustc_rsp_path = "{{target_out_dir}}/{{label_name}}.rsp"
      rustc_wrapper += " --rsp=$rustc_rsp_path"

      if (!defined(rust_compiler_prefix)) {
        rust_compiler_prefix = ""
      }
      rust_sysroot_relative = rebase_path(rust_sysroot, root_build_dir)
      rustc_bin = "$rust_sysroot_relative/bin/rustc"
      rustc = "$rust_compiler_prefix${rustc_bin}"
      rustc_wrapper += " --rustc=$rustc"

      tool("rust_staticlib") {
        libname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$libname.d"

        default_output_extension = ".a"
        output_prefix = "lib"

        # Static libraries go in the target out directory by default so we can
        # generate different targets with the same name and not have them
        # collide.
        default_output_dir = "{{target_out_dir}}"
        description = "RUST(STATICLIB) {{output}}"
        outputs = [
          libname,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        # TODO(danakj): When `!toolchain_uses_lld` do we need to specify a path
        # to libtool like the "alink" rule?

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link -o $libname -- -Clinker=\"$_cxx\" {{source}} LDFLAGS $bin_flag RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }

      tool("rust_rlib") {
        # We must always prefix with `lib` even if the library already starts
        # with that prefix or else our stdlib is unable to find libc.rlib (or
        # actually liblibc.rlib).
        rlibname =
            "{{output_dir}}/lib{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$rlibname.d"
        rmetaname = "{{output_dir}}/lib{{target_output_name}}.rmeta"

        default_output_extension = ".rlib"

        # This is prefixed unconditionally in `rlibname`.
        # output_prefix = "lib"

        # Static libraries go in the target out directory by default so we can
        # generate different targets with the same name and not have them
        # collide.
        default_output_dir = "{{target_out_dir}}"
        description = "RUST {{output}}"
        outputs = [
          rlibname,
          rmetaname,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link,metadata=$rmetaname -o $rlibname -- -Clinker=\"$_cxx\" {{source}} LDFLAGS RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }

      tool("rust_bin") {
        exename = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$exename.d"
        pool = "//build/toolchain:link_pool($default_toolchain)"

        # TODO(danakj): solink can generate TOC files for re-exporting library
        # symbols, and we should do the same here.

        default_output_dir = "{{root_out_dir}}"
        description = "RUST(BIN) {{output}}"
        outputs = [
          exename,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        # TODO(danakj): Support dsym_switch like C++ targets.
        # link_command += dsym_switch
        # if (_enable_dsyms) {
        #   outputs += dsym_output
        # }
        # if (_save_unstripped_output) {
        #   outputs += [ _unstripped_output ]
        # }

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$linker_driver_env $rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link -o $exename -- -Clinker=\"$linker_driver\" {{source}} LDFLAGS $linker_driver_args $bin_flag {{ldflags}} RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }

      tool("rust_cdylib") {
        dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$dllname.d"
        pool = "//build/toolchain:link_pool($default_toolchain)"

        # TODO(danakj): solink can generate TOC files for re-exporting library
        # symbols, and we should do the same here.

        default_output_extension = ".dylib"
        output_prefix = "lib"
        default_output_dir = "{{root_out_dir}}"
        description = "RUST(CDYLIB) {{output}}"
        outputs = [
          dllname,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        # TODO(danakj): Support dsym_switch like C++ targets.
        # link_command += dsym_switch
        # if (_enable_dsyms) {
        #   outputs += dsym_output
        # }
        # if (_save_unstripped_output) {
        #   outputs += [ _unstripped_output ]
        # }

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$linker_driver_env $rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link -o $dllname -- -Clinker=\"$linker_driver\" {{source}} LDFLAGS $linker_driver_args $bin_flag {{ldflags}} RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }

      tool("rust_dylib") {
        dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$dllname.d"
        pool = "//build/toolchain:link_pool($default_toolchain)"
        rmetaname = "{{output_dir}}/lib{{target_output_name}}.rmeta"

        # TODO(danakj): solink can generate TOC files for re-exporting library
        # symbols, and we should do the same here.

        default_output_extension = ".dylib"
        output_prefix = "lib"
        default_output_dir = "{{root_out_dir}}"
        description = "RUST(DYLIB) {{output}}"
        outputs = [
          dllname,
          rmetaname,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        # TODO(danakj): Support dsym_switch like C++ targets.
        # link_command += dsym_switch
        # if (_enable_dsyms) {
        #   outputs += dsym_output
        # }
        # if (_save_unstripped_output) {
        #   outputs += [ _unstripped_output ]
        # }

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$linker_driver_env $rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link,metadata=$rmetaname -o $dllname -- -Clinker=\"$linker_driver\" {{source}} LDFLAGS $linker_driver_args {{ldflags}} RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }

      tool("rust_macro") {
        dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
        rspfile = rustc_rsp_path
        depfile = "$dllname.d"
        pool = "//build/toolchain:link_pool($default_toolchain)"

        # TODO(danakj): solink can generate TOC files for re-exporting library
        # symbols, and we should do the same here.

        default_output_extension = ".dylib"
        output_prefix = "lib"
        default_output_dir = "{{root_out_dir}}"
        description = "RUST(MACRO) {{output}}"
        outputs = [
          dllname,
          rustc_env_and_flags_path,
          "${rspfile}.rust",
        ]

        # TODO(danakj): Support dsym_switch like C++ targets.
        # link_command += dsym_switch
        # if (_enable_dsyms) {
        #   outputs += dsym_output
        # }
        # if (_save_unstripped_output) {
        #   outputs += [ _unstripped_output ]
        # }

        rspfile_content =
            "$rustc_common_args {{rustdeps}} {{externs}} SOURCES {{sources}}"
        command = "$rustc_wrapper --depfile=$depfile --emit=dep-info=$depfile,link -o $dllname -- -Clinker=\"${_cxx}\" {{source}} LDFLAGS $bin_flag {{ldflags}} RUSTENV {{rustenv}}"
        rust_sysroot = rust_sysroot_relative
        inputs = rustc_wrapper_inputs
      }
    }

    tool("cc") {
      output = "$object_subdir/{{source_name_part}}.o"
      depfile = "$output.d"
      precompiled_header_type = "gcc"

      # {{output}} may contain additional outputs, so use $output instead of
      # {{output}} in command.
      command = "$coverage_wrapper$cc $md -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -fmodule-name=\"{{cc_module_name}}_Private\"  -c {{source}} -o $output"
      depsformat = "gcc"
      description = "CC {{output}}"
      outputs = [ output ]
    }

    tool("cxx") {
      output = "$object_subdir/{{source_name_part}}.o"
      depfile = "$output.d"
      precompiled_header_type = "gcc"

      # {{output}} may contain additional outputs, so use $output instead of
      # {{output}} in command.
      command = "$coverage_wrapper$cxx $md -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} {{module_deps}} -fmodule-name=\"{{cc_module_name}}_Private\" -c {{source}} -o $output"
      depsformat = "gcc"
      description = "CXX {{output}}"
      outputs = [ output ]
    }

    tool("cxx_module") {
      depfile = "{{output}}.d"
      precompiled_header_type = "gcc"

      # Module file doesn't need coverage instrumentation because module files
      # represent interfaces rather than implementations.
      command = "$cxx -MD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} {{module_deps_no_self}} -fmodule-name=\"{{cc_module_name}}\" -c -x c++ -Xclang -emit-module {{source}} -o {{output}}"
      depsformat = "gcc"
      description = "CXX_MODULE {{output}}"
      outputs = [ "$object_subdir/{{source_name_part}}.pcm" ]
    }

    tool("asm") {
      # For GCC we can just use the C compiler to compile assembly.
      depfile = "{{output}}.d"
      command = "$cc $md -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      description = "ASM {{output}}"
      outputs = [ "$object_subdir/{{source_name_part}}.o" ]
    }

    tool("objc") {
      output = "$object_subdir/{{source_name_part}}.o"
      depfile = "$output.d"
      precompiled_header_type = "gcc"

      # {{output}} may contain additional outputs, so use $output instead of
      # {{output}} in command.
      command = "$coverage_wrapper$cc $md -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objc}} -c {{source}} -o $output"
      depsformat = "gcc"
      description = "OBJC {{output}}"
      outputs = [ output ]
    }

    tool("objcxx") {
      output = "$object_subdir/{{source_name_part}}.o"
      depfile = "$output.d"
      precompiled_header_type = "gcc"

      # {{output}} may contain additional outputs, so use $output instead of
      # {{output}} in command.
      command = "$coverage_wrapper$cxx $md -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_objcc}} -c {{source}} -o $output"
      depsformat = "gcc"
      description = "OBJCXX {{output}}"
      outputs = [ output ]
    }

    tool("alink") {
      rspfile = "{{output}}.rsp"
      rspfile_content = "{{inputs}}"

      if (!toolchain_uses_lld) {
        script = rebase_path("//build/toolchain/apple/filter_libtool.py",
                             root_build_dir)

        # Specify explicit path for libtool.
        libtool = invoker.bin_path + "libtool"

        # Apple's libtool fails if the response file is empty (e.g., for empty
        # targets). We work around this by writing a dummy archive with the ar
        # magic number.
        command = "rm -f {{output}} && if [ -s \"$rspfile\" ]; then TOOL_VERSION=${tool_versions.filter_libtool} $python_path $script $libtool -static -D {{arflags}} -o {{output}} @$rspfile; else printf '!<arch>\n' > {{output}}; fi"
        description = "LIBTOOL-STATIC {{output}}"
      } else {
        ar = "${prefix}llvm-ar"
        command = "\"$ar\" {{arflags}} -r -c -s -D {{output}} @$rspfile"

        # Remove the output file first so that ar doesn't try to modify the
        # existing file.
        command = "rm -f {{output}} && $command"
        description = "AR {{output}}"
      }
      outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
      default_output_dir = "{{target_out_dir}}"
      default_output_extension = ".a"
      output_prefix = "lib"
    }

    tool("solink") {
      # E.g. "./libfoo.dylib":
      dylib = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
      rspfile = dylib + ".rsp"
      pool = "//build/toolchain:link_pool($default_toolchain)"

      # These variables are not built into GN but are helpers that implement
      # (1) linking to produce a .dylib, (2) extracting the symbols from that
      # file to a temporary file, (3) if the temporary file has differences from
      # the existing .TOC file, overwrite it, otherwise, don't change it.
      #
      # As a special case, if the library reexports symbols from other dynamic
      # libraries, we always update the .TOC and skip the temporary file and
      # diffing steps, since that library always needs to be re-linked.
      tocname = dylib + ".TOC"

      # Use explicit paths to binaries. The binaries present on the default
      # search path in /usr/bin are thin wrappers around xcrun, which requires a
      # full CommandLineTools or Xcode install, and still may not choose the
      # appropriate binary if there are multiple installs.
      nm = "${prefix}llvm-nm"
      otool = "${prefix}llvm-otool"

      link_command = "$linker_driver_env $linker_driver"
      link_command += " -Wcrl,otoolpath,$otool -Wcrl,nmpath,$nm"
      link_command += " -Wcrl,tocname,\"$tocname\""
      link_command += " $linker_driver_args -shared "
      if (toolchain_is_component_build) {
        link_command += " -Wl,-install_name,@rpath/\"{{target_output_name}}{{output_extension}}\" "
      }
      link_command += dsym_switch
      link_command += bin_flag
      link_command += " {{ldflags}} -o \"$dylib\" \"@$rspfile\" {{rlibs}}"

      command = "$link_command"

      rspfile_content =
          "{{inputs}} {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}}"

      description = "SOLINK {{output}}"

      # Use this for {{output_extension}} expansions unless a target manually
      # overrides it (in which case {{output_extension}} will be what the target
      # specifies).
      default_output_dir = "{{root_out_dir}}"
      default_output_extension = ".dylib"

      output_prefix = "lib"

      # Since the above commands only updates the .TOC file when it changes, ask
      # Ninja to check if the timestamp actually changed to know if downstream
      # dependencies should be recompiled.
      restat = true

      # Tell GN about the output files. It will link to the dylib but use the
      # tocname for dependency management.
      outputs = [
        dylib,
        tocname,
      ]
      link_output = dylib
      depend_output = tocname

      if (_enable_dsyms) {
        outputs += dsym_output
      }
      if (_save_unstripped_output) {
        outputs += [ _unstripped_output ]
      }
    }

    tool("solink_module") {
      # E.g. "./libfoo.so":
      sofile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
      rspfile = sofile + ".rsp"
      pool = "//build/toolchain:link_pool($default_toolchain)"

      link_command = "$linker_driver_env $linker_driver $linker_driver_args -bundle $bin_flag {{ldflags}} -o \"$sofile\" \"@$rspfile\" {{rlibs}}"
      link_command += dsym_switch
      command = link_command

      rspfile_content =
          "{{inputs}} {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}}"

      description = "SOLINK_MODULE {{output}}"

      # Use this for {{output_extension}} expansions unless a target manually
      # overrides it (in which case {{output_extension}} will be what the target
      # specifies).
      default_output_dir = "{{root_out_dir}}"
      default_output_extension = ".so"

      outputs = [ sofile ]

      if (_enable_dsyms) {
        outputs += dsym_output
      }
      if (_save_unstripped_output) {
        outputs += [ _unstripped_output ]
      }
    }

    tool("link") {
      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
      rspfile = "$outfile.rsp"
      pool = "//build/toolchain:link_pool($default_toolchain)"

      command = "$linker_driver_env $linker_driver $linker_driver_args $bin_flag $dsym_switch {{ldflags}} -o \"$outfile\" \"@$rspfile\" {{rlibs}}"
      description = "LINK $outfile"
      rspfile_content =
          "{{inputs}} {{frameworks}} {{swiftmodules}} {{solibs}} {{libs}}"
      outputs = [ outfile ]

      if (_enable_dsyms) {
        outputs += dsym_output
      }
      if (_save_unstripped_output) {
        outputs += [ _unstripped_output ]
      }

      default_output_dir = "{{root_out_dir}}"
    }

    # These two are really entirely generic, but have to be repeated in
    # each toolchain because GN doesn't allow a template to be used here.
    # See //build/toolchain/toolchain.gni for details.
    tool("stamp") {
      command = stamp_command
      description = stamp_description
    }
    tool("copy") {
      command = copy_command
      description = copy_description
    }

    tool("copy_bundle_data") {
      # copy_command use hardlink if possible but this does not work with
      # directories. Also when running EG2 tests from Xcode, Xcode tries to
      # copy some files into the application bundle which fails if source
      # and destination are hardlinked together.
      #
      # Instead use clonefile to copy the files which is as efficient as
      # hardlink but ensure the file have distinct metadata (thus avoid the
      # error with ditto, see https://crbug.com/1042182).
      if (host_os == "mac") {
        command = "rm -rf {{output}} && /bin/cp -Rc {{source}} {{output}}"
      } else {
        command = "rm -rf {{output}} && /bin/cp -Rld {{source}} {{output}}"
      }
      description = "COPY_BUNDLE_DATA {{source}} {{output}}"
      pool = "//build/toolchain/apple:bundle_pool($default_toolchain)"
    }

    # Swift is only used on iOS, not macOS. We want to minimize the number
    # of Xcode-based tools used by the macOS toolchain, so we intentionally
    # disallow future uses of Swift on macOS. https://crbug.com/965663.
    if (toolchain_args.current_os == "ios") {
      tool("swift") {
        _tool = rebase_path("//build/toolchain/apple/swiftc.py", root_build_dir)

        depfile = "{{target_out_dir}}/{{module_name}}.d"
        depsformat = "gcc"

        _header_path = "{{target_gen_dir}}/{{target_output_name}}.h"
        _output_dir = "{{target_out_dir}}/{{label_name}}"

        outputs = [
          _header_path,
          "$_output_dir/{{module_name}}-OutputFileMap.json",
          "$_output_dir/{{module_name}}.SwiftFileList",
          "$_output_dir/{{module_name}}.abi.json",
          "$_output_dir/{{module_name}}.d",
          "$_output_dir/{{module_name}}.dia",
          "$_output_dir/{{module_name}}.swiftdoc",
          "$_output_dir/{{module_name}}.swiftmodule",
          "$_output_dir/{{module_name}}.swiftsourceinfo",
        ]

        partial_outputs = [ "$_output_dir/{{source_name_part}}.o" ]

        # The list of outputs and partial_outputs change whether the whole
        # module optimization is enabled or not.
        if (swift_whole_module_optimization) {
          outputs += [ "$_output_dir/{{module_name}}.swiftconstvalues" ]

          # This seems broken in Xcode 26 beta 1. The OutputFileMap.json
          # confirms the file should be there, but when
          # whole-module-optimization is enabled it's not there.
          if (xcode_version_int < 2600) {
            outputs += [ "$_output_dir/{{module_name}}.swiftdeps" ]
          }
        } else {
          outputs += [ "$_output_dir/{{module_name}}.priors" ]
          partial_outputs += [
            "$_output_dir/{{source_name_part}}.d",
            "$_output_dir/{{source_name_part}}.dia",
            "$_output_dir/{{source_name_part}}.swiftdeps",
            "$_output_dir/{{source_name_part}}.swiftconstvalues",
          ]
        }

        # If configured to keep the intermediate build files, pass the flag
        # to the script and inform gn of the stamp file only (as the other
        # files have names that cannot be predicted without invoking swiftc).
        if (swift_keep_intermediate_files) {
          _derived_data_dir = "$_output_dir/DerivedData"
          outputs += [ "$_derived_data_dir/{{module_name}}.stamp" ]
        }

        # Additional flags passed to the wrapper script but that are only
        # set conditionally.
        _extra_flags = ""

        # Environment variables passed to the wrapper script. Considered
        # part of the command-line by ninja (and thus cause the build to
        # be considered dirty if they change) without having to be parsed
        # by the script.
        _env_vars = "TOOL_VERSION=${tool_versions.swiftc} " +
                    "JSON_VERSION=${tool_versions.swift_const_gather_protocols}"

        # Include the version of the compiler on the command-line. This causes
        # `ninja` to consider all the compilation output to be dirty when the
        # version changes.
        if (defined(swiftc_version)) {
          _env_vars += " SWIFTC_VERSION=$swiftc_version"
        }

        # Include the version of Xcode on the command-line (if specified via
        # toolchain_args). This causes `ninja` to consider all the compilation
        # outputs to be dirty when the version change.
        #
        # This is required because sometimes module dependency changes between
        # different version of Xcode (e.g. when moving from Xcode 14 beta 6 to
        # Xcode 14 RC). If the swiftmodule are not rebuilt when the version
        # changes, they may encode dependency on now non-existing frameworks
        # causing linker failures ultimately.
        if (defined(toolchain_args.xcode_build)) {
          _env_vars += " XCODE_VERSION=${toolchain_args.xcode_build}"
        }

        if (invoker.sdk_developer_dir != "") {
          _env_vars += " DEVELOPER_DIR=${toolchain_args.sdk_developer_dir}"
        }

        if (swift_toolchain_path != "") {
          _extra_flags += " --swift-toolchain-path " +
                          rebase_path(swift_toolchain_path, root_build_dir)
        }

        if (swift_whole_module_optimization) {
          _extra_flags += " --whole-module-optimization"
        }

        if (swift_keep_intermediate_files) {
          _extra_flags += " --swift-keep-intermediate-files" +
                          " --derived-data-dir $_derived_data_dir"
        }

        # The Swift compiler assumes that the generated header will be used by
        # Objective-C code compiled with module support enabled (-fmodules).
        #
        # As Chromium code is compiled without support for modules (i.e. the
        # code is compiled without `-fmodules`), the dependent modules are not
        # imported from the generated header, which causes compilation failure
        # if the client code does not first import the required modules (see
        # https://crbug.com/1316061 for details).
        #
        # Secondly, the Swift compiler uses absolute path when importing other
        # modules' generated headers or Objective-C bridging headers. This
        # causes issues with the distributed compiler (i.e. reclient or siso)
        # as they want all paths to be relative to the source directory.
        #
        # Instruct swiftc.py to rewrite the generated header use relative
        # import and to use the old #import syntax for system frameworks.
        _extra_flags += " --fix-generated-header"

        _src_dir = rebase_path("//", root_build_dir)
        _gen_dir = rebase_path(root_gen_dir, root_build_dir)
        _const_gather_protocols_file = rebase_path(
                "//build/toolchain/apple/swift_const_gather_protocols.json",
                root_build_dir)

        command =
            "$_env_vars $python_path $_tool --module-name {{module_name}} " +
            "--header-path $_header_path --target-out-dir $_output_dir " +
            "--const-gather-protocols-file $_const_gather_protocols_file " +
            "--depfile-path $depfile --src-dir $_src_dir --gen-dir $_gen_dir " +
            "--bridge-header {{bridge_header}} {{include_dirs}} " +
            "{{module_dirs}} {{swiftflags}} {{inputs}}$_extra_flags"

        description = "SWIFT $_output_dir/{{module_name}}.swiftmodule"
      }
    }

    # xcassets are only used on iOS, not macOS. We want to minimize the number
    # of Xcode-based tools used by the macOS toolchain, so we intentionally
    # disallow future uses of xcassets on macOS. https://crbug.com/965663.
    if (toolchain_args.current_os == "ios") {
      tool("compile_xcassets") {
        _tool = rebase_path("//build/toolchain/apple/compile_xcassets.py",
                            root_build_dir)

        _env_vars = "TOOL_VERSION=${tool_versions.compile_xcassets}"
        if (invoker.sdk_developer_dir != "") {
          _env_vars += " DEVELOPER_DIR=${toolchain_args.sdk_developer_dir}"
        }

        command = "$_env_vars $python_path $_tool " +
                  "-O '${toolchain_args.current_os}' " +
                  "-p '${invoker.target_platform}' " +
                  "-e '${invoker.target_environment}' " +
                  "-t '${invoker.deployment_target}' " +
                  "-T '{{bundle_product_type}}' " +
                  "-P '{{bundle_partial_info_plist}}' " +
                  " {{xcasset_compiler_flags}} " + "-o {{output}} {{inputs}}"

        description = "COMPILE_XCASSETS {{output}}"
        pool = "//build/toolchain/apple:bundle_pool($default_toolchain)"
      }
    }

    tool("action") {
      pool = "//build/toolchain:action_pool($default_toolchain)"
    }
  }
}

# Make an additional toolchain which is used for making tools that are run
# on the host machine as part of the build process (such as proc macros
# and Cargo build scripts). This toolchain uses the prebuilt stdlib that
# comes with the compiler, so it doesn't have to wait for the stdlib to be
# built before building other stuff. And this ensures its proc macro
# outputs have the right ABI to be loaded by the compiler, and it can be
# used to compile build scripts that are part of the stdlib that is built
# for the default toolchain.
template("apple_rust_host_build_tools_toolchain") {
  single_apple_toolchain(target_name) {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker,
                           "*",
                           TESTONLY_AND_VISIBILITY + [ "toolchain_args" ])
    toolchain_args = {
      # Populate toolchain args from the invoker.
      forward_variables_from(invoker.toolchain_args, "*")
      toolchain_for_rust_host_build_tools = true

      # The host build tools are static release builds to make the Chromium
      # build faster. They do not need PGO etc, so no official builds.
      is_debug = false
      is_component_build = false
      is_official_build = false
      use_clang_coverage = false
      use_sanitizer_coverage = false
      generate_linker_map = false
      use_thin_lto = false
    }
  }
}

# If PartitionAlloc is part of the build (even as a transitive dependency), then
# it replaces the system allocator. If this toolchain is used, that will be
# overridden and the system allocator will be used regardless. This is important
# in some third-party binaries outside of Chrome.
template("apple_system_allocator_toolchain") {
  single_apple_toolchain(target_name) {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker,
                           "*",
                           TESTONLY_AND_VISIBILITY + [ "toolchain_args" ])
    toolchain_args = {
      # Populate toolchain args from the invoker.
      forward_variables_from(invoker.toolchain_args, "*")
      toolchain_allows_use_partition_alloc_as_malloc = false

      # Disable component build so that we can copy the exes to the
      # root_build_dir and support the default_toolchain redirection on Windows.
      # See also the comment in //build/symlink.gni.
      is_component_build = false

      # Only one toolchain can be configured with MSAN support with our current
      # GN setup, or they all try to make the instrumented libraries and
      # collide.
      is_msan = false
    }
  }
}

# Makes an Apple toolchain for the target, and an equivalent toolchain with the
# prebuilt Rust stdlib for building proc macros (and other for-build-use
# artifacts).
template("apple_toolchain") {
  single_apple_toolchain(target_name) {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)

    # No need to forward visibility and test_only as they apply to targets not
    # toolchains, but presubmit checks require that we explicitly exclude them
  }

  apple_rust_host_build_tools_toolchain(
      "${target_name}_for_rust_host_build_tools") {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
  }

  apple_system_allocator_toolchain(
      "${target_name}_host_with_system_allocator") {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
  }
  apple_system_allocator_toolchain("${target_name}_with_system_allocator") {
    assert(defined(invoker.toolchain_args),
           "Toolchains must declare toolchain_args")
    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
  }
}
