blob: 6ae92a31f296525adb9b94e2fae389aab830fb8e [file] [log] [blame]
# 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 ability for our C++ toolchain to successfully
# link binaries containing arbitrary Rust code.
#
# By "arbitrary Rust code" I mean .rlib archives full of Rust code, which
# is actually a static archive.
#
# Those static libraries don't link as-is into a final executable because
# they're designed for downstream processing by further invocations of rustc
# which link into a final binary. That final invocation of rustc knows how
# to do two things:
# * Find the Rust standard library.
# * Remap some generic allocator symbols to the specific allocator symbols
# in use.
# This file takes care of equivalent tasks for our C++ toolchains.
# C++ targets should depend upon either link_local_std or
# link_prebuilt_std to ensure that Rust code can be linked into their
# C++ executables.
#
# This is obviously a bit fragile - rustc might do other magic in future.
# But, linking with a final C++ toolchain is something often needed, and
# https://github.com/rust-lang/rust/issues/64191 aims to make this
# officially possible.
import("//build/config/compiler/compiler.gni")
import("//build/config/rust.gni")
if (toolchain_has_rust) {
# Equivalent of allocator symbols injected by rustc.
source_set("remap_alloc") {
sources = [
"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)
"addr2line",
"adler",
"alloc",
"cfg_if",
"compiler_builtins",
"core",
"getopts",
"gimli",
"hashbrown",
"libc",
"memchr",
"miniz_oxide",
"object",
"panic_abort",
"panic_unwind",
"rustc_demangle",
"std_detect",
"test",
"unicode_width",
"unwind",
]
if (is_win) {
# Our C++ builds already link against a wide variety of Windows API import libraries,
# but the Rust stdlib requires a few extra.
_windows_lib_deps = [
"bcrypt.lib",
"ntdll.lib",
"userenv.lib",
]
}
# rlibs explicitly ignored when copying prebuilt sysroot libraries.
# find_std_rlibs.py rightfully errors out if an unexpected prebuilt lib is
# encountered, since it usually indicates we missed something. This ignore
# list is also passed to it. This has no effect on the local std build.
ignore_stdlib_files = []
# proc_macro is special: we only run proc macros on the host, so we only want
# it for our host toolchain.
if (current_toolchain == host_toolchain_no_sanitizers) {
# Directs the local_std_for_rustc target to depend on proc_macro, and
# includes proc_macro in the prebuilts copied in find_stdlib. Otherwise it
# is not built or copied.
stdlib_files += [ "proc_macro" ]
} else {
# Explicitly ignore it from the prebuilts. Nothing needs to be done for the
# local std build.
ignore_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",
]
if (prebuilt_libstd_supported) {
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 (!use_unverified_rust_toolchain) {
args += [
"--stdlibs",
string_join(",", all_stdlibs_to_copy),
]
if (ignore_stdlib_files != []) {
args += [
"--ignore-stdlibs",
string_join(",", ignore_stdlib_files),
]
}
}
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" ]
}
}
} else {
not_needed([ "ignore_stdlib_files" ])
}
# 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 (prebuilt_libstd_supported) {
prebuilt_rustc_sysroot = "$root_out_dir/prebuilt_rustc_sysroot"
copy("prebuilt_rustc_sysroot") {
deps = [ ":find_stdlib" ]
sources = get_target_outputs(":find_stdlib")
outputs =
[ "$prebuilt_rustc_sysroot/$sysroot_lib_subdir/{{source_file_part}}" ]
}
config("prebuilt_stdlib_for_rustc") {
# Match the output directory of :prebuilt_rustc_sysroot
sysroot = rebase_path(prebuilt_rustc_sysroot, root_build_dir)
rustflags = [ "--sysroot=$sysroot" ]
}
# Use the sysroot generated by :prebuilt_rustc_sysroot. Almost all Rust targets should depend
# on this.
group("prebuilt_std_for_rustc") {
assert(
enable_rust,
"Some C++ target is including Rust code even though enable_rust=false")
all_dependent_configs = [ ":prebuilt_stdlib_for_rustc" ]
deps = [ ":prebuilt_rustc_sysroot" ]
}
config("prebuilt_rust_stdlib_config") {
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 ]
}
if (is_win) {
# TODO(crbug.com/1434092): This 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.
ldflags += _windows_lib_deps
}
}
# Provides std libs to non-rustc linkers.
group("link_prebuilt_std") {
assert(
enable_rust,
"Some C++ target is including Rust code even though enable_rust=false")
all_dependent_configs = [ ":prebuilt_rust_stdlib_config" ]
deps = [
":prebuilt_rustc_sysroot",
":remap_alloc",
]
}
}
if (local_libstd_supported) {
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 = ""
}
config("local_stdlib_for_rustc") {
sysroot = rebase_path(local_rustc_sysroot, root_build_dir)
rustflags = [ "--sysroot=$sysroot" ]
}
# 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_for_rustc" ]
}
# Use the sysroot generated by :local_rustc_sysroot, which transitively builds
# std. Only for use in specific tests for now.
group("local_std_for_rustc") {
assert(
enable_rust,
"Some C++ target is including Rust code even though enable_rust=false")
all_dependent_configs = [ ":local_stdlib_for_rustc" ]
deps = []
foreach(libname, stdlib_files + skip_stdlib_files) {
deps += [ "rules:$libname" ]
}
}
config("local_rust_stdlib_config") {
if (is_win) {
# TODO(crbug.com/1434092): This 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.
ldflags = _windows_lib_deps
}
}
# TODO(crbug.com/1368806): rework this so when using locally-built std, we
# don't link the prebuilt std as well.
group("link_local_std") {
assert(
enable_rust,
"Some C++ target is including Rust code even though enable_rust=false")
all_dependent_configs = [ ":local_rust_stdlib_config" ]
deps = [
":local_std_for_rustc",
":remap_alloc",
]
}
}
}