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