# Copyright 2021 The Chromium Project. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/config/chrome_build.gni")
import("//build/config/compiler/compiler.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/toolchain.gni")

if (is_android) {
  import("//build/config/android/config.gni")
}

if (is_ios) {
  # For `target_environment` and `target_platform`.
  import("//build/config/apple/mobile_config.gni")
}

declare_args() {
  # Rust is available in the Chromium build but 3p repos that use //build may
  # not use Rust and thus won't want to depend on having the Rust toolchain
  # present, so this defaults to off in those cases.
  #
  # Chromium-based projects that are built for for architectures Chrome does not
  # support may need to disable this as well, though they may need to replace
  # code with C/C++ to get a functional product.
  #
  # Based on the above:
  #
  # * `enable_rust` may be consulted under `//build` and `//testing` directories
  #   (which may be used outside of Chromium build)
  # * `enable_rust` should *not* be consulted in other Chromium directories
  #   (including `//base`, `//net`, etc.)
  enable_rust = build_with_chromium

  # The chromium prelude crate provides the `chromium::import!` macro which
  # is needed to depend on first-party rust libraries. Third-party libraries
  # are specified with cargo_crate and do not get imported through this macro.
  #
  # The macro requires //third_party/rust for syn, quote, and proc_macro2.
  # Downstream projects that want to use //build for the rust GN templates but
  # don't want to enable the chromium prelude can disable it here, and should
  # specify a globally unique `crate_name` in their rust library GN rules
  # instead. Note that using a `crate_name` is strongly discouraged inside
  # Chromium, and is also discouraged for downstream projects when possible.
  #
  # We do not support disabling this flag in Chromium code.
  enable_chromium_prelude = build_with_chromium

  # Chromium provides a Rust toolchain in //third_party/rust-toolchain.
  #
  # To use a custom toolchain instead, specify an absolute path to the root of
  # a Rust sysroot, which will have a 'bin' directory and others. Commonly
  # <home dir>/.rustup/toolchains/nightly-<something>-<something>
  # Using a custom toolchain should work, but this build configuration is
  # community-supported and not tested by Chromium CQ.  In particular, Chromium
  # build may depend on using a *nightly* version of Rust toolchain (see
  # `tools/rust/unstable_rust_feature_usage.md`) although other projects (e.g. V8)
  # may be compatible with the stable version of Rust toolchain.
  rust_sysroot_absolute = ""

  # Directory under which to find `bin/bindgen` (a `bin` directory containing
  # the bindgen exectuable).
  rust_bindgen_root = "//third_party/rust-toolchain"

  # If you're using a Rust toolchain as specified by rust_sysroot_absolute,
  # set this to the output of `rustc -V`. Changing this string will cause all
  # Rust targets to be rebuilt, which allows you to update your toolchain and
  # not break incremental builds.
  rustc_version = ""

  # Whether artifacts produced by the Rust compiler can participate in ThinLTO.
  #
  # One important consideration is whether the linker uses the same LLVM
  # version as `rustc` (i.e. if it can understand the LLVM-IR from the
  # compilation artifacts produced by `rustc`).  For more context please see
  # older bugs like b/299483903, https://crbug.com/40281834, or b/300937673.
  #
  # Disable on OHOS due to LLVM version mismatch between Rust (LLVM 22) and Clang (LLVM 19).
  toolchain_supports_rust_thin_lto = true

  # Any extra std rlibs in your Rust toolchain, relative to the standard
  # Rust toolchain. Typically used with 'rust_sysroot_absolute'
  added_rust_stdlib_libs = []

  # Any removed std rlibs in your Rust toolchain, relative to the standard
  # Rust toolchain. Typically used with 'rust_sysroot_absolute'
  removed_rust_stdlib_libs = []

  # Non-rlib libs provided in the toolchain sysroot. Usually this is empty, but
  # e.g. the Android Rust Toolchain provides a libunwind.a that rustc expects.
  extra_sysroot_libs = []

  # Force-enable `--color=always` for rustc, even when it would be disabled for
  # a platform. Mostly applicable to Windows, where new versions can handle ANSI
  # escape sequences but it's not reliable in general.
  force_rustc_color_output = false
}

declare_args() {
  # The CXX tool is in //third_party/rust which is not shared with downstream
  # projects yet. So they need to copy the required dependencies and GN files
  # into their project to enable CXX there.
  #
  # Currently, cxx is needed to use Rust with an allocator since the Chromium
  # allocator shim uses cxx
  enable_rust_cxx = enable_rust
}

