# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import("//gn/perfetto.gni")
import("//gn/standalone/android.gni")
import("//gn/standalone/wasm.gni")
import("llvm.gni")
import("msvc.gni")

# This file is evaluated once, within the context of the default toolchain,
# which is the target toolchain.
# Note: This means that is_android=true even on a mac when cross-compiling for
# Android.
assert(current_os == target_os && current_cpu == target_cpu,
       "Assumptions on current_xxx in this file have been violated")

declare_args() {
  cc_wrapper = ""

  # These apply to both target and host toolchains.
  extra_cflags = ""
  extra_cxxflags = ""
  extra_ldflags = ""

  # These apply only to the target toolchain.
  extra_target_cflags = ""
  extra_target_cxxflags = ""
  extra_target_ldflags = ""

  # These apply only to the host toolchain.
  extra_host_cflags = ""
  extra_host_cxxflags = ""
  extra_host_ldflags = ""
}

# First of all determine the host toolchain. The user can override this by:
# 1. setting ar/cc/cxx vars in args.gn.
# 2. setting is_system_compiler=true in args.gn and the env vars AR/CC/CXX.
#    This is used by OSSFuzz and CrOS ebuilds.

_llvm_strip_wrapper = rebase_path("llvm-strip.py", root_build_dir)

declare_args() {
  sysroot = ""
  gcc_toolchain = ""
  ar = "ar"
  linker = ""  # If left to "" uses the default linker (Linux:bfd, Mac:ld64).
  strip = ""

  if (is_linux_host) {
    if (linux_llvm_objcopy != "") {
      # If we are using the hermetic clang toolchain llvm-objcopy from there as
      # it works with Linux-arm cross toolchains. The |_llvm_strip_wrapper| is
      # to set argv0 as llvm-strip. llvm-objcopy's frontend works differently
      # when invoked as llvm-strip, pretending to be just 'strip'.
      strip = "${_llvm_strip_wrapper} ${linux_llvm_objcopy}"
    } else {
      strip = "strip"
    }
  } else if (is_mac_host) {
    strip = "strip -x"
  } else if (is_freebsd_host) {
    ar = "llvm-ar"
  }

  if (is_clang) {
    if (is_linux_host && !is_system_compiler) {
      cc = linux_clang_bin
      cxx = linux_clangxx_bin
      linker = linux_clang_linker
    } else if (is_win_host && !is_system_compiler) {
      cc = win_clang_bin
      cxx = win_clangxx_bin
      linker = win_clang_linker
    } else {
      cc = "clang"
      cxx = "clang++"
      linker = ""
    }
  } else if (is_win) {  # MSVC
    cc = "${win_msvc_bin_dir}\\cl.exe"
    cxx = "${win_msvc_bin_dir}\\cl.exe"
    linker = "${win_msvc_bin_dir}\\link.exe"
  } else {  # GCC
    cc = "gcc"
    cxx = "g++"
  }
}

# Then determine the target toolchain.

declare_args() {
  _default_target_sysroot = ""
  target_gcc_toolchain = ""

  # |target_triplet| is the variable that the user can set via GN args. The user
  # doesn't have to necessarily set it though. In most cases we can infer it
  # by looking at target_os and target_cpu.
  # |_target_triplet| is the final argument passed to the toolchain.
  if (target_triplet != "") {
    assert(is_cross_compiling)

    # If the user provides the target_triplet in gn args, respect that.
    # Otherwise guess it looking at the target os and cpu variables.
    _target_triplet = target_triplet
  } else if (!is_cross_compiling) {
    _target_triplet = ""
  } else if (target_os == "mac" && target_cpu == "x64") {
    _target_triplet = "x86_64-apple-darwin"
  } else if (target_os == "mac" && target_cpu == "x86") {
    _target_triplet = "i686-apple-darwin"
  } else if (target_os == "mac" && target_cpu == "arm64") {
    _target_triplet = "aarch64-apple-darwin"
  } else if (target_os == "linux" && target_cpu == "arm64") {
    _target_triplet = "aarch64-linux-gnu"
    _default_target_sysroot =
        rebase_path("//buildtools/debian_sid_arm64-sysroot", root_build_dir)
  } else if (target_os == "linux" && target_cpu == "arm") {
    _target_triplet = "arm-linux-gnueabihf"
    _default_target_sysroot =
        rebase_path("//buildtools/debian_sid_arm-sysroot", root_build_dir)
  } else if (target_os == "linux" && target_cpu == "riscv64") {
    _target_triplet = "riscv64-linux-gnu"
  } else if (target_os == "linux" && target_cpu == "x64") {
    _target_triplet = "x86_64-linux-gnu"
  } else if (target_os == "linux" && target_cpu == "x86") {
    # Chrome's packaging of clang uses i386 for x86 libs, so an i686 triplet
    # fails to find the necessary sanitizer archives.
    if (is_hermetic_clang && (is_asan || is_lsan)) {
      _target_triplet = "i386-linux-gnu"
    } else {
      _target_triplet = "i686-linux-gnu"
    }
  } else if (target_os == "android" && target_cpu == "arm64") {
    _target_triplet = "aarch64-linux-android"
  } else if (target_os == "android" && target_cpu == "arm") {
    _target_triplet = "arm-linux-androideabi"
  } else if (target_os == "android" && target_cpu == "x86") {
    _target_triplet = "i686-linux-android"
  } else if (target_os == "android" && target_cpu == "x64") {
    _target_triplet = "x86_64-linux-android"
  } else if (target_os == "qnx" && target_cpu == "arm64") {
    _target_triplet = "ntoaarch64"
  } else if (target_os == "qnx" && target_cpu == "x64") {
    _target_triplet = "ntox86_64"
  } else {
    assert(
        false,
        "Cannot guess the target triplet from the target_os and target_cpu combination. Please set the target_triplet GN arg explicitly. See https://clang.llvm.org/docs/CrossCompilation.html#target-triple")
  }
}

