chromium/build/rust/std/BUILD.gn

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

# This file provides the Rust standard library for GN targets.
#
# For Rust targets, it either copies a prebuilt stdlib or builds a stdlib, and
# then points rustc to it with `--sysroot`.
#
# When linking it ensures the libraries (and their C library dependencies) are
# part of the linker line. If Rust drives the linking, this is redundant but if
# Clang drives the linking it is required.
#
# Part of the standard library provided here is "remap_alloc" which maps
# allocator functions to PartitionAlloc when `use_partition_alloc_as_malloc` is
# true, so that Rust and C++ use the same allocator backend.

import("//base/allocator/partition_allocator/partition_alloc.gni")
import("//build/config/compiler/compiler.gni")
import("//build/config/coverage/coverage.gni")
import("//build/config/rust.gni")
import("//build/config/sanitizers/sanitizers.gni")

if (toolchain_has_rust) {
  # If clang performs the link step, we need to provide the allocator symbols
  # that are normally injected by rustc during linking.
  #
  # We also "happen to" use this to redirect allocations to PartitionAlloc,
  # though that would be better done through a #[global_allocator] crate (see
  # above).
  source_set("remap_alloc") {
    public_deps = [ "//base/allocator/partition_allocator:buildflags" ]
    if (use_partition_alloc_as_malloc) {
      public_deps += [ "//base/allocator/partition_allocator:partition_alloc" ]
    }
    sources = [
      # `alias.*`, `compiler_specific.h`, and `immediate_crash.*` have been
      # copied from `//base`.
      # TODO(crbug.com/40279749): Avoid duplication / reuse code.
      "alias.cc",
      "alias.h",
      "compiler_specific.h",
      "immediate_crash.h",
      "remap_alloc.cc",
    ]
  }

  # List of Rust stdlib rlibs which are present in the official Rust toolchain
  # we are using from the Android team. This is usually a version or two behind
  # nightly. Generally this matches the toolchain we build ourselves, but if
  # they differ, append or remove libraries based on the
  # `use_chromium_rust_toolchain` GN variable.
  #
  # If the build fails due to missing symbols, it would be because of a missing
  # library that needs to be added here in a newer stdlib.
  stdlib_files = [
    "std",  # List first because it makes depfiles more debuggable (see below)
    "alloc",
    "cfg_if",
    "compiler_builtins",
    "core",
    "getopts",
    "hashbrown",
    "panic_abort",
    "panic_unwind",
    "rustc_demangle",
    "std_detect",
    "test",
    "unicode_width",
    "unwind",
  ]

  if (!is_win) {
    # These are no longer present in the Windows toolchain.
    stdlib_files += [
      "addr2line",
      "adler",
      "gimli",
      "libc",
      "memchr",
      "miniz_oxide",
      "object",
    ]
  }

  if (toolchain_for_rust_host_build_tools) {
    # When building proc macros, include the proc_macro crate in what should be
    # copied with find_stdlib. Otherwise it is not copied since it will be
    # unused.
    stdlib_files += [ "proc_macro" ]
  }

  # Different Rust toolchains may add or remove files relative to the above
  # list. That can be specified in gn args for anyone using (for instance)
  # nightly or some other experimental toolchain, prior to it becoming official.
  stdlib_files -= removed_rust_stdlib_libs
  stdlib_files += added_rust_stdlib_libs

  # rlib files which are distributed alongside Rust's prebuilt stdlib, but we
  # don't need to pass to the C++ linker because they're used for specialized
  # purposes.
  skip_stdlib_files = [
    "profiler_builtins",
    "rustc_std_workspace_alloc",
    "rustc_std_workspace_core",
    "rustc_std_workspace_std",
  ]

  config("stdlib_dependent_libs") {
    # TODO(crbug.com/40264561): These should really be `libs`, however that
    # breaks. Normally, we specify lib files with the `.lib` suffix but
    # then when rustc links an EXE, it invokes lld-link with `.lib.lib`
    # instead.
    #
    # Omitting the `.lib` suffix breaks linking as well, when clang drives
    # the linking step of a C++ EXE that depends on Rust.
    if (is_win) {
      # The libc crate tries to link in the Windows CRT, but we specify the CRT
      # library ourselves in //build/config/win:dynamic_crt and
      # //build/config/win:static_crt because Rustc does not allow us to specify
      # using the debug CRT: https://github.com/rust-lang/rust/issues/39016
      #
      # As such, we have disabled all #[link] directives from the libc crate,
      # and we need to add any non-CRT libs here.
      ldflags = [ "legacy_stdio_definitions.lib" ]
    }
  }
  config("stdlib_public_dependent_libs") {
    # TODO(crbug.com/40264561): These should really be `libs`, however that
    # breaks. Normally, we specify lib files with the `.lib` suffix but
    # then when rustc links an EXE, it invokes lld-link with `.lib.lib`
    # instead.
    #
    # Omitting the `.lib` suffix breaks linking as well, when clang drives
    # the linking step of a C++ EXE that depends on Rust.
    if (is_win) {
      # These libs provide functions that are used by the stdlib. Rust crates
      # will try to link them in with #[link] directives. However these don't
      # get propagated to the linker if Rust isn't driving the linking (a C++
      # target that depends on a Rust rlib). So these need to be specified
      # explicitly.
      ldflags = [
        "advapi32.lib",
        "bcrypt.lib",
        "kernel32.lib",
        "ntdll.lib",
        "synchronization.lib",
        "userenv.lib",
        "ws2_32.lib",
      ]
    }

    # From rust/library/std/src/sys/unix/mod.rs.
    # TODO(danakj): We should generate this list somehow when building or rolling
    # the Rust toolchain?
    if (is_android) {
      libs = [ "dl" ]
    } else if (target_os == "freebsd") {
      libs = [
        "execinfo",
        "pthread",
      ]
    } else if (target_os == "netbsd") {
      libs = [
        "rt",
        "pthread",
      ]
    } else if (is_mac) {
      libs = [ "System" ]
    } else if (is_ios) {
      libs = [
        "System",
        "objc",
      ]
      frameworks = [
        "Security.framework",
        "Foundation.framework",
      ]
    } else if (is_fuchsia) {
      libs = [
        "zircon",
        "fdio",
      ]
    }
  }

  # Construct sysroots for rustc invocations to better control what libraries
  # are linked. We have two: one with copied prebuilt libraries, and one with
  # our locally-built std. Both reside in root_out_dir: we must only have one of
  # each per GN toolchain anyway.

  sysroot_lib_subdir = "lib/rustlib/$rust_abi_target/lib"

  if (!rust_prebuilt_stdlib) {
    local_rustc_sysroot = "$root_out_dir/local_rustc_sysroot"

    # All std targets starting with core build with our sysroot. It starts empty
    # and is incrementally built. The directory must exist at the start.
    generated_file("empty_sysroot_for_std_build") {
      outputs = [ "$local_rustc_sysroot/$sysroot_lib_subdir/.empty" ]
      contents = ""
      visibility = [ ":*" ]
    }

    # Target to be depended on by std build targets. Creates the initially empty
    # sysroot.
    group("std_build_deps") {
      deps = [ ":empty_sysroot_for_std_build" ]
      public_configs = [ ":local_stdlib_sysroot" ]
      visibility = [ "rules:*" ]
    }

    profiler_builtins_crates = [
      "core",
      "compiler_builtins",
      "profiler_builtins",
    ]

    # When using instrumentation, profiler_builtins and its deps must be built
    # before other std crates. Other crates depend on this target so they are
    # built in the right order.
    group("profiler_builtins_group") {
      deps = []
      foreach(libname, profiler_builtins_crates) {
        deps += [ "rules:$libname" ]
      }
      visibility = [ "rules:*" ]
    }

    config("local_stdlib_sysroot") {
      sysroot = rebase_path(local_rustc_sysroot, root_build_dir)
      rustflags = [ "--sysroot=$sysroot" ]
      visibility = [ ":*" ]
    }

    # When given -Zsanitize=..., rustc insists on passing a sanitizer runtime to
    # the linker it invokes. Unfortunately, our C++ ldflags already tell the
    # linker to link against a C++ sanitizer runtime - which contains the same
    # symbols. So, make a blank library.
    # The list of relevant sanitizers here is taken from
    # https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/compiler/rustc_codegen_ssa/src/back/link.rs#L1148
    template("rustc_sanitizer_runtime") {
      rt_name = target_name
      not_needed([ "invoker" ])
      static_library("sanitizer_rt_$rt_name") {
        sources = []
        output_name = "librustc-${rust_channel}_rt.$rt_name"
        output_dir = "$local_rustc_sysroot/$sysroot_lib_subdir"
        if (is_win) {
          arflags = [ "/llvmlibempty" ]
        }
      }
    }
    rustc_sanitizer_runtimes = []
    if (is_asan) {
      rustc_sanitizer_runtime("asan") {
      }
      rustc_sanitizer_runtimes += [ ":sanitizer_rt_asan" ]
    }
    if (is_lsan) {
      rustc_sanitizer_runtime("lsan") {
      }
      rustc_sanitizer_runtimes += [ ":sanitizer_rt_lsan" ]
    }
    if (is_msan) {
      rustc_sanitizer_runtime("msan") {
      }
      rustc_sanitizer_runtimes += [ ":sanitizer_rt_msan" ]
    }
    if (is_tsan) {
      rustc_sanitizer_runtime("tsan") {
      }
      rustc_sanitizer_runtimes += [ ":sanitizer_rt_tsan" ]
    }
    if (is_hwasan) {
      rustc_sanitizer_runtime("hwasan") {
      }
      rustc_sanitizer_runtimes += [ ":sanitizer_rt_hwasan" ]
    }

    # Builds and links against the Rust stdlib. Both Rust and C++ targets should
    # depend on this, as it provides the path to the library and includes the
    # allocator hooks.
    group("std") {
      assert(toolchain_has_rust,
             "Some C++ target is depending on Rust code even though " +
                 "toolchain_has_rust=false. Usually this would mean" +
                 "a NaCl target is depending on Rust, as there's no Rust " +
                 "toolchain targetting NaCl.")
      all_dependent_configs = [
        ":stdlib_public_dependent_libs",
        ":local_stdlib_sysroot",
        ":stdlib_dependent_libs",
      ]
      deps = []
      foreach(libname, stdlib_files + skip_stdlib_files) {
        deps += [ "rules:$libname" ]
      }
      deps += rustc_sanitizer_runtimes

      public_deps = [ ":remap_alloc" ]
    }
  } else {
    action("find_stdlib") {
      # Collect prebuilt Rust libraries from toolchain package and copy to a
      # known location.
      #
      # The Rust toolchain contains prebuilt rlibs for the standard library and
      # its dependencies. However, they have unstable names: an unpredictable
      # metadata hash is appended to the known crate name.
      #
      # We must depend on these rlibs explicitly when rustc is not in charge of
      # linking. However, it is difficult to construct GN rules to do so when
      # the names can't be known statically.
      #
      # This action copies the prebuilt rlibs to a known location, removing the
      # metadata part of the name. In the process it verifies we have all the
      # libraries we expect and none that we don't. A depfile is generated so
      # this step is re-run when any libraries change. The action script
      # additionally verifies rustc matches the expected version, which is
      # unrelated but this is a convenient place to do so.
      #
      # The action refers to `stdlib_files`, `skip_stdlib_files`, and the
      # associated //build/config/rust.gni vars `removed_rust_stdlib_libs` and
      # `added_rust_stdlib_libs` for which rlib files to expect.
      # `extra_sysroot_libs` is also used to copy non-std libs, if any.
      script = "find_std_rlibs.py"
      depfile = "$target_out_dir/stdlib.d"
      out_libdir = rebase_path(target_out_dir, root_build_dir)
      out_depfile = rebase_path(depfile, root_build_dir)

      # For the rustc sysroot we must include even the rlibs we don't pass to
      # the C++ linker.
      all_stdlibs_to_copy = stdlib_files + skip_stdlib_files
      args = [
        "--rust-bin-dir",
        rebase_path("${rust_sysroot}/bin", root_build_dir),
        "--output",
        out_libdir,
        "--depfile",
        out_depfile,

        # Due to limitations in Ninja's handling of .d files, we have to pick
        # *the first* of our outputs. To make diagnostics more obviously
        # related to the Rust standard library, we ensure libstd.rlib is first.
        "--depfile-target",
        stdlib_files[0],

        # Create a dependency on the rustc version so this action is re-run when
        # it changes. This argument is not actually read by the script.
        "--rustc-revision",
        rustc_revision,
      ]

      if (extra_sysroot_libs != []) {
        args += [
          "--extra-libs",
          string_join(",", extra_sysroot_libs),
        ]
      }

      args += [
        "--target",
        rust_abi_target,
      ]

      outputs = []
      foreach(lib, all_stdlibs_to_copy) {
        outputs += [ "$target_out_dir/lib$lib.rlib" ]
      }
      foreach(lib, extra_sysroot_libs) {
        outputs += [ "$target_out_dir/$lib" ]
      }

      visibility = [ ":*" ]
    }

    prebuilt_rustc_sysroot = "$root_out_dir/prebuilt_rustc_sysroot"
    copy("prebuilt_rustc_copy_to_sysroot") {
      assert(enable_rust,
             "Some C++ target is including Rust code even though " +
                 "enable_rust=false")
      deps = [ ":find_stdlib" ]
      sources = get_target_outputs(":find_stdlib")
      outputs =
          [ "$prebuilt_rustc_sysroot/$sysroot_lib_subdir/{{source_file_part}}" ]

      visibility = [ ":*" ]
    }

    config("prebuilt_stdlib_sysroot") {
      # Match the output directory of :prebuilt_rustc_copy_to_sysroot
      sysroot = rebase_path(prebuilt_rustc_sysroot, root_build_dir)
      rustflags = [ "--sysroot=$sysroot" ]
      visibility = [ ":*" ]
    }

    config("prebuilt_stdlib_libs") {
      ldflags = []
      lib_dir = rebase_path("$prebuilt_rustc_sysroot/$sysroot_lib_subdir",
                            root_build_dir)

      # We're unable to make these files regular gn dependencies because
      # they're prebuilt. Instead, we'll pass them in the ldflags. This doesn't
      # work for all types of build because ldflags propagate differently from
      # actual dependencies and therefore can end up in different targets from
      # the remap_alloc.cc above. For example, in a component build, we might
      # apply the remap_alloc.cc file and these ldlags to shared object A,
      # while shared object B (that depends upon A) might get only the ldflags
      # but not remap_alloc.cc, and thus the build will fail. There is
      # currently no known solution to this for the prebuilt stdlib - this
      # problem does not apply with configurations where we build the stdlib
      # ourselves, which is what we'll use in production.
      foreach(lib, stdlib_files) {
        this_file = "$lib_dir/lib$lib.rlib"
        ldflags += [ this_file ]
      }
      visibility = [ ":*" ]
    }

    group("std") {
      all_dependent_configs = [
        ":prebuilt_stdlib_libs",
        ":stdlib_public_dependent_libs",
      ]
      deps = [ ":prebuilt_rustc_copy_to_sysroot" ]

      # The host builds tools toolchain supports Rust only and does not use
      # the allocator remapping to point it to PartitionAlloc.
      if (!toolchain_for_rust_host_build_tools) {
        deps += [ ":remap_alloc" ]
      }
    }
  }
}