# Use the Rust toolchain built in-tree. When false, we use the prebuilt Rust
# stdlibs that come with the specified custom toolchain.
use_chromium_rust_toolchain = rust_sysroot_absolute == ""

# `rustc_nightly_capability = false` will make the build avoid the use of
# Rust nightly features. There are no bots that test this and there is no
# guarantee it will work, but we accept patches for this configuration.
rustc_nightly_capability = use_chromium_rust_toolchain || is_ohos || is_linux

# Platform support for the Rust toolchain.
chromium_toolchain_supports_platform = !is_wasm
custom_toolchain_supports_platform = true

# Not all target triples (GN toolchains) are supported by the Rust compiler.
# Define if we support the current GN toolchain.
toolchain_has_rust = false

# The rustc_revision is used to introduce a dependency on the toolchain version
# (so e.g. rust targets are rebuilt, and the standard library is re-copied when
# the toolchain changes). It is left empty for custom toolchains.
rustc_revision = ""

if (enable_rust) {
  if (use_chromium_rust_toolchain) {
    toolchain_has_rust = chromium_toolchain_supports_platform
    if (toolchain_has_rust) {
      rustc_revision =
          read_file("//third_party/rust-toolchain/VERSION", "trim string")

      # Example:
      # rustc 1.88.0 c8f94230282a8e8c1148f3e657f0199aad909228 (c8f94230282a8e8c1148f3e657f0199aad909228-1-llvmorg-21-init-9266-g09006611 chromium)
      # Trim it down as much as we can using GN.
      rustc_revision = string_replace(rustc_revision, "rustc ", "")
      rustc_revision = string_replace(rustc_revision, " chromium", "")
      rustc_revision = string_replace(rustc_revision, "init-", "")
      rustc_revision = string_replace(rustc_revision, "llvmorg-", "")
      rustc_revision = string_replace(rustc_revision, " ", "")
      rustc_revision = string_replace(rustc_revision, ".", "")
      rustc_revision = string_replace(rustc_revision, "(", "-")
      rustc_revision = string_replace(rustc_revision, ")", "")
    }

    # The same as written in `config.toml.template`.
    rust_channel = "dev"
  } else {
    toolchain_has_rust = custom_toolchain_supports_platform
    rustc_revision = rustc_version
  }
}

# TODO(crbug.com/40809974): To build unit tests for Android we need to build
# them as a dylib and put them into an APK. We should reuse all the same logic
# for gtests from the `//testing/test:test` template.
can_build_rust_unit_tests = toolchain_has_rust && !is_android

# We want to store rust_sysroot as a source-relative variable for ninja
# portability. In practice if an external toolchain was specified, it might
# be an absolute path, but we'll do our best.
if (enable_rust) {
  if (use_chromium_rust_toolchain) {
    rust_sysroot = "//third_party/rust-toolchain"
  } else {
    rust_sysroot = get_path_info(rust_sysroot_absolute, "abspath")
  }
}

