# 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_unit_test.gni") # The //build directory is re-used for non-Chromium projects. Do not support # cxx bindings in such contexts by default, because //third_party may be # missing. Projects that wish to use cxx bindings must explicitly set the # enable_rust_cxx GN arg to true. if (enable_rust_cxx) { 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") { _target_name = target_name # NOTE: TargetName=>CrateName mangling algorithm should be updated # simultaneously in 3 places: here, //build/rust/rust_static_library.gni, # //build/rust/chromium_prelude/import_attribute.rs if (defined(invoker.crate_name)) { _crate_name = invoker.crate_name } else { # Not using `get_label_info(..., "label_no_toolchain")` to consistently # use `//foo/bar:baz` instead of the alternative shorter `//foo/bar` form. _dir = get_label_info(":${_target_name}", "dir") _dir = string_replace(_dir, "//", "") _crate_name = "${_dir}:${_target_name}" # The `string_replace` calls below replicate the escaping algorithm # from the `escape_non_identifier_chars` function in # //build/rust/chromium_prelude/import_attribute.rs. Note that the # ordering of `match` branches within the Rust function doesn't matter, # but the ordering of `string_replace` calls *does* matter - the escape # character `_` needs to be handled first to meet the injectivity # requirement (otherwise we would get `/` => `_s` => `_us` and the same # result for `_s` => `_us`). _crate_name = string_replace(_crate_name, "_", "_u") _crate_name = string_replace(_crate_name, "/", "_s") _crate_name = string_replace(_crate_name, ":", "_c") _crate_name = string_replace(_crate_name, "-", "_d") } _generate_crate_root = defined(invoker.generate_crate_root) && invoker.generate_crate_root # Only one of `crate_root` or `generate_crate_root` can be specified, or # neither. assert(!defined(invoker.crate_root) || !_generate_crate_root) # 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 (_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};", "", ] } } _generated_crate_root = get_target_outputs(":${_target_name}_crate_root") _crate_root = _generated_crate_root[0] } 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 } _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 } assert(!defined(configs)) _configs = [ "//build/rust:edition_${_edition}" ] _test_configs = [] if (invoker.target_type == "executable") { _configs += invoker.executable_configs } else if (invoker.target_type == "rust_proc_macro") { _configs += invoker.proc_macro_configs _test_configs += [ "//build/rust:proc_macro_extern" ] } else if (invoker.target_type == "shared_library") { _configs += invoker.shared_library_configs } else { _configs += invoker.library_configs } if (invoker.target_type == "rust_proc_macro") { _main_target_suffix = "__proc_macro" } else if (invoker.target_type == "shared_library") { _main_target_suffix = "__proc_macro" } else { _main_target_suffix = "" } _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 = { } } _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 = [] assert(!defined(invoker.cxx_bindings) || enable_rust_cxx, "cxx bindings are not supported when building rust targets " + "outside the Chromium build.") if (defined(invoker.cxx_bindings)) { _cxx_bindings = invoker.cxx_bindings } _rustenv = [ "OUT_DIR=" + rebase_path(_env_out_dir, get_path_info(_crate_root, "dir")) ] if (defined(invoker.rustenv)) { _rustenv += invoker.rustenv } # 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/40200431) - verify this is correct assert(defined(invoker.sources), "sources must be listed") if (invoker.target_type == "rust_proc_macro" && !toolchain_for_rust_host_build_tools) { # Redirect to the proc macro toolchain, which uses prebuilt stdlib libraries # that are not built with panic=abort. group(_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } public_deps = [ ":${_target_name}${_main_target_suffix}($rust_macro_toolchain)" ] } not_needed(invoker, "*") not_needed([ "_aliased_deps", "_allow_unsafe", "_build_unit_tests", "_crate_root", "_crate_name", "_cxx_bindings", "_deps", "_rustc_metadata", "_out_dir", "_public_deps", "_rustenv", "_rustflags", "_support_use_from_cpp", "_testonly", ]) } else { # These are dependencies that must be included into the C++ target that # depends on this Rust one, and into the Rust target itself, respectively. # # For an rlib or exe, it's enough to add all these as dependencies of the # Rust target alone, and they will get included into the final link step. # # But when then Rust target is a shared library, the C++ target needs to # link the C++ thunks that are used to call the cxx bridge functions. And # Cxx library itself needs to be in both. _cxx_generated_deps_for_cpp = [] _cxx_generated_deps_for_rust = [] if (_cxx_bindings != []) { _cxx_generated_deps_for_cpp += [ # The Cxx-generated thunks, which have the public C++ names and bounce # over to the Rust code. ":${_target_name}_cxx_generated", # Additionally, C++ bindings generated by Cxx can include C++ types # that come from the Cxx library, such as `rust::Str`. The header and # implementation of these types are provided in the cxx_cppdeps target. # The C++ targets depending on this Rust target need the headers, while # the Rust target needs the implementation. "//build/rust:cxx_cppdeps", ] _cxx_generated_deps_for_rust = [ # The implementation of the Cxx library needs to be in the Rust target. "//build/rust:cxx_cppdeps", ] } # Proc macros and shared libraries have a group for the target name and # redirect to a suffixed target for the actual library. if (_main_target_suffix != "") { group(_target_name) { testonly = _testonly if (defined(_visibility)) { visibility = _visibility } public_deps = [ ":${_target_name}${_main_target_suffix}" ] public_deps += _cxx_generated_deps_for_cpp } } _rustc_metadata = "" if (defined(invoker.rustc_metadata)) { _rustc_metadata = invoker.rustc_metadata } _rust_deps = _deps _rust_aliased_deps = _aliased_deps _rust_public_deps = _public_deps _cxx_deps = _deps # Include the `chromium` crate in all first-party code. Third-party code # (and the `chromium` crate itself) opts out by setting # `no_chromium_prelude`. if (!defined(invoker.no_chromium_prelude) || !invoker.no_chromium_prelude) { if (enable_chromium_prelude) { _rust_deps += [ "//build/rust/chromium_prelude" ] } } if (_cxx_bindings != []) { # The Rust target (and unit tests) need the Cxx crate when using it to # generate bindings. _rust_deps += [ "//build/rust:cxx_rustdeps" ] } if (!defined(invoker.no_std) || !invoker.no_std) { _rust_deps += [ "//build/rust/std" ] } 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) { testonly = true crate_name = _unit_test_target crate_root = _crate_root sources = invoker.sources + [ 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 } configs += _test_configs rustenv = _rustenv if (!_allow_unsafe) { configs += [ "//build/rust:forbid_unsafe" ] } } } else { not_needed([ "_crate_root", "_crate_name", "_rustc_metadata", "_test_configs", ]) not_needed(invoker, [ "executable_configs" ]) } 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", ]) if (_main_target_suffix != "") { # There's a group that depends on this target, and dependencies must # be through that group. visibility = [ ":$_target_name" ] not_needed([ "_visibility" ]) } else if (defined(_visibility)) { visibility = _visibility } testonly = _testonly crate_name = _crate_name crate_root = _crate_root configs = [] configs = _configs deps = _rust_deps + _cxx_generated_deps_for_rust aliased_deps = _rust_aliased_deps public_deps = _rust_public_deps if (_main_target_suffix == "") { # When these are not provided by a wrapper group target, they are added # to the Rust target itself. public_deps += _cxx_generated_deps_for_cpp } rustflags = _rustflags if (_rustc_metadata != "") { rustflags += [ "-Cmetadata=${_rustc_metadata}" ] } rustenv = _rustenv if (_generate_crate_root) { deps += [ ":${_target_name}_crate_root" ] sources += [ _crate_root ] } if (!defined(output_name)) { # 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. if (invoker.target_type == "executable") { output_name = _target_name } else { # TODO(danakj): Since the crate name includes the whole path for 1p # libraries, we could move the output_dir to `root_out_dir` here for # them, which would make for shorter file paths. But we need to not # do the same for 3p crates or those with a `crate_name` set # explicitly. output_name = _crate_name } } if (!_allow_unsafe) { configs += [ "//build/rust:forbid_unsafe" ] } } if (_cxx_bindings != []) { rust_cxx("${_target_name}_cxx_generated") { testonly = _testonly visibility = [ ":${_target_name}" ] if (defined(_visibility)) { visibility += _visibility } sources = _cxx_bindings deps = _cxx_deps + _public_deps configs = _configs 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" ]) } } }