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.
5 import("//build/rust/rust_executable.gni")
6 import("//build/rust/rust_macro.gni")
7 import("//build/rust/rust_static_library.gni")
9 # This template allows for building Cargo crates within gn.
11 # It is intended for use with pre-existing (third party) code and
12 # is none too efficient. (It will stall the build pipeline whilst
13 # it runs build scripts to work out what flags are needed). First
14 # party code should directly use first-class gn targets, such as
15 # //build/rust/rust_static_library.gni or similar.
17 # Because it's intended for third-party code, it automatically
18 # defaults to //build/config/compiler:no_chromium_code which
19 # suppresses some warnings. If you *do* use this for first party
20 # code, you should remove that config and add the equivalent
21 # //build/config/compiler:chromium_code config.
29 # build_native_rust_unit_tests
32 # All just as in rust_static_library.gni
33 # library_configs/executable_configs
34 # All just as in rust_target.gni
37 # The major version of the library, which is used to differentiate between
38 # multiple versions of the same library name. This includes all leading 0s
39 # and the first non-zero value in the crate's version. This should be left
40 # as the default, which is "0", for first-party code unless there are
41 # multiple versions of a crate present. For third-party code, the version
42 # epoch (matching the directory it is found in) should be specified.
45 # 1.0.2 => epoch = "1"
46 # 4.2.0 => epoch = "4"
47 # 0.2.7 => epoch = "0.2"
48 # 0.0.3 => epoch = "0.0.3"
51 # Same meaning as test_deps in rust_static_library.gni, but called
52 # dev_deps to match Cargo.toml better.
54 # build_root (optional)
55 # Filename of build.rs build script.
57 # build_deps (optional)
58 # Build script dependencies
60 # build_sources (optional)
61 # List of sources for build script. Must be specified if
62 # build_root is specified.
64 # build_script_outputs (optional)
65 # List of .rs files generated by the build script, if any.
66 # Fine to leave undefined even if you have a build script.
67 # This doesn't directly correspond to any Cargo variable,
68 # but unfortunately is necessary for gn to build its dependency
69 # trees automatically.
70 # Many build scripts just output --cfg directives, in which case
71 # no source code is generated and this can remain empty.
73 # build_script_inputs (optional)
74 # If the build script reads any files generated by build_deps,
75 # as opposed to merely linking against them, add a list of such
76 # files here. Again, this doesn't correspond to a Cargo variable
77 # but is necessary for gn.
79 # crate_type "bin", "proc-macro" or "rlib" (optional)
80 # Whether to build an executable. The default is "rlib".
81 # At present others are not supported.
86 # cargo_pkg_description
87 # Strings as found within 'version' and similar fields within Cargo.toml.
88 # Converted to environment variables passed to rustc, in case the crate
89 # uses clap `crate_version!` or `crate_authors!` macros (fairly common in
90 # command line tool help)
92 template("cargo_crate") {
93 _orig_target_name = target_name
95 _crate_name = _orig_target_name
96 if (defined(invoker.crate_name)) {
97 _crate_name = invoker.crate_name
100 # Construct metadata from the crate epoch or an explicitly provided metadata
103 if (defined(invoker.rustc_metadata)) {
104 _rustc_metadata = invoker.rustc_metadata
105 } else if (defined(invoker.epoch)) {
106 _rustc_metadata = "${_crate_name}-${invoker.epoch}"
109 # Executables need to have unique names. Work out a prefix.
110 if (defined(invoker.build_root)) {
111 _epochlabel = "vunknown"
112 if (defined(invoker.epoch)) {
113 _tempepoch = string_replace(invoker.epoch, ".", "_")
114 _epochlabel = "v${_tempepoch}"
117 # This name includes the target name to ensure it's unique for each possible
118 # build target in the same BUILD.gn file.
120 "${_crate_name}_${target_name}_${_epochlabel}_build_script"
122 # Where the OUT_DIR will point when running the build script exe, and
123 # compiling the crate library/binaries. This directory must include the
124 # target name to avoid collisions between multiple GN targets that exist
125 # in the same BUILD.gn.
126 _build_script_env_out_dir = "$target_gen_dir/$target_name"
130 if (defined(invoker.rustenv)) {
131 _rustenv = invoker.rustenv
133 if (defined(invoker.cargo_pkg_authors)) {
134 _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
136 if (defined(invoker.cargo_pkg_version)) {
137 _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]
139 if (defined(invoker.cargo_pkg_name)) {
140 _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
142 if (defined(invoker.cargo_pkg_description)) {
143 _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
146 # Try to determine the CARGO_MANIFEST_DIR, preferring the directory
147 # with build.rs and otherwise assuming that the target contains a
148 # `crate/` subdirectory.
149 if (defined(invoker.build_root)) {
150 manifest_dir = rebase_path(get_path_info(invoker.build_root, "dir"), "")
152 build_gn_dir = get_label_info(target_name, "dir")
153 manifest_dir = rebase_path(build_gn_dir + "/crate", "")
155 _rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ]
157 # cargo_crate() should set library_configs, executable_configs,
158 # proc_macro_configs. Not configs.
159 assert(!defined(invoker.configs))
161 # Work out what we're building.
163 if (defined(invoker.crate_type)) {
164 _crate_type = invoker.crate_type
166 if (_crate_type == "cdylib") {
167 # Crates are rarely cdylibs. The example encountered so far aims
168 # to expose a C API to other code. In a Chromium context, we don't
169 # want to build that as a dylib for a couple of reasons:
170 # * rust_shared_library does not work on Mac. rustc does not know
171 # how to export the __llvm_profile_raw_version symbol.
172 # * even if it did work, this might require us to distribute extra
173 # binaries (.so/.dylib etc.)
174 # For the only case we've had so far, it makes more sense to build
175 # the code as a static library which we can then link into downstream
179 if (_crate_type == "bin") {
180 _target_type = "rust_executable"
181 assert(!defined(invoker.epoch))
182 if (defined(invoker.executable_configs)) {
183 _configs = invoker.executable_configs
185 } else if (_crate_type == "proc-macro") {
186 _target_type = "rust_macro"
187 if (defined(invoker.proc_macro_configs)) {
188 _configs = invoker.proc_macro_configs
191 assert(_crate_type == "rlib")
192 _target_type = "rust_static_library"
193 if (defined(invoker.library_configs)) {
194 _configs = invoker.library_configs
198 # The main target, either a Rust source set or an executable.
199 target(_target_type, target_name) {
200 forward_variables_from(invoker,
202 TESTONLY_AND_VISIBILITY + [
206 "build_script_inputs",
207 "build_script_outputs",
211 "executable_configs",
213 "proc_macro_configs",
217 forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
218 if (defined(crate_type) && crate_type == "cdylib") {
219 # See comments above about cdylib.
223 rustc_metadata = _rustc_metadata
225 # TODO(crbug.com/1422745): don't default to true. This requires changes to
226 # third_party.toml and gnrt when generating third-party build targets.
230 if (defined(_configs)) {
234 if (_crate_type == "rlib") {
235 # Forward configs for unit tests.
236 if (defined(invoker.executable_configs)) {
237 executable_configs = invoker.executable_configs
241 if (!defined(rustflags)) {
246 if (!defined(build_native_rust_unit_tests)) {
247 build_native_rust_unit_tests = _crate_type != "proc-macro"
249 if (build_native_rust_unit_tests) {
250 # Unit tests in a proc-macro crate type don't make sense, you can't
251 # compile executables against the `proc_macro` crate.
252 assert(_crate_type != "proc-macro")
255 # The unit tests for each target, if generated, should be unique as well.
256 # a) It needs to be unique even if multiple build targets have the same
257 # `crate_name`, but different target names.
258 # b) It needs to be unique even if multiple build targets have the same
259 # `crate_name` and target name, but different epochs.
260 _unit_test_unique_target_name = ""
261 if (_crate_name != _orig_target_name) {
262 _unit_test_unique_target_name = "${_orig_target_name}_"
264 _unit_test_unique_epoch = ""
265 if (defined(invoker.epoch)) {
266 _epoch_str = string_replace(invoker.epoch, ".", "_")
267 _unit_test_unique_epoch = "v${_epoch_str}_"
269 if (defined(output_dir) && output_dir != "") {
270 unit_test_output_dir = output_dir
272 unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"
274 if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") {
275 # Cargo crate rlibs can be compiled differently for tests, and must not
276 # collide with the production outputs. This does *not* override the
277 # unit_test_output_dir, which is set above, as that target is not an rlib.
278 output_dir = "$target_out_dir/$_orig_target_name"
281 if (defined(invoker.dev_deps)) {
282 test_deps = invoker.dev_deps
285 if (defined(invoker.build_root)) {
286 # Uh-oh, we have a build script
287 if (!defined(deps)) {
290 if (!defined(sources)) {
293 if (!defined(inputs)) {
297 # This... is a bit weird. We generate a file called cargo_flags.rs which
298 # does not actually contain Rust code, but instead some flags to add
299 # to the rustc command line. We need it to end in a .rs extension so that
300 # we can include it in the 'sources' line and thus have dependency
301 # calculation done correctly. data_deps won't work because targets don't
302 # require them to be present until runtime.
303 flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
304 rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
305 sources += [ flags_file ]
306 if (defined(invoker.build_script_outputs)) {
307 # Build scripts may output arbitrary files. They are usually included in
308 # the main Rust target using include! or include_str! and therefore the
309 # filename may be .rs or may be arbitrary. We want to educate ninja
310 # about the dependency either way.
311 foreach(extra_source,
312 filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
313 sources += [ "$_build_script_env_out_dir/$extra_source" ]
315 foreach(extra_source,
316 filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
317 inputs += [ "$_build_script_env_out_dir/$extra_source" ]
320 deps += [ ":${_build_script_name}_output" ]
324 if (defined(invoker.build_root)) {
325 # Extra targets required to make build script work
326 action("${_build_script_name}_output") {
327 script = rebase_path("//build/rust/run_build_script.py")
328 build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
329 deps = [ build_script_target ]
331 # The build script may be built with a different toolchain when
332 # cross-compiling (the host toolchain) so we must find the path relative
334 _build_script_root_out_dir =
335 get_label_info(build_script_target, "root_out_dir")
336 _build_script_exe = "$_build_script_root_out_dir/$_build_script_name"
338 # The executable is always built with the `rust_macro_toolchain` which
339 # targets the `host_os`. The rule here is on the `target_toolchain` which
340 # can be different (e.g. compiling on Linux, targeting Windows).
341 if (host_os == "win") {
342 _build_script_exe = "${_build_script_exe}.exe"
345 _flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
347 inputs = [ _build_script_exe ]
348 outputs = [ _flags_file ]
351 rebase_path(_build_script_exe, root_build_dir),
353 rebase_path(_flags_file, root_build_dir),
355 rebase_path("${rust_sysroot}/bin", root_build_dir),
357 rebase_path(_build_script_env_out_dir, root_build_dir),
359 rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
361 if (defined(rust_abi_target) && rust_abi_target != "") {
367 if (defined(invoker.features)) {
368 args += [ "--features" ]
369 args += invoker.features
371 if (defined(invoker.build_script_outputs)) {
372 args += [ "--generated-files" ]
373 args += invoker.build_script_outputs
374 foreach(generated_file, invoker.build_script_outputs) {
375 outputs += [ "$_build_script_env_out_dir/$generated_file" ]
378 if (_rustenv != []) {
382 if (defined(invoker.build_script_inputs)) {
383 inputs += invoker.build_script_inputs
387 if (toolchain_for_rust_host_build_tools) {
388 # The build script is only available to be built on the host, and we use
389 # the rust_macro_toolchain for it to unblock building them while the
390 # Chromium stdlib is still being compiled.
391 rust_executable(_build_script_name) {
392 sources = invoker.build_sources
393 crate_root = invoker.build_root
394 if (defined(invoker.build_deps)) {
395 deps = invoker.build_deps
397 if (defined(invoker.build_script_inputs)) {
398 inputs = invoker.build_script_inputs
401 # The ${_build_script_name}_output target looks for the exe in this
402 # location. Due to how the Windows component build works, this has to
403 # be $root_out_dir for all EXEs. In component build, C++ links to the
404 # CRT as a DLL, and if Rust does not match, we can't link mixed target
405 # Rust EXE/DLLs, as the headers in C++ said something different than
406 # what Rust links. Since the CRT DLL is placed in the $root_out_dir,
407 # an EXE can find it if it's also placed in that dir.
408 output_dir = root_out_dir
410 forward_variables_from(invoker,
417 "//build/config/compiler:chromium_code",
419 # Avoid generating profiling data for build scripts.
421 # TODO(crbug.com/1426472): determine for sure whether to remove this
422 # config. I'm not sure of the overlap between PGO instrumentation and
423 # code coverage instrumentation, but we definitely don't want build
424 # script coverage for PGO, while we might for test coverage metrics.
426 # If we do include build script output in test metrics, it could be
427 # misleading: exercising some code from a build script doesn't give us
428 # the same signal as an actual test.
429 "//build/config/coverage:default_coverage",
431 configs += [ "//build/config/compiler:no_chromium_code" ]
439 "build_script_inputs",
440 "build_script_outputs",
445 "_name_specific_output_dir",
451 set_defaults("cargo_crate") {
452 library_configs = default_compiler_configs
453 executable_configs = default_executable_configs
454 proc_macro_configs = default_rust_proc_macro_configs