# 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", ] } } }