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