# Figure out the Rust target triple (aka 'rust_abi_target')
#
# This is here rather than in the toolchain files because it's used also by
# //build/rust/std to find the Rust standard library and construct a sysroot for
# rustc invocations.
#
# The list of architectures supported by Rust is here:
# https://doc.rust-lang.org/nightly/rustc/platform-support.html. We map Chromium
# targets to Rust targets comprehensively despite not having official support
# (see '*_toolchain_supports_platform above') to enable experimentation with
# other toolchains.
rust_abi_target = ""
if (is_linux || is_chromeos) {
  if (current_cpu == "arm64") {
    rust_abi_target = "aarch64-unknown-linux-gnu"
  } else if (current_cpu == "x86") {
    rust_abi_target = "i686-unknown-linux-gnu"
  } else if (current_cpu == "x64") {
    rust_abi_target = "x86_64-unknown-linux-gnu"
  } else if (current_cpu == "arm") {
    if (arm_float_abi == "hard") {
      float_suffix = "hf"
    } else {
      float_suffix = ""
    }
    if (arm_arch == "armv7-a" || arm_arch == "armv7") {
      # We have no way to inform Rust about the -a suffix, so we end up
      # targeting armv7 in both cases.
      #
      # We also try to propagate the availability of NEON without feature
      # detection; in C++ this is done by -mfpu=neon, but in Rust we need to
      # use a different ABI target.
      #
      # The thumbv7 vs. armv7 distinction is for legacy reasons and both
      # targets in fact target Thumb, see:
      # https://github.com/rust-lang/rust/issues/44722
      if (arm_use_neon) {
        rust_abi_target = "thumbv7neon-unknown-linux-gnueabi" + float_suffix
      } else {
        rust_abi_target = "armv7-unknown-linux-gnueabi" + float_suffix
      }
    } else {
      rust_abi_target = "arm-unknown-linux-gnueabi" + float_suffix
    }
  } else if (current_cpu == "riscv64") {
    rust_abi_target = "riscv64gc-unknown-linux-gnu"
  } else if (current_cpu == "ppc64") {
    rust_abi_target = "powerpc64le-unknown-linux-gnu"
  } else if (current_cpu == "s390x") {
    rust_abi_target = "s390x-unknown-linux-gnu"
  } else if (current_cpu == "loong64") {
    rust_abi_target = "loongarch64-unknown-linux-gnu"
  } else {
    # Best guess for other future platforms.
    rust_abi_target = current_cpu + "-unknown-linux-gnu"
  }
} else if (is_android) {
  import("//build/config/android/abi.gni")
  if (android_abi_target == "i686-linux-android") {
    rust_abi_target = android_abi_target
  } else if (android_abi_target == "arm-linux-androideabi") {
    # Android clang target specifications mostly match Rust, but this
    # is an exception.
    # See section above for Linux for thumbv7neon vs. armv7.
    # Note that on Android, NEON is enabled for all builds except Cronet.
    if (arm_use_neon) {
      rust_abi_target = "thumbv7neon-linux-androideabi"
    } else {
      rust_abi_target = "armv7-linux-androideabi"
    }
  } else if (android_abi_target == "mipsel-linux-android") {
    # There is no MIPS android target.
    rust_abi_target = ""
  } else if (android_abi_target == "x86_64-linux-android") {
    rust_abi_target = android_abi_target
  } else if (android_abi_target == "aarch64-linux-android") {
    rust_abi_target = android_abi_target
  } else if (android_abi_target == "mips64el-linux-android") {
    # There is no MIPS android target.
    rust_abi_target = ""
  } else if (android_abi_target == "riscv64-linux-android") {
    rust_abi_target = android_abi_target
  } else {
    assert(false, "Unknown Android ABI: " + android_abi_target)
  }
} else if (is_fuchsia) {
  if (current_cpu == "arm64") {
    rust_abi_target = "aarch64-unknown-fuchsia"
  } else if (current_cpu == "x64") {
    rust_abi_target = "x86_64-unknown-fuchsia"
  } else {
    assert(false, "Architecture not supported")
  }
} else if (is_ios) {
  if (current_cpu == "arm64e") {
    assert(target_platform == "iphoneos",
           "unsupported target_platform=$target_platform")
    rust_abi_target = "arm64e-apple-ios"
  } else if (current_cpu == "arm64") {
    if (target_platform == "iphoneos") {
      if (target_environment == "simulator") {
        rust_abi_target = "aarch64-apple-ios-sim"
      } else if (target_environment == "device") {
        rust_abi_target = "aarch64-apple-ios"
      } else if (target_environment == "catalyst") {
        rust_abi_target = "aarch64-apple-ios-macabi"
      } else {
        assert(false, "unsupported target_environment=$target_environment")
      }
    } else if (target_platform == "tvos") {
      if (target_environment == "simulator") {
        rust_abi_target = "aarch64-apple-tvos-sim"
      } else if (target_environment == "device") {
        rust_abi_target = "aarch64-apple-tvos"
      } else {
        assert(false, "unsupported target_environment=$target_environment")
      }
    } else {
      assert(false, "unsupported target_platform=$target_platform")
    }
  } else if (current_cpu == "arm") {
    rust_abi_target = "armv7s-apple-ios"
  } else if (current_cpu == "x64") {
    if (target_platform == "iphoneos") {
      if (target_environment == "simulator") {
        rust_abi_target = "x86_64-apple-ios"
      } else if (target_environment == "catalyst") {
        rust_abi_target = "x86_64-apple-ios-macabi"
      } else {
        assert(false, "unsupported target_environment=$target_environment")
      }
    } else if (target_platform == "tvos") {
      if (target_environment == "simulator") {
        rust_abi_target = "x86_64-apple-tvos"
      } else {
        assert(false, "unsupported target_environment=$target_environment")
      }
    } else {
      assert(false, "unsupported target_platform=$target_platform")
    }
  } else if (current_cpu == "x86") {
    rust_abi_target = "i386-apple-ios"
  } else {
    assert(false, "Architecture not supported")
  }
} else if (is_mac) {
  if (current_cpu == "arm64") {
    rust_abi_target = "aarch64-apple-darwin"
  } else if (current_cpu == "x64") {
    rust_abi_target = "x86_64-apple-darwin"
  } else {
    assert(false, "Architecture not supported")
  }
} else if (is_win) {
  if (current_cpu == "arm64") {
    rust_abi_target = "aarch64-pc-windows-msvc"
  } else if (current_cpu == "x64") {
    rust_abi_target = "x86_64-pc-windows-msvc"
  } else if (current_cpu == "x86") {
    rust_abi_target = "i686-pc-windows-msvc"
  } else {
    assert(false, "Architecture not supported")
  }
} else if (is_ohos) {
  if (current_cpu == "arm64") {
    rust_abi_target = "aarch64-unknown-linux-ohos"
    cargo_target_abi = ""
  } else if (current_cpu == "arm") {
    rust_abi_target = "armv7-unknown-linux-ohos"
    cargo_target_abi = ""
  } else if (current_cpu == "x64") {
    rust_abi_target = "x86_64-unknown-linux-ohos"
    cargo_target_abi = ""
  } else {
    toolchain_has_rust = false
  }
}