declare_args() {
  if (sysroot != "") {
    # If the user specifies a sysroot, use that for both host and target.
    target_sysroot = sysroot
  } else if (is_qnx) {
    # Do not set any sysroot for QNX.
    target_sysroot = ""
  } else {
    # If no explicit sysroot has been set, use the guessed sysroot from the ones
    # pulled by //tools/install-build-deps (only for Linux).
    target_sysroot = _default_target_sysroot
  }
}

declare_args() {
  target_strip = ""
  target_linker = ""

  if (!is_cross_compiling || is_perfetto_build_generator ||
      is_system_compiler) {
    target_ar = ar
    target_cc = cc
    target_cxx = cxx
    target_linker = linker
    target_strip = strip
  } else {
    target_ar = "ar"
    if (is_android) {
      target_ar = "$android_llvm_dir/bin/llvm-ar"
      target_cc = "$android_llvm_dir/bin/clang"
      target_cxx = "$android_llvm_dir/bin/clang++"
      target_linker = "$android_llvm_dir/bin/ld.lld"
      target_strip = "$android_llvm_dir/bin/llvm-strip"
    } else if (is_qnx) {
      target_ar = "${_target_triplet}-ar"
      target_linker = "${_target_triplet}-ld"
      target_strip = "${_target_triplet}-strip"
      if (target_cpu == "arm64") {
        target_cc = "qcc -Vgcc_${_target_triplet}le"
        target_cxx = "q++ -Vgcc_${_target_triplet}le_cxx"
      } else {
        target_cc = "qcc -Vgcc_${_target_triplet}"
        target_cxx = "q++ -Vgcc_${_target_triplet}_cxx"
      }
    } else {
      assert(_target_triplet != "",
             "target_triplet must be non-empty when cross-compiling")
      target_strip = strip
      if (is_clang) {
        if (is_linux_host) {
          target_cc = "${linux_clang_bin} --target=${_target_triplet}"
          target_cxx = "${linux_clangxx_bin} --target=${_target_triplet}"
          target_linker = "${linux_clang_linker} --target=${_target_triplet}"
        } else {
          target_cc = "clang --target=${_target_triplet}"
          target_cxx = "clang++ --target=${_target_triplet}"
        }
      } else {  # GCC
        target_ar = "${_target_triplet}-ar"
        target_cc = "${_target_triplet}-gcc"
        target_cxx = "${_target_triplet}-g++"
        target_strip = "${_target_triplet}-strip"
      }
    }
  }
}

