xref: /aosp_15_r20/external/angle/build/rust/cargo_crate.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/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#  native_libs (optional)
80#    Paths to library files that need to be in the linking search path when
81#    depending on the crate's library, as it links against them via #[link]
82#    directives.
83#
84#  crate_type "bin", "proc-macro" or "rlib" (optional)
85#    Whether to build an executable. The default is "rlib".
86#    At present others are not supported.
87#
88#  cargo_pkg_authors
89#  cargo_pkg_version
90#  cargo_pkg_name
91#  cargo_pkg_description
92#    Strings as found within 'version' and similar fields within Cargo.toml.
93#    Converted to environment variables passed to rustc, in case the crate
94#    uses clap `crate_version!` or `crate_authors!` macros (fairly common in
95#    command line tool help)
96
97template("cargo_crate") {
98  _orig_target_name = target_name
99
100  _crate_name = _orig_target_name
101  if (defined(invoker.crate_name)) {
102    _crate_name = invoker.crate_name
103  }
104
105  # Construct metadata from the crate epoch or an explicitly provided metadata
106  # field.
107  _rustc_metadata = ""
108  if (defined(invoker.rustc_metadata)) {
109    _rustc_metadata = invoker.rustc_metadata
110  } else if (defined(invoker.epoch)) {
111    _rustc_metadata = "${_crate_name}-${invoker.epoch}"
112  }
113
114  # Executables need to have unique names. Work out a prefix.
115  if (defined(invoker.build_root)) {
116    _epochlabel = "vunknown"
117    if (defined(invoker.epoch)) {
118      _tempepoch = string_replace(invoker.epoch, ".", "_")
119      _epochlabel = "v${_tempepoch}"
120    }
121
122    # This name includes the target name to ensure it's unique for each possible
123    # build target in the same BUILD.gn file.
124    _build_script_name =
125        "${_crate_name}_${target_name}_${_epochlabel}_build_script"
126
127    # Where the OUT_DIR will point when running the build script exe, and
128    # compiling the crate library/binaries. This directory must include the
129    # target name to avoid collisions between multiple GN targets that exist
130    # in the same BUILD.gn.
131    _build_script_env_out_dir = "$target_gen_dir/$target_name"
132  }
133
134  _rustenv = []
135  if (defined(invoker.rustenv)) {
136    _rustenv = invoker.rustenv
137  }
138  if (defined(invoker.cargo_pkg_authors)) {
139    _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
140  }
141  if (defined(invoker.cargo_pkg_version)) {
142    _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]
143  }
144  if (defined(invoker.cargo_pkg_name)) {
145    _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
146  }
147  if (defined(invoker.cargo_pkg_description)) {
148    _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
149  }
150
151  # Try to determine the CARGO_MANIFEST_DIR, preferring the directory
152  # with build.rs and otherwise assuming that the target contains a
153  # `crate/` subdirectory.
154  if (defined(invoker.build_root)) {
155    manifest_dir = "."
156  } else {
157    build_gn_dir = get_label_info(target_name, "dir")
158    manifest_dir = rebase_path(build_gn_dir + "/crate", root_build_dir)
159  }
160  _rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ]
161
162  # cargo_crate() should set library_configs, executable_configs,
163  # proc_macro_configs. Not configs.
164  assert(!defined(invoker.configs))
165
166  # Work out what we're building.
167  _crate_type = "rlib"
168  if (defined(invoker.crate_type)) {
169    _crate_type = invoker.crate_type
170  }
171  if (_crate_type == "cdylib") {
172    # Crates are rarely cdylibs. The example encountered so far aims
173    # to expose a C API to other code. In a Chromium context, we don't
174    # want to build that as a dylib for a couple of reasons:
175    # * rust_shared_library does not work on Mac. rustc does not know
176    #   how to export the __llvm_profile_raw_version symbol.
177    # * even if it did work, this might require us to distribute extra
178    #   binaries (.so/.dylib etc.)
179    # For the only case we've had so far, it makes more sense to build
180    # the code as a static library which we can then link into downstream
181    # binaries.
182    _crate_type = "rlib"
183  }
184  if (_crate_type == "bin") {
185    _target_type = "rust_executable"
186    assert(!defined(invoker.epoch))
187    _configs = invoker.executable_configs
188  } else if (_crate_type == "proc-macro") {
189    _target_type = "rust_macro"
190    _configs = invoker.proc_macro_configs
191  } else {
192    assert(_crate_type == "rlib")
193    _target_type = "rust_static_library"
194    _configs = invoker.library_configs
195  }
196
197  if (defined(invoker.output_name)) {
198    _output_name = invoker.output_name
199  } else if (_crate_type != "bin") {
200    # Note that file names of libraries must start with the crate name in
201    # order for the compiler to find transitive dependencies in the
202    # directory search paths (since they are not all explicitly specified).
203    #
204    # For bin targets, we expect the target name to be unique, and the name
205    # of the exe should not add magic stuff to it. And bin crates can not be
206    # transitive dependencies.
207    _output_name = "${_crate_name}_${_orig_target_name}"
208  }
209
210  _testonly = false
211  if (defined(invoker.testonly)) {
212    _testonly = invoker.testonly
213  }
214
215  if (defined(invoker.native_libs)) {
216    _native_libs_action = "copy_${target_name}_native_libs"
217    _native_libs_config = "config_${target_name}_native_libs"
218    _native_libs_dir =
219        "${root_out_dir}/rustlib/${_crate_name}_${target_name}_${_epochlabel}"
220
221    copy(_native_libs_action) {
222      testonly = _testonly
223      visibility = [ ":$target_name" ]
224      sources = invoker.native_libs
225      outputs = [ "${_native_libs_dir}/{{source_file_part}}" ]
226    }
227    config(_native_libs_config) {
228      lib_dirs = [ "${_native_libs_dir}" ]
229    }
230  }
231
232  # The main target, either a Rust source set or an executable.
233  target(_target_type, target_name) {
234    forward_variables_from(invoker,
235                           "*",
236                           TESTONLY_AND_VISIBILITY + [
237                                 "build_root",
238                                 "build_deps",
239                                 "build_sources",
240                                 "build_script_inputs",
241                                 "build_script_outputs",
242                                 "epoch",
243                                 "unit_test_target",
244                                 "configs",
245                                 "executable_configs",
246                                 "library_configs",
247                                 "proc_macro_configs",
248                                 "rustenv",
249                                 "dev_deps",
250                                 "native_libs_dir",
251                               ])
252
253    testonly = _testonly
254    if (defined(invoker.visibility)) {
255      visibility = invoker.visibility
256    }
257    if (defined(crate_type) && crate_type == "cdylib") {
258      # See comments above about cdylib.
259      crate_type = "rlib"
260    }
261    crate_name = _crate_name
262
263    if (defined(_output_name)) {
264      output_name = _output_name
265    }
266
267    # Don't import the `chromium` crate into third-party code.
268    no_chromium_prelude = true
269
270    rustc_metadata = _rustc_metadata
271
272    # TODO(crbug.com/40259764): don't default to true. This requires changes to
273    # third_party.toml and gnrt when generating third-party build targets.
274    allow_unsafe = true
275
276    configs = []
277    configs = _configs
278    if (_crate_type == "rlib") {
279      # Forward configs for unit tests.
280      executable_configs = invoker.executable_configs
281    }
282
283    if (!defined(rustflags)) {
284      rustflags = []
285    }
286    rustenv = _rustenv
287
288    if (!defined(build_native_rust_unit_tests)) {
289      build_native_rust_unit_tests = _crate_type != "proc-macro"
290    }
291    if (build_native_rust_unit_tests) {
292      # Unit tests in a proc-macro crate type don't make sense, you can't
293      # compile executables against the `proc_macro` crate.
294      assert(_crate_type != "proc-macro")
295    }
296
297    # The unit tests for each target, if generated, should be unique as well.
298    # a) It needs to be unique even if multiple build targets have the same
299    #    `crate_name`, but different target names.
300    # b) It needs to be unique even if multiple build targets have the same
301    #    `crate_name` and target name, but different epochs.
302    _unit_test_unique_target_name = ""
303    if (_crate_name != _orig_target_name) {
304      _unit_test_unique_target_name = "${_orig_target_name}_"
305    }
306    _unit_test_unique_epoch = ""
307    if (defined(invoker.epoch)) {
308      _epoch_str = string_replace(invoker.epoch, ".", "_")
309      _unit_test_unique_epoch = "v${_epoch_str}_"
310    }
311    if (defined(output_dir) && output_dir != "") {
312      unit_test_output_dir = output_dir
313    }
314    unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"
315
316    if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") {
317      # Cargo crate rlibs can be compiled differently for tests, and must not
318      # collide with the production outputs. This does *not* override the
319      # unit_test_output_dir, which is set above, as that target is not an rlib.
320      output_dir = "$target_out_dir/$_orig_target_name"
321    }
322
323    if (defined(invoker.dev_deps)) {
324      test_deps = invoker.dev_deps
325    }
326
327    if (defined(invoker.build_root)) {
328      # Uh-oh, we have a build script
329      if (!defined(deps)) {
330        deps = []
331      }
332      if (!defined(sources)) {
333        sources = []
334      }
335      if (!defined(inputs)) {
336        inputs = []
337      }
338
339      # This... is a bit weird. We generate a file called cargo_flags.rs which
340      # does not actually contain Rust code, but instead some flags to add
341      # to the rustc command line. We need it to end in a .rs extension so that
342      # we can include it in the 'sources' line and thus have dependency
343      # calculation done correctly. data_deps won't work because targets don't
344      # require them to be present until runtime.
345      flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
346      rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
347      sources += [ flags_file ]
348      if (defined(invoker.build_script_outputs)) {
349        # Build scripts may output arbitrary files. They are usually included in
350        # the main Rust target using include! or include_str! and therefore the
351        # filename may be .rs or may be arbitrary. We want to educate ninja
352        # about the dependency either way.
353        foreach(extra_source,
354                filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
355          sources += [ "$_build_script_env_out_dir/$extra_source" ]
356        }
357        foreach(extra_source,
358                filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
359          inputs += [ "$_build_script_env_out_dir/$extra_source" ]
360        }
361      }
362      deps += [ ":${_build_script_name}_output" ]
363      if (defined(_native_libs_action)) {
364        deps += [ ":${_native_libs_action}" ]
365        configs += [ ":${_native_libs_config}" ]
366      }
367    }
368  }
369
370  if (defined(invoker.build_root)) {
371    action("${_build_script_name}_write_rustflags") {
372      _rustflags_txt = "$_build_script_env_out_dir/rustflags.txt"
373      outputs = [ _rustflags_txt ]
374      script = rebase_path("//build/rust/write_rustflags.py")
375      args = [
376        "--output",
377        rebase_path(_rustflags_txt, root_build_dir),
378        "--",
379        "{{rustflags}}",
380      ]
381
382      # The configs are required to get `{{rustflags}}` so that the build script
383      # is compiled with the same flags as the library/binary will be. The build
384      # script is an executable so it also gets the executable configs. If this
385      # is ever a problem we can add a separate build_script_configs to the
386      # cargo_crate template and just have it default to the same thing as
387      # executable_configs.
388      configs = invoker.executable_configs
389    }
390
391    # Extra targets required to make build script work
392    action("${_build_script_name}_output") {
393      script = rebase_path("//build/rust/run_build_script.py")
394      _write_rustflags_outputs =
395          get_target_outputs(":${_build_script_name}_write_rustflags")
396      _rustflags_txt = _write_rustflags_outputs[0]
397      inputs = [
398        "//build/action_helpers.py",
399        "//build/gn_helpers.py",
400        _rustflags_txt,
401      ]
402      build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
403      deps = [
404        ":${_build_script_name}_write_rustflags",
405        build_script_target,
406      ]
407      testonly = _testonly
408      if (defined(invoker.visibility)) {
409        visibility = invoker.visibility
410      }
411
412      # The build script may be built with a different toolchain when
413      # cross-compiling (the host toolchain) so we must find the path relative
414      # to that.
415      _build_script_root_out_dir =
416          get_label_info(build_script_target, "root_out_dir")
417      _build_script_exe = "$_build_script_root_out_dir/$_build_script_name"
418
419      # The executable is always built with the `rust_macro_toolchain` which
420      # targets the `host_os`. The rule here is on the `target_toolchain` which
421      # can be different (e.g. compiling on Linux, targeting Windows).
422      if (host_os == "win") {
423        _build_script_exe = "${_build_script_exe}.exe"
424      }
425
426      _flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
427
428      inputs += [ _build_script_exe ]
429      outputs = [ _flags_file ]
430      args = [
431        "--build-script",
432        rebase_path(_build_script_exe, root_build_dir),
433        "--output",
434        rebase_path(_flags_file, root_build_dir),
435        "--rust-prefix",
436        rebase_path("${rust_sysroot}/bin", root_build_dir),
437        "--out-dir",
438        rebase_path(_build_script_env_out_dir, root_build_dir),
439        "--src-dir",
440        rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
441        "--target",
442        rust_abi_target,
443        "--rustflags",
444        rebase_path(_rustflags_txt, root_build_dir),
445      ]
446      if (cargo_target_abi != "") {
447        args += [
448          "--target-abi",
449          cargo_target_abi,
450        ]
451      }
452      if (current_cpu == "arm64" || current_cpu == "x64" ||
453          current_cpu == "loong64" || current_cpu == "riscv64") {
454        args += [
455          "--pointer-width",
456          "64",
457        ]
458      } else if (current_cpu == "arm" || current_cpu == "x86") {
459        args += [
460          "--pointer-width",
461          "32",
462        ]
463      } else {
464        assert(false, "Architecture not supported")
465      }
466      if (defined(invoker.features)) {
467        args += [ "--features" ]
468        args += invoker.features
469      }
470      if (defined(invoker.build_script_outputs)) {
471        args += [ "--generated-files" ]
472        args += invoker.build_script_outputs
473        foreach(generated_file, invoker.build_script_outputs) {
474          outputs += [ "$_build_script_env_out_dir/$generated_file" ]
475        }
476      }
477      if (_rustenv != []) {
478        args += [ "--env" ]
479        args += _rustenv
480      }
481      if (defined(invoker.build_script_inputs)) {
482        inputs += invoker.build_script_inputs
483      }
484    }
485
486    if (toolchain_for_rust_host_build_tools) {
487      # The build script is only available to be built on the host, and we use
488      # the rust_macro_toolchain for it to unblock building them while the
489      # Chromium stdlib is still being compiled.
490      rust_executable(_build_script_name) {
491        crate_name = _build_script_name
492        sources = invoker.build_sources
493        crate_root = invoker.build_root
494        testonly = _testonly
495        if (defined(invoker.visibility)) {
496          visibility = invoker.visibility
497        }
498        if (defined(invoker.build_deps)) {
499          deps = invoker.build_deps
500        }
501        if (defined(invoker.build_script_inputs)) {
502          inputs = invoker.build_script_inputs
503        }
504
505        # Don't import the `chromium` crate into third-party code.
506        no_chromium_prelude = true
507
508        # The ${_build_script_name}_output target looks for the exe in this
509        # location. Due to how the Windows component build works, this has to
510        # be $root_out_dir for all EXEs. In component build, C++ links to the
511        # CRT as a DLL, and if Rust does not match, we can't link mixed target
512        # Rust EXE/DLLs, as the headers in C++ said something different than
513        # what Rust links. Since the CRT DLL is placed in the $root_out_dir,
514        # an EXE can find it if it's also placed in that dir.
515        output_dir = root_out_dir
516        rustenv = _rustenv
517        forward_variables_from(invoker,
518                               [
519                                 "features",
520                                 "edition",
521                                 "rustflags",
522                               ])
523        configs -= [
524          "//build/config/compiler:chromium_code",
525
526          # Avoid generating profiling data for build scripts.
527          #
528          # TODO(crbug.com/40261306): determine for sure whether to remove this
529          # config. I'm not sure of the overlap between PGO instrumentation and
530          # code coverage instrumentation, but we definitely don't want build
531          # script coverage for PGO, while we might for test coverage metrics.
532          #
533          # If we do include build script output in test metrics, it could be
534          # misleading: exercising some code from a build script doesn't give us
535          # the same signal as an actual test.
536          "//build/config/coverage:default_coverage",
537        ]
538        configs += [ "//build/config/compiler:no_chromium_code" ]
539      }
540    } else {
541      not_needed(invoker,
542                 [
543                   "build_sources",
544                   "build_deps",
545                   "build_root",
546                   "build_script_inputs",
547                   "build_script_outputs",
548                 ])
549    }
550  } else {
551    not_needed([
552                 "_name_specific_output_dir",
553                 "_orig_target_name",
554               ])
555  }
556}
557
558set_defaults("cargo_crate") {
559  library_configs = default_compiler_configs
560  executable_configs = default_executable_configs
561  proc_macro_configs = default_rust_proc_macro_configs
562}
563