| # 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. |
| # |
| # 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 |
| } |
| |
| # 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", |
| ]) |
| |
| 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/1422745): 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(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}($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 (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/1426472): 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 |
| } |