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