template("gcc_like_toolchain") {
  toolchain(target_name) {
    ar = invoker.ar
    cc = invoker.cc
    cxx = invoker.cxx
    lib_switch = "-l"
    lib_dir_switch = "-L"
    ld_arg = ""
    external_cflags = ""
    external_cxxflags = ""
    external_ldflags = ""
    strip = ""
    if (defined(invoker.linker) && invoker.linker != "") {
      _invoker_linker = invoker.linker
      ld_arg = "-fuse-ld=$_invoker_linker"
    }
    if (defined(invoker.sysroot) && invoker.sysroot != "") {
      _invoker_sysroot = invoker.sysroot
      cc = "$cc --sysroot=$_invoker_sysroot"
      cxx = "$cxx --sysroot=$_invoker_sysroot"
    }
    if (defined(invoker.gcc_toolchain) && invoker.gcc_toolchain != "") {
      assert(is_clang, "gcc_toolchain can be used only when using clang")
      _invoker_gcc_toolchain = invoker.gcc_toolchain
      ld_arg = "$ld_arg --gcc-toolchain=$_invoker_gcc_toolchain"
    }
    if (defined(invoker.external_cflags)) {
      external_cflags = invoker.external_cflags
    }
    if (defined(invoker.external_cxxflags)) {
      external_cxxflags = invoker.external_cxxflags
    }
    if (defined(invoker.external_ldflags)) {
      external_ldflags = invoker.external_ldflags
    }
    if (defined(invoker.strip)) {
      strip = invoker.strip
    }

    tool("cc") {
      depfile = "{{output}}.d"
      if (is_qnx) {
        depfile_args = "-Wp,-MMD,$depfile -Wp,-MF,$depfile"
      } else {
        depfile_args = "-MMD -MF $depfile"
      }
      command = "$cc_wrapper $cc $depfile_args {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} ${external_cflags} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      outputs =
          [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
      description = "compile {{source}}"
    }

    tool("cxx") {
      depfile = "{{output}}.d"
      if (is_qnx) {
        depfile_args = "-Wp,-MMD,$depfile -Wp,-MF,$depfile"
      } else {
        depfile_args = "-MMD -MF $depfile"
      }
      command = "$cc_wrapper $cxx $depfile_args {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}  ${external_cflags} ${external_cxxflags} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      outputs =
          [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
      description = "compile {{source}}"
    }

    tool("asm") {
      depfile = "{{output}}.d"
      if (is_qnx) {
        depfile_args = "-Wp,-MMD,$depfile -Wp,-MF,$depfile"
      } else {
        depfile_args = "-MMD -MF $depfile"
      }
      command = "$cc_wrapper $cc $depfile_args {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      outputs =
          [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
      description = "assemble {{source}}"
    }

    tool("alink") {
      rspfile = "{{output}}.rsp"
      if (is_mac && ar != "suppress_unused_ar_variable_warning") {
        rspfile_content = "{{inputs_newline}}"
        command = "rm -f {{output}} && libtool -static {{arflags}} -o {{output}} -filelist $rspfile"
      } else {
        rspfile_content = "{{inputs}}"
        command = "rm -f {{output}} && $ar rcsD {{output}} @$rspfile"
      }
      outputs =
          [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ]
      default_output_extension = ".a"
      output_prefix = "lib"
      description = "link {{output}}"
    }

    tool("solink") {
      soname = "{{target_output_name}}{{output_extension}}"
      unstripped_so = "{{root_out_dir}}/$soname"
      rspfile = "$unstripped_so.rsp"
      rspfile_content = "{{inputs}}"
      rpath = "-Wl,-soname,$soname"
      if (is_mac) {
        rpath = "-Wl,-install_name,@rpath/$soname"
      }
      command = "$cc_wrapper $cxx $ld_arg -shared {{ldflags}} ${external_ldflags} @$rspfile {{solibs}} {{libs}} $rpath -o $unstripped_so"
      outputs = [ unstripped_so ]
      output_prefix = "lib"
      default_output_extension = ".so"
      description = "link $unstripped_so"
      if (strip != "") {
        stripped_so = "{{root_out_dir}}/stripped/$soname"
        outputs += [ stripped_so ]
        command += " && $strip -o $stripped_so $unstripped_so"
      }
    }

    tool("link") {
      unstripped_exe =
          "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
      rspfile = "$unstripped_exe.rsp"
      rspfile_content = "{{inputs}}"
      command = "$cc_wrapper $cxx $ld_arg {{ldflags}} ${external_ldflags} @$rspfile {{solibs}} {{libs}} -o $unstripped_exe"
      outputs = [ unstripped_exe ]
      description = "link $unstripped_exe"
      if (strip != "") {
        stripped_exe = "{{root_out_dir}}/stripped/{{target_output_name}}{{output_extension}}"
        outputs += [ stripped_exe ]
        command += " && $strip -o $stripped_exe $unstripped_exe"
      }
    }

    tool("stamp") {
      command = "touch {{output}}"
      description = "stamp {{output}}"
    }

    tool("copy") {
      command = "cp -af {{source}} {{output}}"
      description = "COPY {{source}} {{output}}"
    }

    toolchain_args = {
      current_cpu = invoker.cpu
      current_os = invoker.os
    }
  }
}

gcc_like_toolchain("gcc_like") {
  cpu = current_cpu
  os = current_os
  ar = target_ar
  cc = target_cc
  cxx = target_cxx
  linker = target_linker
  strip = target_strip
  sysroot = target_sysroot
  gcc_toolchain = target_gcc_toolchain
  external_cflags = string_join(" ",
                                [
                                  extra_cflags,
                                  extra_target_cflags,
                                ])
  external_cxxflags = string_join(" ",
                                  [
                                    extra_cxxflags,
                                    extra_target_cxxflags,
                                  ])
  external_ldflags = string_join(" ",
                                 [
                                   extra_ldflags,
                                   extra_target_ldflags,
                                 ])
}

gcc_like_toolchain("gcc_like_host") {
  cpu = host_cpu
  os = host_os
  ar = ar
  cc = cc
  cxx = cxx
  linker = linker
  strip = strip
  sysroot = sysroot
  gcc_toolchain = gcc_toolchain
  external_cflags = string_join(" ",
                                [
                                  extra_cflags,
                                  extra_host_cflags,
                                ])
  external_cxxflags = string_join(" ",
                                  [
                                    extra_cxxflags,
                                    extra_host_cxxflags,
                                  ])
  external_ldflags = string_join(" ",
                                 [
                                   extra_ldflags,
                                   extra_host_ldflags,
                                 ])
}

# The 32 and 64 bit wasm toolchains are configured identically, but differ in
# the fact that is_wasm_memory64=true (in gn_vars.gni, which looks at the
# current_toolchain) conditions cflags and ldflags. We actually need different
# toolchains because the same .cc files will be build twice for each wasm
# bitness, and they need different output paths.
template("wasm_toolchain") {
  gcc_like_toolchain(target_name) {
    forward_variables_from(invoker, "*")

    # emsdk_dir and em_config are defined in wasm.gni.
    cpu = host_cpu
    os = host_os
    ar = "$emsdk_dir/emscripten/emar --em-config $em_config"
    cc = "$emsdk_dir/emscripten/emcc --em-config $em_config"
    cxx = "$emsdk_dir/emscripten/em++ --em-config $em_config"
    strip = ""
  }
}

# Istantiates both wasm toolchains.
wasm_toolchain("wasm") {
}
wasm_toolchain("wasm_memory64") {
}

# This is used both for MSVC anc clang-cl. clang-cl cmdline interface pretends
# to be MSVC's cl.exe.
toolchain("msvc") {
  lib_switch = ""
  lib_dir_switch = "/LIBPATH:"
  sys_lib_flags = string_join(" ", win_msvc_sys_lib_flags)
  external_cflags = string_join(" ",
                                [
                                  extra_cflags,
                                  extra_host_cflags,
                                ])

  # Note: /showIncludes below is required for ninja, to build a complete
  # dependency graph for headers. Removing it breaks incremental builds.

  tool("cc") {
    precompiled_header_type = "msvc"
    pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} ${external_cflags} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
    depsformat = "msvc"
    outputs =
        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
    description = "compile {{source}}"
  }

  tool("cxx") {
    precompiled_header_type = "msvc"
    pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} ${external_cflags} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
    depsformat = "msvc"
    outputs =
        [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
    description = "compile {{source}}"
  }

  tool("alink") {
    rspfile = "{{output}}.rsp"
    command = "$linker /lib /nologo /ignore:4221 {{arflags}} /OUT:{{output}} @$rspfile"
    outputs = [
      # Ignore {{output_extension}} and always use .lib, there's no reason to
      # allow targets to override this extension on Windows.
      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
    ]
    default_output_extension = ".lib"
    default_output_dir = "{{target_out_dir}}"

    # inputs_newline works around a fixed per-line buffer size in the linker.
    rspfile_content = "{{inputs_newline}}"
    description = "link {{output}}"
  }

  tool("solink") {
    dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
    libname = "${dllname}.lib"
    pdbname = "${dllname}.pdb"
    rspfile = "${dllname}.rsp"

    command = "$linker /nologo /IMPLIB:$libname ${sys_lib_flags} /DLL /OUT:$dllname /PDB:$pdbname @$rspfile"
    outputs = [
      dllname,
      libname,
      pdbname,
    ]
    default_output_extension = ".dll"
    default_output_dir = "{{root_out_dir}}"

    link_output = libname
    depend_output = libname
    runtime_outputs = [
      dllname,
      pdbname,
    ]

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

    # inputs_newline works around a fixed per-line buffer size in the linker.
    rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
    description = "link {{output}}"
  }

  tool("link") {
    exename = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
    pdbname = "$exename.pdb"
    rspfile = "$exename.rsp"

    command = "$linker /nologo /guard:cf /DYNAMICBASE /OUT:$exename ${sys_lib_flags} /DEBUG /PDB:$pdbname @$rspfile"
    default_output_extension = ".exe"
    default_output_dir = "{{root_out_dir}}"
    outputs = [ exename ]

    # inputs_newline works around a fixed per-line buffer size in the linker.
    rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
    description = "link {{output}}"
  }

  tool("stamp") {
    command = "cmd /c type nul > \"{{output}}\""
    description = "stamp {{output}}"
  }

  tool("copy") {
    cp_py = rebase_path("../cp.py")
    command = "cmd.exe /c python \"$cp_py\" {{source}} {{output}}"
    description = "copy {{source}} {{output}}"
  }
}
