xref: /aosp_15_r20/external/cronet/build/rust/rust_target.gni (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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
5import("//build/config/rust.gni")
6import("//build/rust/rust_unit_test.gni")
7
8# The //build directory is re-used for non-Chromium products. We do not support
9# cxx bindings in such contexts, because //third_party may be missing.
10if (build_with_chromium) {
11  import("//third_party/rust/cxx/chromium_integration/rust_cxx.gni")
12}
13
14# Creates a Rust target (rlib, executable, proc macro etc.) with ability to
15# understand some handy variables such as "edition" and "features" and also to
16# build any associated unit tests.
17#
18# Normally, you should not use this directly. Use either
19# - cargo_crate.gni - for 3p crates only
20# - rust_static_library.gni - for 1p Rust code
21#
22# Because the common use of this is rust_static_library, all the documentation
23# for the supported options is given in rust_static_library.gni. Please refer
24# over there.
25#
26# If you're using rust_target directly, you will also need to specify:
27# target_type executable, rust_library etc. per GN norms
28#
29# There is one area where this differs from `rust_static_library`: configs.
30# Here, you must specify `executable_configs` or `library_configs` depending on
31# the type of thing you're generating. This is so that different defaults can
32# be provided.
33
34template("rust_target") {
35  _target_name = target_name
36
37  # NOTE: TargetName=>CrateName mangling algorithm should be updated
38  # simultaneously in 3 places: here, //build/rust/rust_static_library.gni,
39  # //build/rust/chromium_prelude/import_attribute.rs
40  if (defined(invoker.crate_name)) {
41    _crate_name = invoker.crate_name
42  } else {
43    # Not using `get_label_info(..., "label_no_toolchain")` to consistently
44    # use `//foo/bar:baz` instead of the alternative shorter `//foo/bar` form.
45    _dir = get_label_info(":${_target_name}", "dir")
46    _dir = string_replace(_dir, "//", "")
47    _crate_name = "${_dir}:${_target_name}"
48
49    # The `string_replace` calls below replicate the escaping algorithm
50    # from the `escape_non_identifier_chars` function in
51    # //build/rust/chromium_prelude/import_attribute.rs.  Note that the
52    # ordering of `match` branches within the Rust function doesn't matter,
53    # but the ordering of `string_replace` calls *does* matter - the escape
54    # character `_` needs to be handled first to meet the injectivity
55    # requirement (otherwise we would get `/` => `_s` => `_us` and the same
56    # result for `_s` => `_us`).
57    _crate_name = string_replace(_crate_name, "_", "_u")
58    _crate_name = string_replace(_crate_name, "/", "_s")
59    _crate_name = string_replace(_crate_name, ":", "_c")
60    _crate_name = string_replace(_crate_name, "-", "_d")
61  }
62  _generate_crate_root =
63      defined(invoker.generate_crate_root) && invoker.generate_crate_root
64
65  # Only one of `crate_root` or `generate_crate_root` can be specified, or
66  # neither.
67  assert(!defined(invoker.crate_root) || !_generate_crate_root)
68
69  # This is where the OUT_DIR environment variable points to when running a
70  # build script and when compiling the build target, for consuming generated
71  # files.
72  _env_out_dir = "$target_gen_dir/$_target_name"
73
74  _allow_unsafe = false
75  if (defined(invoker.allow_unsafe)) {
76    _allow_unsafe = invoker.allow_unsafe
77  }
78
79  if (_generate_crate_root) {
80    generated_file("${_target_name}_crate_root") {
81      outputs = [ "${target_gen_dir}/${target_name}.rs" ]
82      contents = [
83        "// Generated crate root for ${_target_name}.",
84        "// @generated",
85        "",
86      ]
87      foreach(rs, invoker.sources) {
88        rs_path_from_root = rebase_path(rs, target_gen_dir)
89        contents += [ "#[path = \"${rs_path_from_root}\"]" ]
90
91        # Drop the file extension from the module name.
92        rs_modname = string_replace(rs, ".rs", "")
93
94        # Replace invalid "/" chars in the source file path.
95        rs_modname = string_replace(rs_modname, "/", "_")
96
97        # Since source files are specified relative to the BUILD.gn they may
98        # also have ".." path components.
99        rs_modname = string_replace(rs_modname, "..", "dotdot")
100        contents += [
101          "mod ${rs_modname};",
102          "",
103        ]
104      }
105    }
106    _generated_crate_root = get_target_outputs(":${_target_name}_crate_root")
107    _crate_root = _generated_crate_root[0]
108  } else if (defined(invoker.crate_root)) {
109    _crate_root = invoker.crate_root
110  } else if (invoker.target_type == "executable") {
111    _crate_root = "src/main.rs"
112  } else {
113    _crate_root = "src/lib.rs"
114  }
115
116  _testonly = false
117  if (defined(invoker.testonly)) {
118    _testonly = invoker.testonly
119  }
120  if (defined(invoker.visibility)) {
121    _visibility = invoker.visibility
122  }
123
124  _rustflags = []
125  if (defined(invoker.rustflags)) {
126    _rustflags += invoker.rustflags
127  }
128  if (defined(invoker.features)) {
129    foreach(i, invoker.features) {
130      _rustflags += [ "--cfg=feature=\"${i}\"" ]
131    }
132  }
133  _edition = "2021"
134  if (defined(invoker.edition)) {
135    _edition = invoker.edition
136  }
137
138  assert(!defined(configs))
139  _configs = [ "//build/rust:edition_${_edition}" ]
140  _test_configs = []
141  if (invoker.target_type == "executable") {
142    _configs += invoker.executable_configs
143  } else if (invoker.target_type == "rust_proc_macro") {
144    _configs += invoker.proc_macro_configs
145    _test_configs += [ "//build/rust:proc_macro_extern" ]
146  } else if (invoker.target_type == "shared_library") {
147    _configs += invoker.shared_library_configs
148  } else {
149    _configs += invoker.library_configs
150  }
151
152  if (invoker.target_type == "rust_proc_macro") {
153    _main_target_suffix = "__proc_macro"
154  } else if (invoker.target_type == "shared_library") {
155    _main_target_suffix = "__proc_macro"
156  } else {
157    _main_target_suffix = ""
158  }
159
160  _deps = []
161  if (defined(invoker.deps)) {
162    _deps += invoker.deps
163  }
164  _public_deps = []
165  if (defined(invoker.public_deps)) {
166    _public_deps += invoker.public_deps
167  }
168  if (defined(invoker.aliased_deps)) {
169    _aliased_deps = invoker.aliased_deps
170  } else {
171    _aliased_deps = {
172    }
173  }
174
175  _build_unit_tests = false
176  if (defined(invoker.build_native_rust_unit_tests)) {
177    _build_unit_tests =
178        invoker.build_native_rust_unit_tests && can_build_rust_unit_tests
179  }
180
181  # Declares that the Rust crate generates bindings between C++ and Rust via the
182  # Cxx crate. It may generate C++ headers and/or use the cxx crate macros to
183  # generate Rust code internally, depending on what bindings are declared. If
184  # set, it's a set of rust files that include Cxx bindings declarations.
185  _cxx_bindings = []
186  assert(!defined(invoker.cxx_bindings) || enable_cxx,
187         "cxx bindings are not supported when building rust targets " +
188             "outside the Chromium build.")
189  if (defined(invoker.cxx_bindings)) {
190    _cxx_bindings = invoker.cxx_bindings
191  }
192  _rustenv = [ "OUT_DIR=" +
193               rebase_path(_env_out_dir, get_path_info(_crate_root, "dir")) ]
194  if (defined(invoker.rustenv)) {
195    _rustenv += invoker.rustenv
196  }
197
198  # We require that all source files are listed, even though this is
199  # not a requirement for rustc. The reason is to ensure that tools
200  # such as `gn deps` give the correct answer, and thus we trigger
201  # the right test suites etc. on code change.
202  # TODO(crbug.com/1256930) - verify this is correct
203  assert(defined(invoker.sources), "sources must be listed")
204
205  if (invoker.target_type == "rust_proc_macro" &&
206      !toolchain_for_rust_host_build_tools) {
207    # Redirect to the proc macro toolchain, which uses prebuilt stdlib libraries
208    # that are not built with panic=abort.
209    group(_target_name) {
210      testonly = _testonly
211      if (defined(_visibility)) {
212        visibility = _visibility
213      }
214      public_deps =
215          [ ":${_target_name}${_main_target_suffix}($rust_macro_toolchain)" ]
216    }
217
218    not_needed(invoker, "*")
219    not_needed([
220                 "_aliased_deps",
221                 "_allow_unsafe",
222                 "_build_unit_tests",
223                 "_crate_root",
224                 "_crate_name",
225                 "_cxx_bindings",
226                 "_deps",
227                 "_rustc_metadata",
228                 "_out_dir",
229                 "_public_deps",
230                 "_rustenv",
231                 "_rustflags",
232                 "_support_use_from_cpp",
233                 "_testonly",
234               ])
235  } else {
236    # These are dependencies that must be included into the C++ target that
237    # depends on this Rust one, and into the Rust target itself, respectively.
238    #
239    # For an rlib or exe, it's enough to add all these as dependencies of the
240    # Rust target alone, and they will get included into the final link step.
241    #
242    # But when then Rust target is a shared library, the C++ target needs to
243    # link the C++ thunks that are used to call the cxx bridge functions. And
244    # Cxx library itself needs to be in both.
245    _cxx_generated_deps_for_cpp = []
246    _cxx_generated_deps_for_rust = []
247    if (_cxx_bindings != []) {
248      _cxx_generated_deps_for_cpp += [
249        # The Cxx-generated thunks, which have the public C++ names and bounce
250        # over to the Rust code.
251        ":${_target_name}_cxx_generated",
252
253        # Additionally, C++ bindings generated by Cxx can include C++ types
254        # that come from the Cxx library, such as `rust::Str`. The header and
255        # implementation of these types are provided in the cxx_cppdeps target.
256        # The C++ targets depending on this Rust target need the headers, while
257        # the Rust target needs the implementation.
258        "//build/rust:cxx_cppdeps",
259      ]
260      _cxx_generated_deps_for_rust = [
261        # The implementation of the Cxx library needs to be in the Rust target.
262        "//build/rust:cxx_cppdeps",
263      ]
264    }
265
266    # Proc macros and shared libraries have a group for the target name and
267    # redirect to a suffixed target for the actual library.
268    if (_main_target_suffix != "") {
269      group(_target_name) {
270        testonly = _testonly
271        if (defined(_visibility)) {
272          visibility = _visibility
273        }
274        public_deps = [ ":${_target_name}${_main_target_suffix}" ]
275        public_deps += _cxx_generated_deps_for_cpp
276      }
277    }
278
279    _rustc_metadata = ""
280    if (defined(invoker.rustc_metadata)) {
281      _rustc_metadata = invoker.rustc_metadata
282    }
283
284    _rust_deps = _deps
285    _rust_aliased_deps = _aliased_deps
286    _rust_public_deps = _public_deps
287    _cxx_deps = _deps
288
289    # Include the `chromium` crate in all first-party code. Third-party code
290    # (and the `chromium` crate itself) opts out by setting
291    # `no_chromium_prelude`.
292    if (!defined(invoker.no_chromium_prelude) || !invoker.no_chromium_prelude) {
293      if (enable_chromium_prelude) {
294        _rust_deps += [ "//build/rust/chromium_prelude" ]
295      }
296    }
297
298    if (_cxx_bindings != []) {
299      # The Rust target (and unit tests) need the Cxx crate when using it to
300      # generate bindings.
301      _rust_deps += [ "//build/rust:cxx_rustdeps" ]
302    }
303
304    if (!defined(invoker.no_std) || !invoker.no_std) {
305      _rust_deps += [ "//build/rust/std" ]
306    }
307
308    if (_build_unit_tests) {
309      _unit_test_target = "${_target_name}_unittests"
310      if (defined(invoker.unit_test_target)) {
311        _unit_test_target = invoker.unit_test_target
312      }
313
314      rust_unit_test(_unit_test_target) {
315        testonly = true
316        crate_name = _unit_test_target
317        crate_root = _crate_root
318        sources = invoker.sources + [ crate_root ]
319        rustflags = _rustflags
320        env_out_dir = _env_out_dir
321        if (defined(invoker.unit_test_output_dir)) {
322          output_dir = invoker.unit_test_output_dir
323        }
324        deps = _rust_deps + _public_deps
325        aliased_deps = _rust_aliased_deps
326        public_deps = [ ":${_target_name}" ]
327        if (defined(invoker.test_deps)) {
328          deps += invoker.test_deps
329        }
330        inputs = []
331        if (defined(invoker.inputs)) {
332          inputs += invoker.inputs
333        }
334        if (defined(invoker.test_inputs)) {
335          inputs += invoker.test_inputs
336        }
337        if (defined(invoker.executable_configs)) {
338          configs = []
339          configs += invoker.executable_configs
340        }
341        configs += _test_configs
342        rustenv = _rustenv
343
344        if (!_allow_unsafe) {
345          configs += [ "//build/rust:forbid_unsafe" ]
346        }
347      }
348    } else {
349      not_needed([
350                   "_crate_root",
351                   "_crate_name",
352                   "_rustc_metadata",
353                   "_test_configs",
354                 ])
355      not_needed(invoker, [ "executable_configs" ])
356    }
357
358    target(invoker.target_type, "${_target_name}${_main_target_suffix}") {
359      forward_variables_from(invoker,
360                             "*",
361                             TESTONLY_AND_VISIBILITY + [
362                                   "features",
363                                   "deps",
364                                   "aliased_deps",
365                                   "public_deps",
366                                   "rustflags",
367                                   "rustenv",
368                                   "configs",
369                                   "unit_test_output_dir",
370                                   "unit_test_target",
371                                   "test_inputs",
372                                 ])
373
374      if (_main_target_suffix != "") {
375        # There's a group that depends on this target, and dependencies must
376        # be through that group.
377        visibility = [ ":$_target_name" ]
378        not_needed([ "_visibility" ])
379      } else if (defined(_visibility)) {
380        visibility = _visibility
381      }
382
383      testonly = _testonly
384      crate_name = _crate_name
385      crate_root = _crate_root
386      configs = []
387      configs = _configs
388      deps = _rust_deps + _cxx_generated_deps_for_rust
389      aliased_deps = _rust_aliased_deps
390      public_deps = _rust_public_deps
391      if (_main_target_suffix == "") {
392        # When these are not provided by a wrapper group target, they are added
393        # to the Rust target itself.
394        public_deps += _cxx_generated_deps_for_cpp
395      }
396      rustflags = _rustflags
397      if (_rustc_metadata != "") {
398        rustflags += [ "-Cmetadata=${_rustc_metadata}" ]
399      }
400      rustenv = _rustenv
401
402      if (_generate_crate_root) {
403        deps += [ ":${_target_name}_crate_root" ]
404        sources += [ _crate_root ]
405      }
406
407      if (!defined(output_name)) {
408        # Note that file names of libraries must start with the crate name in
409        # order for the compiler to find transitive dependencies in the
410        # directory search paths (since they are not all explicitly specified).
411        #
412        # For bin targets, we expect the target name to be unique, and the name
413        # of the exe should not add magic stuff to it. And bin crates can not be
414        # transitive dependencies.
415        if (invoker.target_type == "executable") {
416          output_name = _target_name
417        } else {
418          # TODO(danakj): Since the crate name includes the whole path for 1p
419          # libraries, we could move the output_dir to `root_out_dir` here for
420          # them, which would make for shorter file paths. But we need to not
421          # do the same for 3p crates or those with a `crate_name` set
422          # explicitly.
423          output_name = _crate_name
424        }
425      }
426
427      if (!_allow_unsafe) {
428        configs += [ "//build/rust:forbid_unsafe" ]
429      }
430    }
431
432    if (_cxx_bindings != []) {
433      rust_cxx("${_target_name}_cxx_generated") {
434        testonly = _testonly
435        visibility = [ ":${_target_name}" ]
436        if (defined(_visibility)) {
437          visibility += _visibility
438        }
439        sources = _cxx_bindings
440        deps = _cxx_deps + _public_deps
441        configs = _configs
442
443        if (is_component_build) {
444          # In a component_build the cxx bindings may be linked into a shared
445          # library at any point up the dependency tree, so always export.
446          export_symbols = true
447        } else if (invoker.target_type == "shared_library") {
448          export_symbols = true
449        } else {
450          export_symbols = false
451        }
452      }
453    } else {
454      not_needed([ "_cxx_deps" ])
455    }
456  }
457}
458