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