# Copyright 2021 The Chromium Authors. All rights reserved. # 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_autocxx.gni") import("//build/rust/rust_unit_test.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 # - mixed_static_library.gni - for 1p C++ and Rust code together. # # 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 # # support_use_from_cpp (bool) # Whether both C++ and Rust may link against this. If so, the Rust standard # library will be explicitly included for C++ to link against. This is # always true if cxx_bindings is non-empty, in which case [auto]cxx # will also be implicitly depended on. # # 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 != "") { _out_dir = invoker.output_dir } else { _out_dir = target_out_dir } 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 } _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 = [ string_join("", [ "//build/rust:edition_", _edition, ]) ] if (invoker.target_type == "executable") { if (defined(invoker.executable_configs)) { _configs += invoker.executable_configs } } else { if (defined(invoker.library_configs)) { _configs += invoker.library_configs } } _forward_to_host_toolchain = false if (invoker.target_type == "rust_proc_macro") { # TODO(crbug.com/gn/104): GN rust_proc_macro targets are missing this # command line flag, for the proc_macro crate which is provided by rustc for # compiling proc-macros. _rustflags += [ "--extern", "proc_macro", ] if (current_toolchain != host_toolchain) { _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 } _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/autocxx crates. 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 or autocxx bindings declarations. _cxx_bindings = [] if (defined(invoker.cxx_bindings)) { _cxx_bindings = invoker.cxx_bindings } _rustenv = [] if (defined(invoker.rustenv)) { _rustenv += invoker.rustenv } foreach(_autocxx_generated, _cxx_bindings) { # TODO(crbug.com/1306841): Currently we support only a single # include_cpp! macro per target and this ugly environment # variable is necessary. Meanwhile, this path must match that of # out_gen0_rs in rust_autocxx.gni. _rustenv += [ "AUTOCXX_RS_FILE=" + rebase_path( "$target_gen_dir/$_autocxx_generated/gen0.include.rs") ] } # Normally we generate a C++ bindings group only if there are C++ bindings, # since it's not useful otherwise. But mixed targets want to be able to depend # on the Rust side regardless, or other Rust targets that provide interop # without using Cxx to generate bindings (such as via #[no_mangle] functions). _support_use_from_cpp = _cxx_bindings != [] if (defined(invoker.support_use_from_cpp) && invoker.support_use_from_cpp) { _support_use_from_cpp = true } if (defined(invoker.mutually_dependent_target)) { _mutually_dependent_target = invoker.mutually_dependent_target _mutually_dependent_public_deps = invoker.mutually_dependent_public_deps } else { assert(!defined(_mutually_dependent_public_deps)) } # 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)" ] } not_needed(invoker, "*") not_needed([ "_build_unit_tests", "_crate_root", "_crate_name", "_cxx_bindings", "_deps", "_metadata", "_mutually_dependent_public_deps", "_mutually_dependent_target", "_out_dir", "_public_deps", "_rustenv", "_support_use_from_cpp", "_test_deps", "_testonly", "_visibility", "proc_macro_target", ]) } 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}" ] if (_support_use_from_cpp) { if (_cxx_bindings != []) { public_deps += [ ":${_target_name}_autocxx_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" ] } else { # If we're supporting use from C++ but not using Cxx bindings, then we # just need to make sure C++ links in the Rust stdlib. deps = [ "//build/rust/std" ] } } else { # Mixed targets (which define the below) always enable use from C++. assert(!defined(_mutually_dependent_target)) } } _rust_deps = _deps _rust_public_deps = _public_deps _autocxx_deps = _deps + _public_deps # In a mixed target, the C++ bindings may include headers from the C++ # part of the mixed target. Those headers rely on being used with the # correct dependencies present. if (defined(_mutually_dependent_public_deps)) { _autocxx_deps += _mutually_dependent_public_deps } # The Rust target (and unit tests) need the autocxx & cxx crates when using # them to generate bindings - both are in autocxx_rustdeps. We also need # to depend upon the autocxx code generation step, since when we build # our Rust code, a procedural macro will look for a file generated by that # codegen step. if (_cxx_bindings != []) { _rust_deps += [ "//build/rust:autocxx_rustdeps" ] # Generated bindings are a public dep so that the unit tests can include # them. _rust_public_deps += [ ":${_target_name}_autocxx_generated" ] } # 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", "public_deps", "rustflags", "rustenv", "configs", "output_dir", "unit_test_target", ]) testonly = _testonly visibility = _visibility crate_name = _crate_name crate_root = _crate_root configs = [] configs = _configs deps = _rust_deps public_deps = _rust_public_deps rustflags = _rustflags rustflags += [ string_join("", [ "-Cmetadata=", _metadata, ]) ] rustenv = _rustenv rustenv += [ "OUT_DIR=" + rebase_path(_out_dir) ] # 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. output_dir = _out_dir if (!defined(output_name) || output_name == "") { output_name = crate_name } } if (_cxx_bindings != []) { rust_autocxx("${_target_name}_autocxx_generated") { testonly = _testonly visibility = [ ":${_target_name}${_main_target_suffix}" ] if (defined(_visibility)) { visibility += _visibility } sources = _cxx_bindings deps = _autocxx_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([ "_autocxx_deps", "_mutually_dependent_public_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 output_dir = _out_dir deps = _rust_deps + _public_deps if (defined(invoker.test_deps)) { deps += invoker.test_deps } if (defined(_mutually_dependent_target)) { # This routes through the C++ mixed target rule to get back to the # Rust $_target_name. public_deps = [ _mutually_dependent_target ] } else { public_deps = [ ":${_target_name}" ] } if (defined(invoker.executable_configs)) { configs = [] configs = invoker.executable_configs } rustenv = _rustenv } } else { not_needed([ "_crate_root", "_crate_name", "_metadata", "_mutually_dependent_target", ]) } } } set_defaults("rust_target") { executable_configs = default_executable_configs library_configs = default_compiler_configs }