1# Copyright 2021 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5# This file provides the ability for our C++ toolchain to successfully 6# link binaries containing arbitrary Rust code. 7# 8# By "arbitrary Rust code" I mean .rlib archives full of Rust code, which 9# is actually a static archive. 10# 11# Those static libraries don't link as-is into a final executable because 12# they're designed for downstream processing by further invocations of rustc 13# which link into a final binary. That final invocation of rustc knows how 14# to do two things: 15# * Find the Rust standard library. 16# * Remap some generic allocator symbols to the specific allocator symbols 17# in use. 18# This file takes care of equivalent tasks for our C++ toolchains. 19# C++ targets should depend upon either local_stdlib_for_clang or 20# prebuilt_stdlib_for_clang to ensure that Rust code can be linked into their 21# C++ executables. 22# 23# This is obviously a bit fragile - rustc might do other magic in future. 24# But, linking with a final C++ toolchain is something often needed, and 25# https://github.com/rust-lang/rust/issues/64191 aims to make this 26# officially possible. 27 28import("//build/config/compiler/compiler.gni") 29import("//build/config/coverage/coverage.gni") 30import("//build/config/rust.gni") 31import("//build/config/sanitizers/sanitizers.gni") 32 33if (toolchain_has_rust) { 34 # Equivalent of allocator symbols injected by rustc. 35 source_set("remap_alloc") { 36 sources = [ 37 # `alias.*`, `compiler_specific.h`, and `immediate_crash.*` have been 38 # copied from `//base`. 39 # TODO(https://crbug.com/1475734): Avoid duplication / reuse code. 40 "alias.cc", 41 "alias.h", 42 "compiler_specific.h", 43 "immediate_crash.h", 44 "remap_alloc.cc", 45 ] 46 } 47 48 # List of Rust stdlib rlibs which are present in the official Rust toolchain 49 # we are using from the Android team. This is usually a version or two behind 50 # nightly. Generally this matches the toolchain we build ourselves, but if 51 # they differ, append or remove libraries based on the 52 # `use_chromium_rust_toolchain` GN variable. 53 # 54 # If the build fails due to missing symbols, it would be because of a missing 55 # library that needs to be added here in a newer stdlib. 56 stdlib_files = [ 57 "std", # List first because it makes depfiles more debuggable (see below) 58 "alloc", 59 "cfg_if", 60 "compiler_builtins", 61 "core", 62 "getopts", 63 "hashbrown", 64 "libc", 65 "panic_abort", 66 "panic_unwind", 67 "rustc_demangle", 68 "std_detect", 69 "test", 70 "unicode_width", 71 "unwind", 72 ] 73 74 if (!is_win) { 75 # These are no longer present in the Windows toolchain. 76 stdlib_files += [ 77 "addr2line", 78 "adler", 79 "gimli", 80 "memchr", 81 "miniz_oxide", 82 "object", 83 ] 84 } 85 86 if (toolchain_for_rust_host_build_tools) { 87 # When building proc macros, include the proc_macro crate in what should be 88 # copied with find_stdlib. Otherwise it is not copied since it will be 89 # unused. 90 stdlib_files += [ "proc_macro" ] 91 } 92 93 # Different Rust toolchains may add or remove files relative to the above 94 # list. That can be specified in gn args for anyone using (for instance) 95 # nightly or some other experimental toolchain, prior to it becoming official. 96 stdlib_files -= removed_rust_stdlib_libs 97 stdlib_files += added_rust_stdlib_libs 98 99 # rlib files which are distributed alongside Rust's prebuilt stdlib, but we 100 # don't need to pass to the C++ linker because they're used for specialized 101 # purposes. 102 skip_stdlib_files = [ 103 "profiler_builtins", 104 "rustc_std_workspace_alloc", 105 "rustc_std_workspace_core", 106 "rustc_std_workspace_std", 107 ] 108 109 config("stdlib_dependent_libs") { 110 # TODO(crbug.com/1434092): These should really be `libs`, however that 111 # breaks. Normally, we specify lib files with the `.lib` suffix but 112 # then when rustc links an EXE, it invokes lld-link with `.lib.lib` 113 # instead. 114 # 115 # Omitting the `.lib` suffix breaks linking as well, when clang drives 116 # the linking step of a C++ EXE that depends on Rust. 117 if (is_win) { 118 # The libc crate tries to link in the Windows CRT, but we specify the CRT 119 # library ourselves in //build/config/win:dynamic_crt and 120 # //build/config/win:static_crt because Rustc does not allow us to specify 121 # using the debug CRT: https://github.com/rust-lang/rust/issues/39016 122 # 123 # As such, we have disabled all #[link] directives from the libc crate, 124 # and we need to add any non-CRT libs here. 125 ldflags = [ "legacy_stdio_definitions.lib" ] 126 } 127 } 128 config("stdlib_public_dependent_libs") { 129 # TODO(crbug.com/1434092): These should really be `libs`, however that 130 # breaks. Normally, we specify lib files with the `.lib` suffix but 131 # then when rustc links an EXE, it invokes lld-link with `.lib.lib` 132 # instead. 133 # 134 # Omitting the `.lib` suffix breaks linking as well, when clang drives 135 # the linking step of a C++ EXE that depends on Rust. 136 if (is_win) { 137 # These libs provide functions that are used by the stdlib. Rust crates 138 # will try to link them in with #[link] directives. However these don't 139 # get propagated to the linker if Rust isn't driving the linking (a C++ 140 # target that depends on a Rust rlib). So these need to be specified 141 # explicitly. 142 ldflags = [ 143 "advapi32.lib", 144 "bcrypt.lib", 145 "kernel32.lib", 146 "ntdll.lib", 147 "userenv.lib", 148 "ws2_32.lib", 149 ] 150 } 151 152 # From rust/library/std/src/sys/unix/mod.rs. 153 # TODO(danakj): We should generate this list somehow when building or rolling 154 # the Rust toolchain? 155 if (is_android) { 156 libs = [ "dl" ] 157 } else if (target_os == "freebsd") { 158 libs = [ 159 "execinfo", 160 "pthread", 161 ] 162 } else if (target_os == "netbsd") { 163 libs = [ 164 "rt", 165 "pthread", 166 ] 167 } else if (is_mac) { 168 libs = [ "System" ] 169 } else if (is_ios) { 170 libs = [ 171 "System", 172 "objc", 173 ] 174 frameworks = [ 175 "Security.framework", 176 "Foundation.framework", 177 ] 178 } else if (is_fuchsia) { 179 libs = [ 180 "zircon", 181 "fdio", 182 ] 183 } 184 } 185 186 # Construct sysroots for rustc invocations to better control what libraries 187 # are linked. We have two: one with copied prebuilt libraries, and one with 188 # our locally-built std. Both reside in root_out_dir: we must only have one of 189 # each per GN toolchain anyway. 190 191 sysroot_lib_subdir = "lib/rustlib/$rust_abi_target/lib" 192 193 if (!rust_prebuilt_stdlib) { 194 local_rustc_sysroot = "$root_out_dir/local_rustc_sysroot" 195 196 # All std targets starting with core build with our sysroot. It starts empty 197 # and is incrementally built. The directory must exist at the start. 198 generated_file("empty_sysroot_for_std_build") { 199 outputs = [ "$local_rustc_sysroot/$sysroot_lib_subdir/.empty" ] 200 contents = "" 201 visibility = [ ":*" ] 202 } 203 204 # Target to be depended on by std build targets. Creates the initially empty 205 # sysroot. 206 group("std_build_deps") { 207 deps = [ ":empty_sysroot_for_std_build" ] 208 public_configs = [ ":local_stdlib_sysroot" ] 209 visibility = [ "rules:*" ] 210 } 211 212 profiler_builtins_crates = [ 213 "core", 214 "compiler_builtins", 215 "profiler_builtins", 216 ] 217 218 # When using instrumentation, profiler_builtins and its deps must be built 219 # before other std crates. Other crates depend on this target so they are 220 # built in the right order. 221 group("profiler_builtins_group") { 222 deps = [] 223 foreach(libname, profiler_builtins_crates) { 224 deps += [ "rules:$libname" ] 225 } 226 visibility = [ "rules:*" ] 227 } 228 229 config("local_stdlib_sysroot") { 230 sysroot = rebase_path(local_rustc_sysroot, root_build_dir) 231 rustflags = [ "--sysroot=$sysroot" ] 232 visibility = [ ":*" ] 233 } 234 235 # When given -Zsanitize=..., rustc insists on passing a sanitizer runtime to 236 # the linker it invokes. Unfortunately, our C++ ldflags already tell the 237 # linker to link against a C++ sanitizer runtime - which contains the same 238 # symbols. So, make a blank library. 239 # The list of relevant sanitizers here is taken from 240 # https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/compiler/rustc_codegen_ssa/src/back/link.rs#L1148 241 template("rustc_sanitizer_runtime") { 242 rt_name = target_name 243 not_needed([ "invoker" ]) 244 static_library("sanitizer_rt_$rt_name") { 245 sources = [] 246 output_name = "librustc-${rust_channel}_rt.$rt_name" 247 output_dir = "$local_rustc_sysroot/$sysroot_lib_subdir" 248 if (is_win) { 249 arflags = [ "/llvmlibempty" ] 250 } 251 } 252 } 253 rustc_sanitizer_runtimes = [] 254 if (is_asan) { 255 rustc_sanitizer_runtime("asan") { 256 } 257 rustc_sanitizer_runtimes += [ ":sanitizer_rt_asan" ] 258 } 259 if (is_lsan) { 260 rustc_sanitizer_runtime("lsan") { 261 } 262 rustc_sanitizer_runtimes += [ ":sanitizer_rt_lsan" ] 263 } 264 if (is_msan) { 265 rustc_sanitizer_runtime("msan") { 266 } 267 rustc_sanitizer_runtimes += [ ":sanitizer_rt_msan" ] 268 } 269 if (is_tsan) { 270 rustc_sanitizer_runtime("tsan") { 271 } 272 rustc_sanitizer_runtimes += [ ":sanitizer_rt_tsan" ] 273 } 274 if (is_hwasan) { 275 rustc_sanitizer_runtime("hwasan") { 276 } 277 rustc_sanitizer_runtimes += [ ":sanitizer_rt_hwasan" ] 278 } 279 280 group("local_stdlib_libs") { 281 assert(toolchain_has_rust, 282 "Some C++ target is depending on Rust code even though " + 283 "toolchain_has_rust=false. Usually this would mean" + 284 "a NaCl target is depending on Rust, as there's no Rust " + 285 "toolchain targetting NaCl.") 286 all_dependent_configs = [ ":stdlib_dependent_libs" ] 287 deps = [] 288 foreach(libname, stdlib_files + skip_stdlib_files) { 289 deps += [ "rules:$libname" ] 290 } 291 deps += rustc_sanitizer_runtimes 292 visibility = [ ":*" ] 293 } 294 295 # Builds the stdlib and points the rustc `--sysroot` to them. Used by 296 # targets for which linking is driven by Rust (bins and dylibs). 297 group("stdlib_for_rustc") { 298 all_dependent_configs = [ ":local_stdlib_sysroot" ] 299 public_deps = [ ":local_stdlib_libs" ] 300 } 301 302 # Builds and links against the Rust stdlib. Used by targets for which 303 # linking is driven by C++. 304 group("stdlib_for_clang") { 305 all_dependent_configs = [ ":stdlib_public_dependent_libs" ] 306 public_deps = [ 307 ":local_stdlib_libs", 308 ":remap_alloc", 309 ] 310 } 311 } else { 312 action("find_stdlib") { 313 # Collect prebuilt Rust libraries from toolchain package and copy to a 314 # known location. 315 # 316 # The Rust toolchain contains prebuilt rlibs for the standard library and 317 # its dependencies. However, they have unstable names: an unpredictable 318 # metadata hash is appended to the known crate name. 319 # 320 # We must depend on these rlibs explicitly when rustc is not in charge of 321 # linking. However, it is difficult to construct GN rules to do so when 322 # the names can't be known statically. 323 # 324 # This action copies the prebuilt rlibs to a known location, removing the 325 # metadata part of the name. In the process it verifies we have all the 326 # libraries we expect and none that we don't. A depfile is generated so 327 # this step is re-run when any libraries change. The action script 328 # additionally verifies rustc matches the expected version, which is 329 # unrelated but this is a convenient place to do so. 330 # 331 # The action refers to `stdlib_files`, `skip_stdlib_files`, and the 332 # associated //build/config/rust.gni vars `removed_rust_stdlib_libs` and 333 # `added_rust_stdlib_libs` for which rlib files to expect. 334 # `extra_sysroot_libs` is also used to copy non-std libs, if any. 335 script = "find_std_rlibs.py" 336 depfile = "$target_out_dir/stdlib.d" 337 out_libdir = rebase_path(target_out_dir, root_build_dir) 338 out_depfile = rebase_path(depfile, root_build_dir) 339 340 # For the rustc sysroot we must include even the rlibs we don't pass to 341 # the C++ linker. 342 all_stdlibs_to_copy = stdlib_files + skip_stdlib_files 343 args = [ 344 "--rust-bin-dir", 345 rebase_path("${rust_sysroot}/bin", root_build_dir), 346 "--output", 347 out_libdir, 348 "--depfile", 349 out_depfile, 350 351 # Due to limitations in Ninja's handling of .d files, we have to pick 352 # *the first* of our outputs. To make diagnostics more obviously 353 # related to the Rust standard library, we ensure libstd.rlib is first. 354 "--depfile-target", 355 stdlib_files[0], 356 357 # Create a dependency on the rustc version so this action is re-run when 358 # it changes. This argument is not actually read by the script. 359 "--rustc-revision", 360 rustc_revision, 361 ] 362 363 if (extra_sysroot_libs != []) { 364 args += [ 365 "--extra-libs", 366 string_join(",", extra_sysroot_libs), 367 ] 368 } 369 370 args += [ 371 "--target", 372 rust_abi_target, 373 ] 374 375 outputs = [] 376 foreach(lib, all_stdlibs_to_copy) { 377 outputs += [ "$target_out_dir/lib$lib.rlib" ] 378 } 379 foreach(lib, extra_sysroot_libs) { 380 outputs += [ "$target_out_dir/$lib" ] 381 } 382 383 visibility = [ ":*" ] 384 } 385 386 prebuilt_rustc_sysroot = "$root_out_dir/prebuilt_rustc_sysroot" 387 copy("prebuilt_rustc_copy_to_sysroot") { 388 assert(enable_rust, 389 "Some C++ target is including Rust code even though " + 390 "enable_rust=false") 391 deps = [ ":find_stdlib" ] 392 sources = get_target_outputs(":find_stdlib") 393 outputs = 394 [ "$prebuilt_rustc_sysroot/$sysroot_lib_subdir/{{source_file_part}}" ] 395 396 visibility = [ ":*" ] 397 } 398 399 config("prebuilt_stdlib_sysroot") { 400 # Match the output directory of :prebuilt_rustc_copy_to_sysroot 401 sysroot = rebase_path(prebuilt_rustc_sysroot, root_build_dir) 402 rustflags = [ "--sysroot=$sysroot" ] 403 visibility = [ ":*" ] 404 } 405 406 config("prebuilt_stdlib_libs") { 407 ldflags = [] 408 lib_dir = rebase_path("$prebuilt_rustc_sysroot/$sysroot_lib_subdir", 409 root_build_dir) 410 411 # We're unable to make these files regular gn dependencies because 412 # they're prebuilt. Instead, we'll pass them in the ldflags. This doesn't 413 # work for all types of build because ldflags propagate differently from 414 # actual dependencies and therefore can end up in different targets from 415 # the remap_alloc.cc above. For example, in a component build, we might 416 # apply the remap_alloc.cc file and these ldlags to shared object A, 417 # while shared object B (that depends upon A) might get only the ldflags 418 # but not remap_alloc.cc, and thus the build will fail. There is 419 # currently no known solution to this for the prebuilt stdlib - this 420 # problem does not apply with configurations where we build the stdlib 421 # ourselves, which is what we'll use in production. 422 foreach(lib, stdlib_files) { 423 this_file = "$lib_dir/lib$lib.rlib" 424 ldflags += [ this_file ] 425 } 426 visibility = [ ":*" ] 427 } 428 429 # Use the sysroot generated by :prebuilt_rustc_copy_to_sysroot. 430 group("stdlib_for_rustc") { 431 all_dependent_configs = [ ":prebuilt_stdlib_sysroot" ] 432 deps = [ ":prebuilt_rustc_copy_to_sysroot" ] 433 } 434 435 # Links the Rust stdlib. Used by targets for which linking is driven by 436 # C++. 437 group("stdlib_for_clang") { 438 all_dependent_configs = [ 439 ":prebuilt_stdlib_libs", 440 ":stdlib_public_dependent_libs", 441 ] 442 deps = [ ":prebuilt_rustc_copy_to_sysroot" ] 443 444 # The host builds tools toolchain supports Rust only and does not use 445 # the allocator remapping to point it to PartitionAlloc. 446 if (!toolchain_for_rust_host_build_tools) { 447 deps += [ ":remap_alloc" ] 448 } 449 } 450 } 451} 452