# 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/analyze.gni") import("//build/rust/rust_unit_test.gni") # The //build directory is re-used for non-Chromium products. We do not support # cxx bindings in such contexts, because //third_party may be missing. if (build_with_chromium) { import("//third_party/rust/cxx/chromium_integration/rust_cxx.gni") } # Creates a Rust target (rlib, executable, proc macro etc.) with ability to # understand some handy variables such as "edition" and "features" and also to # build any associated unit tests. # # Normally, you should not use this directly. Use either # - cargo_crate.gni - for 3p crates only # - rust_static_library.gni - for 1p Rust code # # Because the common use of this is rust_static_library, all the documentation # for the supported options is given in rust_static_library.gni. Please refer # over there. # # If you're using rust_target directly, you will also need to specify: # target_type executable, rust_library etc. per GN norms # # There is one area where this differs from `rust_static_library`: configs. # Here, you must specify `executable_configs` or `library_configs` depending on # the type of thing you're generating. This is so that different defaults can # be provided. template("rust_target") { # Only one of `crate_root` or `generate_crate_root` can be specified, or # neither. assert(!defined(invoker.crate_root) || !(defined(invoker.generate_crate_root) && invoker.generate_crate_root)) _target_name = target_name _crate_name = target_name if (defined(invoker.crate_name)) { _crate_name = invoker.crate_name } if (defined(invoker.output_dir) && invoker.output_dir != "") { # This is where the build target (.exe, .rlib, etc) goes. _output_dir = invoker.output_dir } # This is where the OUT_DIR environment variable points to when running a # build script and when compiling the build target, for consuming generated # files. _env_out_dir = "$target_gen_dir/$_target_name" _allow_unsafe = false if (defined(invoker.allow_unsafe)) { _allow_unsafe = invoker.allow_unsafe } if (defined(invoker.generate_crate_root) && invoker.generate_crate_root) { generated_file("${_target_name}_crate_root") { outputs = [ "${target_gen_dir}/${target_name}.rs" ] contents = [ "// Generated crate root for ${_target_name}.", "// @generated", "", ] foreach(rs, invoker.sources) { rs_path_from_root = rebase_path(rs, target_gen_dir) contents += [ "#[path = \"${rs_path_from_root}\"]" ] # Drop the file extension from the module name. rs_modname = string_replace(rs, ".rs", "") # Replace invalid "/" chars in the source file path. rs_modname = string_replace(rs_modname, "/", "_") # Since source files are specified relative to the BUILD.gn they may # also have ".." path components. rs_modname = string_replace(rs_modname, "..", "dotdot") contents += [ "mod ${rs_modname};", "", ] } } _crate_root = string_join("", get_target_outputs(":${_target_name}_crate_root")) } else if (defined(invoker.crate_root)) { _crate_root = invoker.crate_root } else if (invoker.target_type == "executable") { _crate_root = "src/main.rs" } else { _crate_root = "src/lib.rs" } _testonly = false if (defined(invoker.testonly)) { _testonly = invoker.testonly } if (defined(invoker.visibility)) { _visibility = invoker.visibility } _use_local_std = use_local_std_by_default if (defined(invoker.use_local_std)) { _use_local_std = invoker.use_local_std } _rustflags = [] if (defined(invoker.rustflags)) { _rustflags += invoker.rustflags } if (defined(invoker.features)) { foreach(i, invoker.features) { _rustflags += [ "--cfg=feature=\"${i}\"" ] } } _edition = "2021" if (defined(invoker.edition)) { _edition = invoker.edition } _configs = [ "//build/rust:edition_${_edition}" ] _test_configs = [] if (invoker.target_type == "executable") { if (defined(invoker.executable_configs)) { _configs += invoker.executable_configs } } else if (invoker.target_type == "rust_proc_macro") { if (defined(invoker.proc_macro_configs)) { _configs += invoker.proc_macro_configs _test_configs += [ "//build/rust:proc_macro_extern" ] } } else { if (defined(invoker.library_configs)) { _configs += invoker.library_configs } } _forward_to_host_toolchain = false if (invoker.target_type == "rust_proc_macro") { if (current_toolchain != host_toolchain_no_sanitizers) { _forward_to_host_toolchain = true } _main_target_suffix = "${target_name}__proc_macro" } else { _main_target_suffix = "__rlib" } _deps = [] if (defined(invoker.deps)) { _deps += invoker.deps } _public_deps = [] if (defined(invoker.public_deps)) { _public_deps += invoker.public_deps } if (defined(invoker.aliased_deps)) { _aliased_deps = invoker.aliased_deps } else { _aliased_deps = { } } _is_data_dep = defined(invoker.is_data_dep) && invoker.is_data_dep _build_unit_tests = false if (defined(invoker.build_native_rust_unit_tests)) { _build_unit_tests = invoker.build_native_rust_unit_tests && can_build_rust_unit_tests } # Declares that the Rust crate generates bindings between C++ and Rust via the # Cxx crate. It may generate C++ headers and/or use the cxx crate macros to # generate Rust code internally, depending on what bindings are declared. If # set, it's a set of rust files that include Cxx bindings declarations. _cxx_bindings = [] if (defined(invoker.cxx_bindings)) { assert(build_with_chromium, "cxx bindings are not supported when building rust targets " + "outside the Chromium build.") _cxx_bindings = invoker.cxx_bindings } _rustenv = [ "OUT_DIR=" + rebase_path(_env_out_dir) ] if (defined(invoker.rustenv)) { _rustenv += invoker.rustenv } # TODO(danakj): This could be a hash generated from the input crate, such as # from its path, in which case the BUILD.gn would not need to specify # anything. But GN doesn't give us a hash function to make that easy. _metadata = "0" if (defined(invoker.epoch)) { _metadata = invoker.epoch } # We require that all source files are listed, even though this is # not a requirement for rustc. The reason is to ensure that tools # such as `gn deps` give the correct answer, and thus we trigger # the right test suites etc. on code change. # TODO(crbug.com/1256930) - verify this is correct assert(defined(invoker.sources), "sources must be listed") if (_forward_to_host_toolchain) { # Redirect to the host toolchain. group(_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } public_deps = [ ":${_target_name}${_main_target_suffix}($host_toolchain_no_sanitizers)", ] } not_needed(invoker, "*") not_needed([ "_allow_unsafe", "_build_unit_tests", "_crate_root", "_crate_name", "_cxx_bindings", "_deps", "_aliased_deps", "_is_data_dep", "_metadata", "_out_dir", "_public_deps", "_rustenv", "_rustflags", "_support_use_from_cpp", "_testonly", "_use_local_std", "_visibility", ]) } else { group(_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } # Both the C++ bindings (if present) and the Rust crate should be treated # like direct dependencies, so we expose them both in public_deps. public_deps = [ ":${_target_name}${_main_target_suffix}" ] # TODO(danakj): This would not be needed if we stopped forwarding through # a group in the common (non-procmacro) case. if (_is_data_dep) { data_deps = [ ":${_target_name}${_main_target_suffix}" ] } if (_cxx_bindings != []) { public_deps += [ ":${_target_name}_cxx_generated" ] # Additionally, C++ bindings generated by Cxx can include C++ types # that come from the Cxx library, such as `rust::Str`. So any C++ # target that depends on a rust target directly may need access to Cxx # as well, which means it must appear in public_deps. public_deps += [ "//build/rust:cxx_cppdeps" ] # cxx_cppdeps pulls in the default libstd, so make sure the default was # not overridden. assert( _use_local_std == use_local_std_by_default, "Rust targets with cxx bindings cannot override the default libstd") } else if (!defined(invoker.no_std) || !invoker.no_std) { # If C++ depends on and links in the library, we need to make sure C++ # links in the Rust stdlib. This is orthogonal to if the library exports # bindings for C++ to use. if (_use_local_std) { deps = [ "//build/rust/std:link_local_std" ] } else { assert(prebuilt_libstd_supported, "Prebuilt Rust stdlib is not available for this target") deps = [ "//build/rust/std:link_prebuilt_std" ] } } } _rust_deps = _deps _rust_aliased_deps = _aliased_deps _rust_public_deps = _public_deps _cxx_deps = _deps # The Rust target (and unit tests) need the Cxx crate when using it to # generate bindings. if (_cxx_bindings != []) { _rust_deps += [ "//build/rust:cxx_rustdeps" ] # C++ targets can depend on the Rust target from the BUILD.gn file to # access the headers generated from it _rust_public_deps += [ ":${_target_name}_cxx_generated" ] } if (!defined(invoker.no_std) || !invoker.no_std) { if (_use_local_std) { _rust_deps += [ "//build/rust/std:local_std_for_rustc" ] } else { _rust_deps += [ "//build/rust/std:prebuilt_std_for_rustc" ] } } else { not_needed([ "_use_local_std" ]) } # You must go through the groups above to get to these targets. _visibility = [] _visibility = [ ":${_target_name}" ] target(invoker.target_type, "${_target_name}${_main_target_suffix}") { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "features", "deps", "aliased_deps", "public_deps", "rustflags", "rustenv", "configs", "unit_test_output_dir", "unit_test_target", "test_inputs", ]) testonly = _testonly visibility = _visibility crate_name = _crate_name crate_root = _crate_root configs = [] configs = _configs deps = _rust_deps aliased_deps = _rust_aliased_deps public_deps = _rust_public_deps rustflags = _rustflags rustflags += [ "-Cmetadata=${_metadata}" ] rustenv = _rustenv # The Rust tool() declarations, like C++ ones, use the output_name and # output_dir, so that GN targets can override these if needed. Here we # give them their default values, or allow them to be overridden. if (defined(_output_dir)) { output_dir = _output_dir } if (!defined(output_name) || output_name == "") { output_name = crate_name } if (compute_inputs_for_analyze) { deps += [ ":${_target_name}_analyze" ] } if (!_allow_unsafe) { configs += [ "//build/rust:forbid_unsafe" ] } } if (compute_inputs_for_analyze) { # Find and depend on all rust files in the crate for the purpose of `gn # analyze`. analyze_rust("${_target_name}_analyze") { forward_variables_from(invoker, "*", [ "crate_root" ]) crate_root = _crate_root } } if (_cxx_bindings != []) { rust_cxx("${_target_name}_cxx_generated") { testonly = _testonly visibility = [ ":${_target_name}${_main_target_suffix}" ] if (defined(_visibility)) { visibility += _visibility } sources = _cxx_bindings deps = _cxx_deps + _public_deps if (is_component_build) { # In a component_build the cxx bindings may be linked into a shared # library at any point up the dependency tree, so always export. export_symbols = true } else if (invoker.target_type == "shared_library") { export_symbols = true } else { export_symbols = false } } } else { not_needed([ "_cxx_deps" ]) } if (_build_unit_tests) { _unit_test_target = "${_target_name}_unittests" if (defined(invoker.unit_test_target)) { _unit_test_target = invoker.unit_test_target } rust_unit_test(_unit_test_target) { forward_variables_from(invoker, [ "sources" ]) testonly = true crate_root = _crate_root rustflags = _rustflags env_out_dir = _env_out_dir if (defined(invoker.unit_test_output_dir)) { output_dir = invoker.unit_test_output_dir } deps = _rust_deps + _public_deps aliased_deps = _rust_aliased_deps public_deps = [ ":${_target_name}" ] if (defined(invoker.test_deps)) { deps += invoker.test_deps } inputs = [] if (defined(invoker.inputs)) { inputs += invoker.inputs } if (defined(invoker.test_inputs)) { inputs += invoker.test_inputs } if (defined(invoker.executable_configs)) { configs = [] configs = invoker.executable_configs } else if (!defined(configs)) { configs = [] } configs += _test_configs rustenv = _rustenv if (!_allow_unsafe) { configs += [ "//build/rust:forbid_unsafe" ] } } } else { not_needed([ "_crate_root", "_crate_name", "_metadata", "_test_configs", ]) } } } set_defaults("rust_target") { executable_configs = default_executable_configs library_configs = default_compiler_configs proc_macro_configs = default_rust_proc_macro_configs }