# 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/rust/rust_executable.gni") import("//build/rust/rust_macro.gni") import("//build/rust/rust_static_library.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 # deps # aliased_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 # # epoch (optional) # The major version of the library, which is used to differentiate between # multiple versions of the same library name. This includes all leading 0s # and the first non-zero value in the crate's version. This should be left # as the default, which is "0", for first-party code unless there are # multiple versions of a crate present. For third-party code, the version # epoch (matching the directory it is found in) should be specified. # # Examples: # 1.0.2 => epoch = "1" # 4.2.0 => epoch = "4" # 0.2.7 => epoch = "0.2" # 0.0.3 => epoch = "0.0.3" # # 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. # # native_libs (optional) # Paths to library files that need to be in the linking search path when # depending on the crate's library, as it links against them via #[link] # directives. # # 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 } # Construct metadata from the crate epoch or an explicitly provided metadata # field. _rustc_metadata = "" if (defined(invoker.rustc_metadata)) { _rustc_metadata = invoker.rustc_metadata } else if (defined(invoker.epoch)) { _rustc_metadata = "${_crate_name}-${invoker.epoch}" } # 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}" } # This name includes the target name to ensure it's unique for each possible # build target in the same BUILD.gn file. _build_script_name = "${_crate_name}_${target_name}_${_epochlabel}_build_script" # Where the OUT_DIR will point when running the build script exe, and # compiling the crate library/binaries. This directory must include the # target name to avoid collisions between multiple GN targets that exist # in the same BUILD.gn. _build_script_env_out_dir = "$target_gen_dir/$target_name" } _rustenv = [] if (defined(invoker.rustenv)) { _rustenv = invoker.rustenv } if (defined(invoker.cargo_pkg_authors)) { _rustenv += [ "CARGO_PKG_AUTHORS=${invoker.cargo_pkg_authors}" ] } if (defined(invoker.cargo_pkg_version)) { _rustenv += [ "CARGO_PKG_VERSION=${invoker.cargo_pkg_version}" ] } if (defined(invoker.cargo_pkg_name)) { _rustenv += [ "CARGO_PKG_NAME=${invoker.cargo_pkg_name}" ] } if (defined(invoker.cargo_pkg_description)) { _rustenv += [ "CARGO_PKG_DESCRIPTION=${invoker.cargo_pkg_description}" ] } # Try to determine the CARGO_MANIFEST_DIR, preferring the directory # with build.rs and otherwise assuming that the target contains a # `crate/` subdirectory. if (defined(invoker.build_root)) { manifest_dir = "." } else { build_gn_dir = get_label_info(target_name, "dir") manifest_dir = rebase_path(build_gn_dir + "/crate", root_build_dir) } _rustenv += [ "CARGO_MANIFEST_DIR=${manifest_dir}" ] # cargo_crate() should set library_configs, executable_configs, # proc_macro_configs. Not configs. assert(!defined(invoker.configs)) # Work out what we're building. _crate_type = "rlib" if (defined(invoker.crate_type)) { _crate_type = invoker.crate_type } if (_crate_type == "cdylib") { # Crates are rarely cdylibs. The example encountered so far aims # to expose a C API to other code. In a Chromium context, we don't # want to build that as a dylib for a couple of reasons: # * rust_shared_library does not work on Mac. rustc does not know # how to export the __llvm_profile_raw_version symbol. # * even if it did work, this might require us to distribute extra # binaries (.so/.dylib etc.) # For the only case we've had so far, it makes more sense to build # the code as a static library which we can then link into downstream # binaries. _crate_type = "rlib" } if (_crate_type == "bin") { _target_type = "rust_executable" assert(!defined(invoker.epoch)) if (defined(invoker.executable_configs)) { _configs = invoker.executable_configs } } else if (_crate_type == "proc-macro") { _target_type = "rust_macro" if (defined(invoker.proc_macro_configs)) { _configs = invoker.proc_macro_configs } } else { assert(_crate_type == "rlib") _target_type = "rust_static_library" if (defined(invoker.library_configs)) { _configs = invoker.library_configs } } if (defined(invoker.output_name)) { _output_name = invoker.output_name } else if (_crate_type != "bin") { # Note that file names of libraries must start with the crate name in # order for the compiler to find transitive dependencies in the # directory search paths (since they are not all explicitly specified). # # For bin targets, we expect the target name to be unique, and the name # of the exe should not add magic stuff to it. And bin crates can not be # transitive dependencies. _output_name = "${_crate_name}_${_orig_target_name}" } _testonly = false if (defined(invoker.testonly)) { _testonly = invoker.testonly } if (defined(invoker.native_libs)) { _native_libs_action = "copy_${target_name}_native_libs" _native_libs_config = "config_${target_name}_native_libs" _native_libs_dir = "${root_out_dir}/rustlib/${_crate_name}_${target_name}_${_epochlabel}" copy(_native_libs_action) { testonly = _testonly visibility = [ ":$target_name" ] sources = invoker.native_libs outputs = [ "${_native_libs_dir}/{{source_file_part}}" ] } config(_native_libs_config) { lib_dirs = [ "${_native_libs_dir}" ] } } # The main target, either a Rust source set or an executable. target(_target_type, target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "build_root", "build_deps", "build_sources", "build_script_inputs", "build_script_outputs", "epoch", "unit_test_target", "configs", "executable_configs", "library_configs", "proc_macro_configs", "rustenv", "dev_deps", "native_libs_dir", ]) testonly = _testonly if (defined(invoker.visibility)) { visibility = invoker.visibility } if (defined(crate_type) && crate_type == "cdylib") { # See comments above about cdylib. crate_type = "rlib" } crate_name = _crate_name if (defined(_output_name)) { output_name = _output_name } # Don't import the `chromium` crate into third-party code. no_chromium_prelude = true rustc_metadata = _rustc_metadata # TODO(crbug.com/40259764): don't default to true. This requires changes to # third_party.toml and gnrt when generating third-party build targets. allow_unsafe = true configs = [] if (defined(_configs)) { configs += _configs } if (_crate_type == "rlib") { # Forward configs for unit tests. if (defined(invoker.executable_configs)) { executable_configs = invoker.executable_configs } } if (!defined(rustflags)) { rustflags = [] } rustenv = _rustenv if (!defined(build_native_rust_unit_tests)) { build_native_rust_unit_tests = _crate_type != "proc-macro" } if (build_native_rust_unit_tests) { # Unit tests in a proc-macro crate type don't make sense, you can't # compile executables against the `proc_macro` crate. assert(_crate_type != "proc-macro") } # 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}_" } if (defined(output_dir) && output_dir != "") { unit_test_output_dir = output_dir } unit_test_target = "${_unit_test_unique_target_name}${_crate_name}_${_unit_test_unique_epoch}unittests" if ((!defined(output_dir) || output_dir == "") && _crate_type == "rlib") { # Cargo crate rlibs can be compiled differently for tests, and must not # collide with the production outputs. This does *not* override the # unit_test_output_dir, which is set above, as that target is not an rlib. output_dir = "$target_out_dir/$_orig_target_name" } if (defined(invoker.dev_deps)) { test_deps = invoker.dev_deps } if (defined(invoker.build_root)) { # Uh-oh, we have a build script if (!defined(deps)) { deps = [] } if (!defined(sources)) { sources = [] } if (!defined(inputs)) { inputs = [] } # 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_env_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_env_out_dir/$extra_source" ] } foreach(extra_source, filter_exclude(invoker.build_script_outputs, [ "*.rs" ])) { inputs += [ "$_build_script_env_out_dir/$extra_source" ] } } deps += [ ":${_build_script_name}_output" ] if (defined(_native_libs_action)) { deps += [ ":${_native_libs_action}" ] configs += [ ":${_native_libs_config}" ] } } } 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") inputs = [ "//build/action_helpers.py", "//build/gn_helpers.py", ] build_script_target = ":${_build_script_name}($rust_macro_toolchain)" deps = [ build_script_target ] testonly = _testonly if (defined(invoker.visibility)) { visibility = invoker.visibility } # The build script may be built with a different toolchain when # cross-compiling (the host toolchain) so we must find the path relative # to that. _build_script_root_out_dir = get_label_info(build_script_target, "root_out_dir") _build_script_exe = "$_build_script_root_out_dir/$_build_script_name" # The executable is always built with the `rust_macro_toolchain` which # targets the `host_os`. The rule here is on the `target_toolchain` which # can be different (e.g. compiling on Linux, targeting Windows). if (host_os == "win") { _build_script_exe = "${_build_script_exe}.exe" } _flags_file = "$_build_script_env_out_dir/cargo_flags.rs" inputs += [ _build_script_exe ] outputs = [ _flags_file ] args = [ "--build-script", rebase_path(_build_script_exe, root_build_dir), "--output", rebase_path(_flags_file, root_build_dir), "--rust-prefix", rebase_path("${rust_sysroot}/bin", root_build_dir), "--out-dir", rebase_path(_build_script_env_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 (current_cpu == "arm64" || current_cpu == "x64" || current_cpu == "loong64" || current_cpu == "riscv64") { args += [ "--pointer-width", "64", ] } else if (current_cpu == "arm" || current_cpu == "x86") { args += [ "--pointer-width", "32", ] } else { assert(false, "Architecture not supported") } 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_env_out_dir/$generated_file" ] } } if (_rustenv != []) { args += [ "--env" ] args += _rustenv } if (defined(invoker.build_script_inputs)) { inputs += invoker.build_script_inputs } } if (toolchain_for_rust_host_build_tools) { # The build script is only available to be built on the host, and we use # the rust_macro_toolchain for it to unblock building them while the # Chromium stdlib is still being compiled. rust_executable(_build_script_name) { crate_name = _build_script_name sources = invoker.build_sources crate_root = invoker.build_root testonly = _testonly if (defined(invoker.visibility)) { visibility = invoker.visibility } if (defined(invoker.build_deps)) { deps = invoker.build_deps } if (defined(invoker.build_script_inputs)) { inputs = invoker.build_script_inputs } # Don't import the `chromium` crate into third-party code. no_chromium_prelude = true # The ${_build_script_name}_output target looks for the exe in this # location. Due to how the Windows component build works, this has to # be $root_out_dir for all EXEs. In component build, C++ links to the # CRT as a DLL, and if Rust does not match, we can't link mixed target # Rust EXE/DLLs, as the headers in C++ said something different than # what Rust links. Since the CRT DLL is placed in the $root_out_dir, # an EXE can find it if it's also placed in that dir. output_dir = root_out_dir rustenv = _rustenv forward_variables_from(invoker, [ "features", "edition", "rustflags", ]) configs -= [ "//build/config/compiler:chromium_code", # Avoid generating profiling data for build scripts. # # TODO(crbug.com/40261306): determine for sure whether to remove this # config. I'm not sure of the overlap between PGO instrumentation and # code coverage instrumentation, but we definitely don't want build # script coverage for PGO, while we might for test coverage metrics. # # If we do include build script output in test metrics, it could be # misleading: exercising some code from a build script doesn't give us # the same signal as an actual test. "//build/config/coverage:default_coverage", ] configs += [ "//build/config/compiler:no_chromium_code" ] } } else { not_needed(invoker, [ "build_sources", "build_deps", "build_root", "build_script_inputs", "build_script_outputs", ]) } } else { not_needed([ "_name_specific_output_dir", "_orig_target_name", ]) } } set_defaults("cargo_crate") { library_configs = default_compiler_configs executable_configs = default_executable_configs proc_macro_configs = default_rust_proc_macro_configs }