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