xref: /aosp_15_r20/external/cronet/build/rust/cargo_crate.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/rust/rust_executable.gni")
6import("//build/rust/rust_macro.gni")
7import("//build/rust/rust_static_library.gni")
8
9# This template allows for building Cargo crates within gn.
10#
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.
16#
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.
22#
23# Arguments:
24#  sources
25#  crate_root
26#  deps
27#  aliased_deps
28#  features
29#  build_native_rust_unit_tests
30#  edition
31#  crate_name
32#    All just as in rust_static_library.gni
33#  library_configs/executable_configs
34#    All just as in rust_target.gni
35#
36#  epoch (optional)
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.
43#
44#    Examples:
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"
49#
50#  dev_deps
51#    Same meaning as test_deps in rust_static_library.gni, but called
52#    dev_deps to match Cargo.toml better.
53#
54#  build_root (optional)
55#    Filename of build.rs build script.
56#
57#  build_deps (optional)
58#    Build script dependencies
59#
60#  build_sources (optional)
61#    List of sources for build script. Must be specified if
62#    build_root is specified.
63#
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.
72#
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.
78#
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.
82#
83#  cargo_pkg_authors
84#  cargo_pkg_version
85#  cargo_pkg_name
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)
91
92template("cargo_crate") {
93  _orig_target_name = target_name
94
95  _crate_name = _orig_target_name
96  if (defined(invoker.crate_name)) {
97    _crate_name = invoker.crate_name
98  }
99
100  # Construct metadata from the crate epoch or an explicitly provided metadata
101  # field.
102  _rustc_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}"
107  }
108
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}"
115    }
116
117    # This name includes the target name to ensure it's unique for each possible
118    # build target in the same BUILD.gn file.
119    _build_script_name =
120        "${_crate_name}_${target_name}_${_epochlabel}_build_script"
121
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"
127  }
128
129  _rustenv = []
130  if (defined(invoker.rustenv)) {
131    _rustenv = invoker.rustenv
132  }
133  if (defined(invoker.cargo_pkg_authors)) {
134    _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
135  }
136  if (defined(invoker.cargo_pkg_version)) {
137    _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]
138  }
139  if (defined(invoker.cargo_pkg_name)) {
140    _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
141  }
142  if (defined(invoker.cargo_pkg_description)) {
143    _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
144  }
145
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 = "."
151  } else {
152    build_gn_dir = get_label_info(target_name, "dir")
153    manifest_dir = rebase_path(build_gn_dir + "/crate", root_build_dir)
154  }
155  _rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ]
156
157  # cargo_crate() should set library_configs, executable_configs,
158  # proc_macro_configs. Not configs.
159  assert(!defined(invoker.configs))
160
161  # Work out what we're building.
162  _crate_type = "rlib"
163  if (defined(invoker.crate_type)) {
164    _crate_type = invoker.crate_type
165  }
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
176    # binaries.
177    _crate_type = "rlib"
178  }
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
184    }
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
189    }
190  } else {
191    assert(_crate_type == "rlib")
192    _target_type = "rust_static_library"
193    if (defined(invoker.library_configs)) {
194      _configs = invoker.library_configs
195    }
196  }
197
198  if (defined(invoker.output_name)) {
199    _output_name = invoker.output_name
200  } else if (_crate_type != "bin") {
201    # Note that file names of libraries must start with the crate name in
202    # order for the compiler to find transitive dependencies in the
203    # directory search paths (since they are not all explicitly specified).
204    #
205    # For bin targets, we expect the target name to be unique, and the name
206    # of the exe should not add magic stuff to it. And bin crates can not be
207    # transitive dependencies.
208    _output_name = "${_crate_name}_${_orig_target_name}"
209  }
210
211  _testonly = false
212  if (defined(invoker.testonly)) {
213    _testonly = invoker.testonly
214  }
215
216  # The main target, either a Rust source set or an executable.
217  target(_target_type, target_name) {
218    forward_variables_from(invoker,
219                           "*",
220                           TESTONLY_AND_VISIBILITY + [
221                                 "build_root",
222                                 "build_deps",
223                                 "build_sources",
224                                 "build_script_inputs",
225                                 "build_script_outputs",
226                                 "epoch",
227                                 "unit_test_target",
228                                 "configs",
229                                 "executable_configs",
230                                 "library_configs",
231                                 "proc_macro_configs",
232                                 "rustenv",
233                                 "dev_deps",
234                               ])
235
236    testonly = _testonly
237    if (defined(invoker.visibility)) {
238      visibility = invoker.visibility
239    }
240    if (defined(crate_type) && crate_type == "cdylib") {
241      # See comments above about cdylib.
242      crate_type = "rlib"
243    }
244    crate_name = _crate_name
245
246    if (defined(_output_name)) {
247      output_name = _output_name
248    }
249
250    # Don't import the `chromium` crate into third-party code.
251    no_chromium_prelude = true
252
253    rustc_metadata = _rustc_metadata
254
255    # TODO(crbug.com/1422745): don't default to true. This requires changes to
256    # third_party.toml and gnrt when generating third-party build targets.
257    allow_unsafe = true
258
259    configs = []
260    if (defined(_configs)) {
261      configs += _configs
262    }
263
264    if (_crate_type == "rlib") {
265      # Forward configs for unit tests.
266      if (defined(invoker.executable_configs)) {
267        executable_configs = invoker.executable_configs
268      }
269    }
270
271    if (!defined(rustflags)) {
272      rustflags = []
273    }
274    rustenv = _rustenv
275
276    if (!defined(build_native_rust_unit_tests)) {
277      build_native_rust_unit_tests = _crate_type != "proc-macro"
278    }
279    if (build_native_rust_unit_tests) {
280      # Unit tests in a proc-macro crate type don't make sense, you can't
281      # compile executables against the `proc_macro` crate.
282      assert(_crate_type != "proc-macro")
283    }
284
285    # The unit tests for each target, if generated, should be unique as well.
286    # a) It needs to be unique even if multiple build targets have the same
287    #    `crate_name`, but different target names.
288    # b) It needs to be unique even if multiple build targets have the same
289    #    `crate_name` and target name, but different epochs.
290    _unit_test_unique_target_name = ""
291    if (_crate_name != _orig_target_name) {
292      _unit_test_unique_target_name = "${_orig_target_name}_"
293    }
294    _unit_test_unique_epoch = ""
295    if (defined(invoker.epoch)) {
296      _epoch_str = string_replace(invoker.epoch, ".", "_")
297      _unit_test_unique_epoch = "v${_epoch_str}_"
298    }
299    if (defined(output_dir) && output_dir != "") {
300      unit_test_output_dir = output_dir
301    }
302    unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"
303
304    if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") {
305      # Cargo crate rlibs can be compiled differently for tests, and must not
306      # collide with the production outputs. This does *not* override the
307      # unit_test_output_dir, which is set above, as that target is not an rlib.
308      output_dir = "$target_out_dir/$_orig_target_name"
309    }
310
311    if (defined(invoker.dev_deps)) {
312      test_deps = invoker.dev_deps
313    }
314
315    if (defined(invoker.build_root)) {
316      # Uh-oh, we have a build script
317      if (!defined(deps)) {
318        deps = []
319      }
320      if (!defined(sources)) {
321        sources = []
322      }
323      if (!defined(inputs)) {
324        inputs = []
325      }
326
327      # This... is a bit weird. We generate a file called cargo_flags.rs which
328      # does not actually contain Rust code, but instead some flags to add
329      # to the rustc command line. We need it to end in a .rs extension so that
330      # we can include it in the 'sources' line and thus have dependency
331      # calculation done correctly. data_deps won't work because targets don't
332      # require them to be present until runtime.
333      flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
334      rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
335      sources += [ flags_file ]
336      if (defined(invoker.build_script_outputs)) {
337        # Build scripts may output arbitrary files. They are usually included in
338        # the main Rust target using include! or include_str! and therefore the
339        # filename may be .rs or may be arbitrary. We want to educate ninja
340        # about the dependency either way.
341        foreach(extra_source,
342                filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
343          sources += [ "$_build_script_env_out_dir/$extra_source" ]
344        }
345        foreach(extra_source,
346                filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
347          inputs += [ "$_build_script_env_out_dir/$extra_source" ]
348        }
349      }
350      deps += [ ":${_build_script_name}_output" ]
351    }
352  }
353
354  if (defined(invoker.build_root)) {
355    # Extra targets required to make build script work
356    action("${_build_script_name}_output") {
357      script = rebase_path("//build/rust/run_build_script.py")
358      build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
359      deps = [ build_script_target ]
360      testonly = _testonly
361      if (defined(invoker.visibility)) {
362        visibility = invoker.visibility
363      }
364
365      # The build script may be built with a different toolchain when
366      # cross-compiling (the host toolchain) so we must find the path relative
367      # to that.
368      _build_script_root_out_dir =
369          get_label_info(build_script_target, "root_out_dir")
370      _build_script_exe = "$_build_script_root_out_dir/$_build_script_name"
371
372      # The executable is always built with the `rust_macro_toolchain` which
373      # targets the `host_os`. The rule here is on the `target_toolchain` which
374      # can be different (e.g. compiling on Linux, targeting Windows).
375      if (host_os == "win") {
376        _build_script_exe = "${_build_script_exe}.exe"
377      }
378
379      _flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
380
381      inputs = [ _build_script_exe ]
382      outputs = [ _flags_file ]
383      args = [
384        "--build-script",
385        rebase_path(_build_script_exe, root_build_dir),
386        "--output",
387        rebase_path(_flags_file, root_build_dir),
388        "--rust-prefix",
389        rebase_path("${rust_sysroot}/bin", root_build_dir),
390        "--out-dir",
391        rebase_path(_build_script_env_out_dir, root_build_dir),
392        "--src-dir",
393        rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
394      ]
395      if (defined(rust_abi_target) && rust_abi_target != "") {
396        args += [
397          "--target",
398          rust_abi_target,
399        ]
400      }
401      if (defined(invoker.features)) {
402        args += [ "--features" ]
403        args += invoker.features
404      }
405      if (defined(invoker.build_script_outputs)) {
406        args += [ "--generated-files" ]
407        args += invoker.build_script_outputs
408        foreach(generated_file, invoker.build_script_outputs) {
409          outputs += [ "$_build_script_env_out_dir/$generated_file" ]
410        }
411      }
412      if (_rustenv != []) {
413        args += [ "--env" ]
414        args += _rustenv
415      }
416      if (defined(invoker.build_script_inputs)) {
417        inputs += invoker.build_script_inputs
418      }
419    }
420
421    if (toolchain_for_rust_host_build_tools) {
422      # The build script is only available to be built on the host, and we use
423      # the rust_macro_toolchain for it to unblock building them while the
424      # Chromium stdlib is still being compiled.
425      rust_executable(_build_script_name) {
426        crate_name = _build_script_name
427        sources = invoker.build_sources
428        crate_root = invoker.build_root
429        testonly = _testonly
430        if (defined(invoker.visibility)) {
431          visibility = invoker.visibility
432        }
433        if (defined(invoker.build_deps)) {
434          deps = invoker.build_deps
435        }
436        if (defined(invoker.build_script_inputs)) {
437          inputs = invoker.build_script_inputs
438        }
439
440        # Don't import the `chromium` crate into third-party code.
441        no_chromium_prelude = true
442
443        # The ${_build_script_name}_output target looks for the exe in this
444        # location. Due to how the Windows component build works, this has to
445        # be $root_out_dir for all EXEs. In component build, C++ links to the
446        # CRT as a DLL, and if Rust does not match, we can't link mixed target
447        # Rust EXE/DLLs, as the headers in C++ said something different than
448        # what Rust links. Since the CRT DLL is placed in the $root_out_dir,
449        # an EXE can find it if it's also placed in that dir.
450        output_dir = root_out_dir
451        rustenv = _rustenv
452        forward_variables_from(invoker,
453                               [
454                                 "features",
455                                 "edition",
456                                 "rustflags",
457                               ])
458        configs -= [
459          "//build/config/compiler:chromium_code",
460
461          # Avoid generating profiling data for build scripts.
462          #
463          # TODO(crbug.com/1426472): determine for sure whether to remove this
464          # config. I'm not sure of the overlap between PGO instrumentation and
465          # code coverage instrumentation, but we definitely don't want build
466          # script coverage for PGO, while we might for test coverage metrics.
467          #
468          # If we do include build script output in test metrics, it could be
469          # misleading: exercising some code from a build script doesn't give us
470          # the same signal as an actual test.
471          "//build/config/coverage:default_coverage",
472        ]
473        configs += [ "//build/config/compiler:no_chromium_code" ]
474      }
475    } else {
476      not_needed(invoker,
477                 [
478                   "build_sources",
479                   "build_deps",
480                   "build_root",
481                   "build_script_inputs",
482                   "build_script_outputs",
483                 ])
484    }
485  } else {
486    not_needed([
487                 "_name_specific_output_dir",
488                 "_orig_target_name",
489               ])
490  }
491}
492
493set_defaults("cargo_crate") {
494  library_configs = default_compiler_configs
495  executable_configs = default_executable_configs
496  proc_macro_configs = default_rust_proc_macro_configs
497}
498