• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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