• 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_target.gni")
7
8# This template allows for building Cargo crates within gn.
9#
10# It is intended for use with pre-existing (third party) code and
11# is none too efficient. (It will stall the build pipeline whilst
12# it runs build scripts to work out what flags are needed). First
13# party code should directly use first-class gn targets, such as
14# //build/rust/rust_static_library.gni or similar.
15#
16# Because it's intended for third-party code, it automatically
17# defaults to //build/config/compiler:no_chromium_code which
18# suppresses some warnings. If you *do* use this for first party
19# code, you should remove that config and add the equivalent
20# //build/config/compiler:chromium_code config.
21#
22# Arguments:
23#  sources
24#  crate_root
25#  epoch
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#  dev_deps
37#    Same meaning as test_deps in rust_static_library.gni, but called
38#    dev_deps to match Cargo.toml better.
39#
40#  build_root (optional)
41#    Filename of build.rs build script.
42#
43#  build_deps (optional)
44#    Build script dependencies
45#
46#  build_sources (optional)
47#    List of sources for build script. Must be specified if
48#    build_root is specified.
49#
50#  build_script_outputs (optional)
51#    List of .rs files generated by the build script, if any.
52#    Fine to leave undefined even if you have a build script.
53#    This doesn't directly correspond to any Cargo variable,
54#    but unfortunately is necessary for gn to build its dependency
55#    trees automatically.
56#    Many build scripts just output --cfg directives, in which case
57#    no source code is generated and this can remain empty.
58#
59#  build_script_inputs (optional)
60#    If the build script reads any files generated by build_deps,
61#    as opposed to merely linking against them, add a list of such
62#    files here. Again, this doesn't correspond to a Cargo variable
63#    but is necessary for gn.
64#
65#  crate_type "bin", "proc-macro" or "rlib" (optional)
66#    Whether to build an executable. The default is "rlib".
67#    At present others are not supported.
68#
69#  cargo_pkg_authors
70#  cargo_pkg_version
71#  cargo_pkg_name
72#  cargo_pkg_description
73#    Strings as found within 'version' and similar fields within Cargo.toml.
74#    Converted to environment variables passed to rustc, in case the crate
75#    uses clap `crate_version!` or `crate_authors!` macros (fairly common in
76#    command line tool help)
77
78template("cargo_crate") {
79  _orig_target_name = target_name
80
81  _crate_name = _orig_target_name
82  if (defined(invoker.crate_name)) {
83    _crate_name = invoker.crate_name
84  }
85
86  # Executables need to have unique names. Work out a prefix.
87  if (defined(invoker.build_root)) {
88    _epochlabel = "vunknown"
89    if (defined(invoker.epoch)) {
90      _tempepoch = string_replace(invoker.epoch, ".", "_")
91      _epochlabel = "v${_tempepoch}"
92    }
93
94    # This name includes the target name to ensure it's unique for each possible
95    # build target in the same BUILD.gn file.
96    _build_script_name =
97        "${_crate_name}_${target_name}_${_epochlabel}_build_script"
98
99    # Where the OUT_DIR will point when running the build script exe, and
100    # compiling the crate library/binaries. This directory must include the
101    # target name to avoid collisions between multiple GN targets that exist
102    # in the same BUILD.gn.
103    _build_script_env_out_dir = "$target_gen_dir/$target_name"
104  }
105
106  _rustenv = []
107  if (defined(invoker.rustenv)) {
108    _rustenv = invoker.rustenv
109  }
110  if (defined(invoker.cargo_pkg_authors)) {
111    _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ]
112  }
113  if (defined(invoker.cargo_pkg_version)) {
114    _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ]
115  }
116  if (defined(invoker.cargo_pkg_name)) {
117    _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ]
118  }
119  if (defined(invoker.cargo_pkg_description)) {
120    _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ]
121  }
122
123  # The main target, either a Rust source set or an executable.
124  rust_target(target_name) {
125    forward_variables_from(invoker,
126                           "*",
127                           TESTONLY_AND_VISIBILITY + [
128                                 "build_root",
129                                 "build_deps",
130                                 "build_sources",
131                                 "build_script_inputs",
132                                 "build_script_outputs",
133                                 "unit_test_target",
134                                 "target_type",
135                                 "configs",
136                                 "rustenv",
137                               ])
138    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
139
140    # Work out what we're building.
141    crate_type = "rlib"
142    if (defined(invoker.crate_type)) {
143      crate_type = invoker.crate_type
144    }
145
146    # TODO(crbug.com/1422745): don't default to true. This requires changes to
147    # third_party.toml and gnrt when generating third-party build targets.
148    allow_unsafe = true
149
150    if (!defined(rustflags)) {
151      rustflags = []
152    }
153    rustenv = _rustenv
154    if (crate_type == "bin") {
155      target_type = "executable"
156      assert(!defined(invoker.epoch))
157    } else if (crate_type == "proc-macro") {
158      target_type = "rust_proc_macro"
159    } else {
160      assert(crate_type == "rlib")
161      target_type = "rust_library"
162    }
163
164    if (!defined(build_native_rust_unit_tests)) {
165      build_native_rust_unit_tests = true
166    }
167
168    # The unit tests for each target, if generated, should be unique as well.
169    # a) It needs to be unique even if multiple build targets have the same
170    #    `crate_name`, but different target names.
171    # b) It needs to be unique even if multiple build targets have the same
172    #    `crate_name` and target name, but different epochs.
173    _unit_test_unique_target_name = ""
174    if (_crate_name != _orig_target_name) {
175      _unit_test_unique_target_name = "${_orig_target_name}_"
176    }
177    _unit_test_unique_epoch = ""
178    if (defined(invoker.epoch)) {
179      _epoch_str = string_replace(invoker.epoch, ".", "_")
180      _unit_test_unique_epoch = "v${_epoch_str}_"
181    }
182    if (defined(output_dir) && output_dir != "") {
183      unit_test_output_dir = output_dir
184    }
185    unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests"
186
187    if ((!defined(output_dir) || output_dir == "") && crate_type == "rlib") {
188      # Cargo crate rlibs can be compiled differently for tests, and must not
189      # collide with the production outputs. This does *not* override the
190      # unit_test_output_dir, which is set above, as that target is not an rlib.
191      output_dir = "$target_out_dir/$_orig_target_name"
192    }
193
194    if (defined(invoker.build_root)) {
195      # Uh-oh, we have a build script
196      if (!defined(deps)) {
197        deps = []
198      }
199      if (!defined(sources)) {
200        sources = []
201      }
202      if (defined(invoker.dev_deps)) {
203        test_deps = invoker.dev_deps
204      }
205
206      # This... is a bit weird. We generate a file called cargo_flags.rs which
207      # does not actually contain Rust code, but instead some flags to add
208      # to the rustc command line. We need it to end in a .rs extension so that
209      # we can include it in the 'sources' line and thus have dependency
210      # calculation done correctly. data_deps won't work because targets don't
211      # require them to be present until runtime.
212      flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
213      rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ]
214      sources += [ flags_file ]
215      if (defined(invoker.build_script_outputs)) {
216        # Build scripts may output arbitrary files. They are usually included in
217        # the main Rust target using include! or include_str! and therefore the
218        # filename may be .rs or may be arbitrary. We want to educate ninja
219        # about the dependency either way.
220        foreach(extra_source,
221                filter_include(invoker.build_script_outputs, [ "*.rs" ])) {
222          sources += [ "$_build_script_env_out_dir/$extra_source" ]
223        }
224        inputs = []
225        foreach(extra_source,
226                filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) {
227          inputs += [ "$_build_script_env_out_dir/$extra_source" ]
228        }
229      }
230      deps += [ ":${_build_script_name}_output" ]
231    }
232  }
233
234  if (defined(invoker.build_root)) {
235    # Extra targets required to make build script work
236    action("${_build_script_name}_output") {
237      script = rebase_path("//build/rust/run_build_script.py")
238      build_script_target =
239          ":${_build_script_name}($host_toolchain_no_sanitizers)"
240      deps = [ build_script_target ]
241
242      # The build script output is always in the name-specific output dir. It
243      # may be built with a different toolchain when cross-compiling (the host
244      # toolchain) so we must find the path relative to that.
245      _build_script_target_out_dir =
246          get_label_info(build_script_target, "target_out_dir")
247      _build_script_exe =
248          "$_build_script_target_out_dir/$_orig_target_name/$_build_script_name"
249      if (is_win) {
250        _build_script_exe = "${_build_script_exe}.exe"
251      }
252
253      _flags_file = "$_build_script_env_out_dir/cargo_flags.rs"
254
255      inputs = [ _build_script_exe ]
256      outputs = [ _flags_file ]
257      args = [
258        "--build-script",
259        rebase_path(_build_script_exe, root_build_dir),
260        "--output",
261        rebase_path(_flags_file, root_build_dir),
262        "--rust-prefix",
263        rebase_path("${rust_sysroot}/bin"),
264        "--out-dir",
265        rebase_path(_build_script_env_out_dir, root_build_dir),
266        "--src-dir",
267        rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir),
268      ]
269      if (defined(rust_abi_target) && rust_abi_target != "") {
270        args += [
271          "--target",
272          rust_abi_target,
273        ]
274      }
275      if (defined(invoker.features)) {
276        args += [ "--features" ]
277        args += invoker.features
278      }
279      if (defined(invoker.build_script_outputs)) {
280        args += [ "--generated-files" ]
281        args += invoker.build_script_outputs
282        foreach(generated_file, invoker.build_script_outputs) {
283          outputs += [ "$_build_script_env_out_dir/$generated_file" ]
284        }
285      }
286      if (_rustenv != []) {
287        args += [ "--env" ]
288        args += _rustenv
289      }
290      if (defined(invoker.build_script_inputs)) {
291        inputs += invoker.build_script_inputs
292      }
293    }
294
295    if (current_toolchain == host_toolchain_no_sanitizers) {
296      rust_target(_build_script_name) {
297        target_type = "executable"
298        sources = invoker.build_sources
299        crate_root = invoker.build_root
300        if (defined(invoker.build_deps)) {
301          deps = invoker.build_deps
302        }
303
304        # An rlib's build script may be built differently for tests and for
305        # production, so they must be in a name specific to the GN target. The
306        # ${_build_script_name}_output target looks for the exe in this
307        # location.
308        output_dir = "$target_out_dir/$_orig_target_name"
309        rustenv = _rustenv
310        forward_variables_from(invoker,
311                               [
312                                 "features",
313                                 "edition",
314                                 "rustflags",
315                               ])
316        executable_configs -= [ "//build/config/compiler:chromium_code" ]
317        executable_configs += [ "//build/config/compiler:no_chromium_code" ]
318      }
319    } else {
320      not_needed(invoker,
321                 [
322                   "build_sources",
323                   "build_deps",
324                   "build_root",
325                   "build_script_inputs",
326                   "build_script_outputs",
327                 ])
328    }
329  } else {
330    not_needed([
331                 "_name_specific_output_dir",
332                 "_orig_target_name",
333               ])
334  }
335}
336
337set_defaults("cargo_crate") {
338  library_configs = default_compiler_configs
339  executable_configs = default_executable_configs
340}
341