if (toolchain_has_rust) {
  assert(rust_abi_target != "")

  _known_rust_target_triples_filepath = "//build/rust/known-target-triples.txt"
  _is_rust_abi_target_a_known_triple = false
  _known_rust_target_triples_ =
      read_file(_known_rust_target_triples_filepath, "list lines")
  foreach(_known_triple, _known_rust_target_triples_) {
    if (_known_triple == rust_abi_target) {
      _is_rust_abi_target_a_known_triple = true
    }
  }
  assert(_is_rust_abi_target_a_known_triple,
         "`${rust_abi_target}` needs to be added to " +
             "`//${_known_rust_target_triples_filepath}`")
}

# This variable is passed to the Rust libstd build.
rust_target_arch = ""
if (current_cpu == "x86") {
  rust_target_arch = "x86"
} else if (current_cpu == "x64") {
  rust_target_arch = "x86_64"
} else if (current_cpu == "arm") {
  rust_target_arch = "arm"
} else if (current_cpu == "arm64") {
  rust_target_arch = "aarch64"
} else if (current_cpu == "arm64e") {
  rust_target_arch = "arm64e"
} else if (current_cpu == "mipsel") {
  rust_target_arch = "mips"
} else if (current_cpu == "mips64el") {
  rust_target_arch = "mips64"
} else if (current_cpu == "s390x") {
  rust_target_arch = "s390x"
} else if (current_cpu == "ppc64") {
  rust_target_arch = "powerpc64"
} else if (current_cpu == "riscv64") {
  rust_target_arch = "riscv64"
} else if (current_cpu == "loong64") {
  rust_target_arch = "loongarch64"
}

assert(!toolchain_has_rust || rust_target_arch != "")

# Arguments for Rust invocation.
# This is common between gcc/clang, Mac and Windows toolchains so specify once,
# here. This is not the complete command-line: toolchains should add -o
# and probably --emit arguments too.
rustc_common_args = "--crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}}"

# Rust procedural macros are shared objects loaded into a prebuilt host rustc
# binary. To build them, we obviously need to build for the host. Not only
# that, but because the host rustc is prebuilt, it lacks the machinery to be
# able to load shared objects built using sanitizers (ASAN etc.). For that
# reason, we need to use a host toolchain that lacks sanitizers. Additionally,
# proc macros should use panic=unwind, which means they need a stdlib that is
# compiled the same way, as is the stdlib that we ship with the compiler.
if (toolchain_for_rust_host_build_tools) {
  rust_macro_toolchain = current_toolchain
} else {
  rust_macro_toolchain = "${host_toolchain}_for_rust_host_build_tools"
}

# When this is true, a prebuilt Rust stdlib will be used. This has implications
# such as that the panic strategy (unwind, abort) must match how the stdlib is
# compiled, which is typically as unwind.
rust_prebuilt_stdlib =
    (!use_chromium_rust_toolchain && !is_ohos) || toolchain_for_rust_host_build_tools