• 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
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  _crate_name = target_name
37  if (defined(invoker.crate_name)) {
38    _crate_name = invoker.crate_name
39  }
40  _generate_crate_root =
41      defined(invoker.generate_crate_root) && invoker.generate_crate_root
42
43  # Only one of `crate_root` or `generate_crate_root` can be specified, or
44  # neither.
45  assert(!defined(invoker.crate_root) || !_generate_crate_root)
46
47  if (defined(invoker.output_dir) && invoker.output_dir != "") {
48    # This is where the build target (.exe, .rlib, etc) goes.
49    _output_dir = invoker.output_dir
50  }
51
52  # This is where the OUT_DIR environment variable points to when running a
53  # build script and when compiling the build target, for consuming generated
54  # files.
55  _env_out_dir = "$target_gen_dir/$_target_name"
56
57  _allow_unsafe = false
58  if (defined(invoker.allow_unsafe)) {
59    _allow_unsafe = invoker.allow_unsafe
60  }
61
62  if (_generate_crate_root) {
63    generated_file("${_target_name}_crate_root") {
64      outputs = [ "${target_gen_dir}/${target_name}.rs" ]
65      contents = [
66        "// Generated crate root for ${_target_name}.",
67        "// @generated",
68        "",
69      ]
70      foreach(rs, invoker.sources) {
71        rs_path_from_root = rebase_path(rs, target_gen_dir)
72        contents += [ "#[path = \"${rs_path_from_root}\"]" ]
73
74        # Drop the file extension from the module name.
75        rs_modname = string_replace(rs, ".rs", "")
76
77        # Replace invalid "/" chars in the source file path.
78        rs_modname = string_replace(rs_modname, "/", "_")
79
80        # Since source files are specified relative to the BUILD.gn they may
81        # also have ".." path components.
82        rs_modname = string_replace(rs_modname, "..", "dotdot")
83        contents += [
84          "mod ${rs_modname};",
85          "",
86        ]
87      }
88    }
89    _generated_crate_root = get_target_outputs(":${_target_name}_crate_root")
90    _crate_root = _generated_crate_root[0]
91  } else if (defined(invoker.crate_root)) {
92    _crate_root = invoker.crate_root
93  } else if (invoker.target_type == "executable") {
94    _crate_root = "src/main.rs"
95  } else {
96    _crate_root = "src/lib.rs"
97  }
98
99  _testonly = false
100  if (defined(invoker.testonly)) {
101    _testonly = invoker.testonly
102  }
103  if (defined(invoker.visibility)) {
104    _visibility = invoker.visibility
105  }
106
107  _rustflags = []
108  if (defined(invoker.rustflags)) {
109    _rustflags += invoker.rustflags
110  }
111  if (defined(invoker.features)) {
112    foreach(i, invoker.features) {
113      _rustflags += [ "--cfg=feature=\"${i}\"" ]
114    }
115  }
116  _edition = "2021"
117  if (defined(invoker.edition)) {
118    _edition = invoker.edition
119  }
120
121  assert(!defined(configs))
122  _configs = [ "//build/rust:edition_${_edition}" ]
123  _test_configs = []
124  if (invoker.target_type == "executable") {
125    _configs += invoker.executable_configs
126  } else if (invoker.target_type == "rust_proc_macro") {
127    _configs += invoker.proc_macro_configs
128    _test_configs += [ "//build/rust:proc_macro_extern" ]
129  } else if (invoker.target_type == "shared_library") {
130    _configs += invoker.shared_library_configs
131  } else {
132    _configs += invoker.library_configs
133  }
134
135  if (invoker.target_type == "rust_proc_macro") {
136    _main_target_suffix = "${target_name}__proc_macro"
137  } else {
138    _main_target_suffix = "__rlib"
139  }
140
141  _deps = []
142  if (defined(invoker.deps)) {
143    _deps += invoker.deps
144  }
145  _public_deps = []
146  if (defined(invoker.public_deps)) {
147    _public_deps += invoker.public_deps
148  }
149  if (defined(invoker.aliased_deps)) {
150    _aliased_deps = invoker.aliased_deps
151  } else {
152    _aliased_deps = {
153    }
154  }
155
156  _is_data_dep = defined(invoker.is_data_dep) && invoker.is_data_dep
157
158  _build_unit_tests = false
159  if (defined(invoker.build_native_rust_unit_tests)) {
160    _build_unit_tests =
161        invoker.build_native_rust_unit_tests && can_build_rust_unit_tests
162  }
163
164  # Declares that the Rust crate generates bindings between C++ and Rust via the
165  # Cxx crate. It may generate C++ headers and/or use the cxx crate macros to
166  # generate Rust code internally, depending on what bindings are declared. If
167  # set, it's a set of rust files that include Cxx bindings declarations.
168  _cxx_bindings = []
169  assert(!defined(invoker.cxx_bindings) || enable_cxx,
170         "cxx bindings are not supported when building rust targets " +
171             "outside the Chromium build.")
172  if (defined(invoker.cxx_bindings)) {
173    _cxx_bindings = invoker.cxx_bindings
174  }
175  _rustenv = [ "OUT_DIR=" +
176               rebase_path(_env_out_dir, get_path_info(_crate_root, "dir")) ]
177  if (defined(invoker.rustenv)) {
178    _rustenv += invoker.rustenv
179  }
180
181  # We require that all source files are listed, even though this is
182  # not a requirement for rustc. The reason is to ensure that tools
183  # such as `gn deps` give the correct answer, and thus we trigger
184  # the right test suites etc. on code change.
185  # TODO(crbug.com/1256930) - verify this is correct
186  assert(defined(invoker.sources), "sources must be listed")
187
188  if (invoker.target_type == "rust_proc_macro" &&
189      !toolchain_for_rust_host_build_tools) {
190    # Redirect to the proc macro toolchain, which uses prebuilt stdlib libraries
191    # that are not built with panic=abort.
192    group(_target_name) {
193      testonly = _testonly
194      if (defined(_visibility)) {
195        visibility = _visibility
196      }
197      public_deps =
198          [ ":${_target_name}${_main_target_suffix}($rust_macro_toolchain)" ]
199    }
200
201    not_needed(invoker, "*")
202    not_needed([
203                 "_allow_unsafe",
204                 "_build_unit_tests",
205                 "_crate_root",
206                 "_crate_name",
207                 "_cxx_bindings",
208                 "_deps",
209                 "_aliased_deps",
210                 "_is_data_dep",
211                 "_rustc_metadata",
212                 "_out_dir",
213                 "_public_deps",
214                 "_rustenv",
215                 "_rustflags",
216                 "_support_use_from_cpp",
217                 "_testonly",
218                 "_visibility",
219               ])
220  } else {
221    _rustc_metadata = ""
222    if (defined(invoker.rustc_metadata)) {
223      _rustc_metadata = invoker.rustc_metadata
224    }
225
226    # Add a metadata-influenced suffix to the output name for libraries only.
227    _output_suffix = ""
228    if (invoker.target_type == "rust_library" && _rustc_metadata != "") {
229      _output_suffix = "-${_rustc_metadata}"
230    }
231
232    group(_target_name) {
233      testonly = _testonly
234      if (defined(_visibility)) {
235        visibility = _visibility
236      }
237
238      # Both the C++ bindings (if present) and the Rust crate should be treated
239      # like direct dependencies, so we expose them both in public_deps.
240      public_deps = [ ":${_target_name}${_main_target_suffix}" ]
241
242      # TODO(danakj): This would not be needed if we stopped forwarding through
243      # a group in the common (non-procmacro) case.
244      if (_is_data_dep) {
245        data_deps = [ ":${_target_name}${_main_target_suffix}" ]
246      }
247
248      if (_cxx_bindings != []) {
249        public_deps += [ ":${_target_name}_cxx_generated" ]
250
251        # Additionally, C++ bindings generated by Cxx can include C++ types
252        # that come from the Cxx library, such as `rust::Str`. So any C++
253        # target that depends on a rust target directly may need access to Cxx
254        # as well, which means it must appear in public_deps.
255        public_deps += [ "//build/rust:cxx_cppdeps" ]
256      } else if (!defined(invoker.no_std) || !invoker.no_std) {
257        # If C++ depends on and links in the library, we need to make sure C++
258        # links in the Rust stdlib. This is orthogonal to if the library exports
259        # bindings for C++ to use.
260        deps = [ "//build/rust/std:stdlib_for_clang" ]
261      }
262    }
263
264    _rust_deps = _deps
265    _rust_aliased_deps = _aliased_deps
266    _rust_public_deps = _public_deps
267    _cxx_deps = _deps
268
269    # The Rust target (and unit tests) need the Cxx crate when using it to
270    # generate bindings.
271    if (_cxx_bindings != []) {
272      _rust_deps += [ "//build/rust:cxx_rustdeps" ]
273
274      # C++ targets can depend on the Rust target from the BUILD.gn file to
275      # access the headers generated from it
276      _rust_public_deps += [ ":${_target_name}_cxx_generated" ]
277    }
278
279    if (!defined(invoker.no_std) || !invoker.no_std) {
280      _rust_deps += [ "//build/rust/std:stdlib_for_rustc" ]
281    }
282
283    # You must go through the groups above to get to these targets.
284    _visibility = []
285    _visibility = [ ":${_target_name}" ]
286
287    if (_build_unit_tests) {
288      _unit_test_target = "${_target_name}_unittests"
289      if (defined(invoker.unit_test_target)) {
290        _unit_test_target = invoker.unit_test_target
291      }
292
293      rust_unit_test(_unit_test_target) {
294        testonly = true
295        crate_root = _crate_root
296        sources = invoker.sources + [ crate_root ]
297        rustflags = _rustflags
298        env_out_dir = _env_out_dir
299        if (defined(invoker.unit_test_output_dir)) {
300          output_dir = invoker.unit_test_output_dir
301        }
302        deps = _rust_deps + _public_deps
303        aliased_deps = _rust_aliased_deps
304        public_deps = [ ":${_target_name}" ]
305        if (defined(invoker.test_deps)) {
306          deps += invoker.test_deps
307        }
308        inputs = []
309        if (defined(invoker.inputs)) {
310          inputs += invoker.inputs
311        }
312        if (defined(invoker.test_inputs)) {
313          inputs += invoker.test_inputs
314        }
315        if (defined(invoker.executable_configs)) {
316          configs = []
317          configs += invoker.executable_configs
318        }
319        configs += _test_configs
320        rustenv = _rustenv
321
322        if (!_allow_unsafe) {
323          configs += [ "//build/rust:forbid_unsafe" ]
324        }
325      }
326    } else {
327      not_needed([
328                   "_crate_root",
329                   "_crate_name",
330                   "_rustc_metadata",
331                   "_test_configs",
332                 ])
333      not_needed(invoker, [ "executable_configs" ])
334    }
335
336    target(invoker.target_type, "${_target_name}${_main_target_suffix}") {
337      forward_variables_from(invoker,
338                             "*",
339                             TESTONLY_AND_VISIBILITY + [
340                                   "features",
341                                   "deps",
342                                   "aliased_deps",
343                                   "public_deps",
344                                   "rustflags",
345                                   "rustenv",
346                                   "configs",
347                                   "unit_test_output_dir",
348                                   "unit_test_target",
349                                   "test_inputs",
350                                 ])
351
352      testonly = _testonly
353      visibility = _visibility
354      crate_name = _crate_name
355      crate_root = _crate_root
356      configs = []
357      configs = _configs
358      deps = _rust_deps
359      aliased_deps = _rust_aliased_deps
360      public_deps = _rust_public_deps
361      rustflags = _rustflags
362      if (_rustc_metadata != "") {
363        rustflags += [ "-Cmetadata=${_rustc_metadata}" ]
364      }
365      rustenv = _rustenv
366
367      if (_generate_crate_root) {
368        deps += [ ":${_target_name}_crate_root" ]
369        sources += [ _crate_root ]
370      }
371
372      # The Rust tool() declarations, like C++ ones, use the output_name and
373      # output_dir, so that GN targets can override these if needed. Here we
374      # give them their default values, or allow them to be overridden.
375      if (defined(_output_dir)) {
376        output_dir = _output_dir
377      }
378      if (!defined(output_name) || output_name == "") {
379        output_name = "${crate_name}${_output_suffix}"
380      }
381
382      if (!_allow_unsafe) {
383        configs += [ "//build/rust:forbid_unsafe" ]
384      }
385    }
386
387    if (_cxx_bindings != []) {
388      rust_cxx("${_target_name}_cxx_generated") {
389        testonly = _testonly
390        visibility = [ ":${_target_name}${_main_target_suffix}" ]
391        if (defined(_visibility)) {
392          visibility += _visibility
393        }
394        sources = _cxx_bindings
395        deps = _cxx_deps + _public_deps
396        configs = _configs
397
398        if (is_component_build) {
399          # In a component_build the cxx bindings may be linked into a shared
400          # library at any point up the dependency tree, so always export.
401          export_symbols = true
402        } else if (invoker.target_type == "shared_library") {
403          export_symbols = true
404        } else {
405          export_symbols = false
406        }
407      }
408    } else {
409      not_needed([ "_cxx_deps" ])
410    }
411  }
412}
413