# Copyright 2021 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import("//build/config/rust.gni") import("//build/rust/rust_target.gni") # This template allows for building Cargo crates within gn. # # It is intended for use with pre-existing (third party) code and # is none too efficient. (It will stall the build pipeline whilst # it runs build scripts to work out what flags are needed). First # party code should directly use first-class gn targets, such as # //build/rust/rust_static_library.gni or similar. # # Because it's intended for third-party code, it automatically # defaults to //build/config/compiler:no_chromium_code which # suppresses some warnings. If you *do* use this for first party # code, you should remove that config and add the equivalent # //build/config/compiler:chromium_code config. # # Arguments: # sources # crate_root # epoch # deps # features # build_native_rust_unit_tests # edition # crate_name # All just as in rust_static_library.gni # library_configs/executable_configs # All just as in rust_target.gni # # dev_deps # Same meaning as test_deps in rust_static_library.gni, but called # dev_deps to match Cargo.toml better. # # build_root (optional) # Filename of build.rs build script. # # build_deps (optional) # Build script dependencies # # build_sources (optional) # List of sources for build script. Must be specified if # build_root is specified. # # build_script_outputs (optional) # List of .rs files generated by the build script, if any. # Fine to leave undefined even if you have a build script. # This doesn't directly correspond to any Cargo variable, # but unfortunately is necessary for gn to build its dependency # trees automatically. # Many build scripts just output --cfg directives, in which case # no source code is generated and this can remain empty. # # build_script_inputs (optional) # If the build script reads any files generated by build_deps, # as opposed to merely linking against them, add a list of such # files here. Again, this doesn't correspond to a Cargo variable # but is necessary for gn. # # crate_type "bin", "proc-macro" or "rlib" (optional) # Whether to build an executable. The default is "rlib". # At present others are not supported. # # cargo_pkg_authors # cargo_pkg_version # cargo_pkg_name # cargo_pkg_description # Strings as found within 'version' and similar fields within Cargo.toml. # Converted to environment variables passed to rustc, in case the crate # uses clap `crate_version!` or `crate_authors!` macros (fairly common in # command line tool help) template("cargo_crate") { orig_target_name = target_name _crate_name = orig_target_name if (defined(invoker.crate_name)) { _crate_name = invoker.crate_name } # Executables need to have unique names. Work out a prefix. if (defined(invoker.build_root)) { _epochlabel = "vunknown" if (defined(invoker.epoch)) { _tempepoch = string_replace(invoker.epoch, ".", "_") _epochlabel = "v${_tempepoch}" } build_script_name = "${_crate_name}_${target_name}_${_epochlabel}_build_script" } _rustenv = [] if (defined(invoker.rustenv)) { _rustenv = invoker.rustenv } if (defined(invoker.cargo_pkg_authors)) { _rustenv += [ string_join("=", [ "CARGO_PKG_AUTHORS", invoker.cargo_pkg_authors, ]) ] } if (defined(invoker.cargo_pkg_version)) { _rustenv += [ string_join("=", [ "CARGO_PKG_VERSION", invoker.cargo_pkg_version, ]) ] } if (defined(invoker.cargo_pkg_name)) { _rustenv += [ string_join("=", [ "CARGO_PKG_NAME", invoker.cargo_pkg_name, ]) ] } if (defined(invoker.cargo_pkg_description)) { _rustenv += [ string_join("=", [ "CARGO_PKG_DESCRIPTION", invoker.cargo_pkg_description, ]) ] } # The main target, either a Rust source set or an executable. rust_target(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "build_root", "build_deps", "build_sources", "build_script_inputs", "build_script_outputs", "output_dir", "unit_test_target", "target_type", "configs", "rustenv", ]) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) # Work out what we're building. crate_type = "rlib" if (defined(invoker.crate_type)) { crate_type = invoker.crate_type } if (!defined(rustflags)) { rustflags = [] } rustenv = _rustenv if (crate_type == "bin") { target_type = "executable" assert(!defined(invoker.epoch)) } else if (crate_type == "proc-macro") { target_type = "rust_proc_macro" } else { assert(crate_type == "rlib") target_type = "rust_library" } if (!defined(build_native_rust_unit_tests)) { build_native_rust_unit_tests = true } # We may generate multiple build rules for the same Cargo crate, as they may # have multiple build configurations: for use from deps, build-deps or # dev-deps. But that would try to build multiple crates with the same name, # colliding on the libcratename.rlib outputs. So we specify an output_dir # for Cargo crates which includes the GN target name to disambiguate them. output_dir = "${target_out_dir}/${orig_target_name}" # The unit tests for each target, if generated, should be unique as well. # a) It needs to be unique even if multiple build targets have the same # `crate_name`, but different target names. # b) It needs to be unique even if multiple build targets have the same # `crate_name` and target name, but different epochs. _unit_test_unique_target_name = "" if (_crate_name != orig_target_name) { _unit_test_unique_target_name = "${orig_target_name}_" } _unit_test_unique_epoch = "" if (defined(invoker.epoch)) { _epoch_str = string_replace(invoker.epoch, ".", "_") _unit_test_unique_epoch = "v${_epoch_str}_" } unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests" if (defined(invoker.build_root)) { # Uh-oh, we have a build script if (!defined(deps)) { deps = [] } if (!defined(sources)) { sources = [] } if (defined(invoker.dev_deps)) { test_deps = invoker.dev_deps } # Re-compute the directory the build script used as its `--out-dir`. This # logic needs to match that in `action("${build_script_name}_output")`. _build_script_target_out_dir = get_label_info(":${build_script_name}_output", "target_out_dir") _build_script_out_dir = "$_build_script_target_out_dir/$orig_target_name" # This... is a bit weird. We generate a file called cargo_flags.rs which # does not actually contain Rust code, but instead some flags to add # to the rustc command line. We need it to end in a .rs extension so that # we can include it in the 'sources' line and thus have dependency # calculation done correctly. data_deps won't work because targets don't # require them to be present until runtime. flags_file = "$_build_script_out_dir/cargo_flags.rs" rustflags += [ "@" + rebase_path(flags_file, root_build_dir) ] sources += [ flags_file ] if (defined(invoker.build_script_outputs)) { # Build scripts may output arbitrary files. They are usually included in # the main Rust target using include! or include_str! and therefore the # filename may be .rs or may be arbitrary. We want to educate ninja # about the dependency either way. foreach(extra_source, filter_include(invoker.build_script_outputs, [ "*.rs" ])) { sources += [ "$_build_script_out_dir/$extra_source" ] } inputs = [] foreach(extra_source, filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) { inputs += [ "$_build_script_out_dir/$extra_source" ] } } deps += [ ":${build_script_name}_output" ] } else { not_needed([ "orig_target_name" ]) } } if (defined(invoker.build_root)) { # Extra targets required to make build script work action("${build_script_name}_output") { script = rebase_path("//build/rust/run_build_script.py") build_script_target = ":${build_script_name}($host_toolchain)" deps = [ build_script_target ] _build_script_exe_dir = get_label_info(build_script_target, "root_out_dir") build_script = "$_build_script_exe_dir/${build_script_name}" if (is_win) { build_script = "${build_script}.exe" } _build_script_out_dir = "$target_out_dir/$orig_target_name" flags_file = "$_build_script_out_dir/cargo_flags.rs" inputs = [ build_script ] outputs = [ flags_file ] args = [ "--build-script", rebase_path(build_script, root_build_dir), "--output", rebase_path(flags_file, root_build_dir), "--rust-prefix", rebase_path("${rust_sysroot}/bin"), "--out-dir", rebase_path(_build_script_out_dir, root_build_dir), "--src-dir", rebase_path(get_path_info(invoker.build_root, "dir"), root_build_dir), ] if (defined(rust_abi_target) && rust_abi_target != "") { args += [ "--target", rust_abi_target, ] } if (defined(invoker.features)) { args += [ "--features" ] args += invoker.features } if (defined(invoker.build_script_outputs)) { args += [ "--generated-files" ] args += invoker.build_script_outputs foreach(generated_file, invoker.build_script_outputs) { outputs += [ "$_build_script_out_dir/$generated_file" ] } } if (_rustenv != []) { args += [ "--env" ] args += _rustenv } if (defined(invoker.build_script_inputs)) { inputs += invoker.build_script_inputs } } if (current_toolchain == host_toolchain) { rust_target(build_script_name) { target_type = "executable" sources = invoker.build_sources crate_root = invoker.build_root if (defined(invoker.build_deps)) { deps = invoker.build_deps } rustenv = _rustenv forward_variables_from(invoker, [ "features", "edition", "rustflags", ]) executable_configs -= [ "//build/config/compiler:chromium_code" ] executable_configs += [ "//build/config/compiler:no_chromium_code" ] } } else { not_needed(invoker, [ "build_sources", "build_deps", "build_root", "build_script_inputs", "build_script_outputs", ]) } } } set_defaults("cargo_crate") { library_configs = default_compiler_configs executable_configs = default_executable_configs }