diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 863e5b6f3b..df54795d95 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -448,7 +448,8 @@ def construct_arguments( out_dir, build_env_files, build_flags_files, - emit = ["dep-info", "link"]): + emit = ["dep-info", "link"], + force_all_deps_direct = False): """Builds an Args object containing common rustc flags Args: @@ -468,6 +469,8 @@ def construct_arguments( build_env_files (list): Files containing rustc environment variables, for instance from `cargo_build_script` actions. build_flags_files (list): The output files of a `cargo_build_script` actions containing rustc build flags emit (list): Values for the --emit flag to rustc. + force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern + to the commandline as opposed to -L. Returns: tuple: A tuple of the following items @@ -598,7 +601,7 @@ def construct_arguments( _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, crate_info.type, toolchain, cc_toolchain, feature_configuration) # These always need to be added, even if not linking this crate. - add_crate_link_flags(rustc_flags, dep_info) + add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct) needs_extern_proc_macro_flag = "proc-macro" in [crate_info.type, crate_info.wrapped_crate_type] and \ crate_info.edition != "2015" @@ -647,7 +650,8 @@ def rustc_compile_action( crate_info, output_hash = None, rust_flags = [], - environ = {}): + environ = {}, + force_all_deps_direct = False): """Create and run a rustc compile action based on the current rule's attributes Args: @@ -658,6 +662,8 @@ def rustc_compile_action( output_hash (str, optional): The hashed path of the crate root. Defaults to None. rust_flags (list, optional): Additional flags to pass to rustc. Defaults to []. environ (dict, optional): A set of makefile expandable environment variables for the action + force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern + to the commandline as opposed to -L. Returns: list: A list of the following providers: @@ -710,6 +716,7 @@ def rustc_compile_action( out_dir = out_dir, build_env_files = build_env_files, build_flags_files = build_flags_files, + force_all_deps_direct = force_all_deps_direct, ) if hasattr(attr, "version") and attr.version != "0.0.0": @@ -962,16 +969,30 @@ def _get_dir_names(files): dirs[f.dirname] = None return dirs.keys() -def add_crate_link_flags(args, dep_info): +def add_crate_link_flags(args, dep_info, force_all_deps_direct = False): """Adds link flags to an Args object reference Args: args (Args): An arguments object reference dep_info (DepInfo): The current target's dependency info + force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern + to the commandline as opposed to -L. """ - # nb. Crates are linked via --extern regardless of their crate_type - args.add_all(dep_info.direct_crates, map_each = _crate_to_link_flag) + if force_all_deps_direct: + args.add_all( + depset( + transitive = [ + dep_info.direct_crates, + dep_info.transitive_crates, + ], + ), + uniquify = True, + map_each = _crate_to_link_flag, + ) + else: + # nb. Direct crates are linked via --extern regardless of their crate_type + args.add_all(dep_info.direct_crates, map_each = _crate_to_link_flag) args.add_all( dep_info.transitive_crates, map_each = _get_crate_dirname, @@ -979,16 +1000,24 @@ def add_crate_link_flags(args, dep_info): format_each = "-Ldependency=%s", ) -def _crate_to_link_flag(crate_info): +def _crate_to_link_flag(crate): """A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object Args: - crate_info (CrateInfo): A CrateInfo provider from the current rule + crate (CrateInfo|AliasableDepInfo): A CrateInfo or an AliasableDepInfo provider Returns: - list: Link flags for the current crate info + list: Link flags for the given provider """ - return ["--extern={}={}".format(crate_info.name, crate_info.dep.output.path)] + + # This is AliasableDepInfo, we should use the alias as a crate name + if hasattr(crate, "dep"): + name = crate.name + crate_info = crate.dep + else: + name = crate.name + crate_info = crate + return ["--extern={}={}".format(name, crate_info.output.path)] def _get_crate_dirname(crate): """A helper macro used by `add_crate_link_flags` for getting the directory name of the current crate's output path diff --git a/test/unit/force_all_deps_direct/BUILD.bazel b/test/unit/force_all_deps_direct/BUILD.bazel new file mode 100644 index 0000000000..12f22c5177 --- /dev/null +++ b/test/unit/force_all_deps_direct/BUILD.bazel @@ -0,0 +1,4 @@ +load(":force_all_deps_direct_test.bzl", "force_all_deps_direct_test_suite") + +############################ UNIT TESTS ############################# +force_all_deps_direct_test_suite(name = "force_all_deps_direct_test_suite") diff --git a/test/unit/force_all_deps_direct/direct.rs b/test/unit/force_all_deps_direct/direct.rs new file mode 100644 index 0000000000..eae45b3f72 --- /dev/null +++ b/test/unit/force_all_deps_direct/direct.rs @@ -0,0 +1,5 @@ +use transitive::transitive_fn; + +pub fn direct_fn() { + transitive_fn(); +} diff --git a/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl b/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl new file mode 100644 index 0000000000..7e0964aa08 --- /dev/null +++ b/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl @@ -0,0 +1,61 @@ +"""Unittest to verify that we can treat all dependencies as direct dependencies""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest") +load("//rust:defs.bzl", "rust_library") +load("//test/unit:common.bzl", "assert_action_mnemonic", "assert_argv_contains_prefix") +load("//test/unit/force_all_deps_direct:generator.bzl", "generator") + +def _force_all_deps_direct_rustc_flags_test(ctx): + env = analysistest.begin(ctx) + tut = analysistest.target_under_test(env) + action = tut.actions[1] + argv = action.argv + assert_action_mnemonic(env, action, "Rustc") + assert_argv_contains_prefix( + env, + action, + "--extern=transitive", + ) + return analysistest.end(env) + +force_all_deps_direct_test = analysistest.make(_force_all_deps_direct_rustc_flags_test) + +def _force_all_deps_direct_test(): + rust_library( + name = "direct", + srcs = ["direct.rs"], + edition = "2018", + deps = [":transitive"], + ) + + rust_library( + name = "transitive", + srcs = ["transitive.rs"], + edition = "2018", + ) + + generator( + name = "generate", + deps = [":direct"], + tags = ["noclippy"], + ) + + force_all_deps_direct_test( + name = "force_all_deps_direct_rustc_flags_test", + target_under_test = ":generate", + ) + +def force_all_deps_direct_test_suite(name): + """Entry-point macro called from the BUILD file. + + Args: + name: Name of the macro. + """ + _force_all_deps_direct_test() + + native.test_suite( + name = name, + tests = [ + ":force_all_deps_direct_rustc_flags_test", + ], + ) diff --git a/test/unit/force_all_deps_direct/generator.bzl b/test/unit/force_all_deps_direct/generator.bzl new file mode 100644 index 0000000000..e8ab4450f1 --- /dev/null +++ b/test/unit/force_all_deps_direct/generator.bzl @@ -0,0 +1,92 @@ +"""A custom rule that threats all its dependencies as direct dependencies.""" + +# buildifier: disable=bzl-visibility +load("//rust/private:common.bzl", "rust_common") + +# buildifier: disable=bzl-visibility +load("//rust/private:providers.bzl", "BuildInfo", "CrateInfo", "DepInfo", "DepVariantInfo") + +# buildifier: disable=bzl-visibility +load("//rust/private:rustc.bzl", "rustc_compile_action") + +def _generator_impl(ctx): + rs_file = ctx.actions.declare_file(ctx.label.name + "_generated.rs") + ctx.actions.run_shell( + outputs = [rs_file], + command = """cat < {} +use direct::direct_fn; +use transitive::transitive_fn; + +pub fn call_both() {} + direct_fn(); + transitive_fn(); +{} +EOF +""".format(rs_file.path, "{", "}"), + mnemonic = "WriteRsFile", + ) + + toolchain = ctx.toolchains[Label("//rust:toolchain")] + + # Determine unique hash for this rlib + output_hash = repr(hash(rs_file.path)) + crate_name = ctx.label.name + crate_type = "rlib" + + rust_lib_name = "{prefix}{name}-{lib_hash}{extension}".format( + prefix = "lib", + name = crate_name, + lib_hash = output_hash, + extension = ".rlib", + ) + + deps = [DepVariantInfo( + crate_info = dep[CrateInfo] if CrateInfo in dep else None, + dep_info = dep[DepInfo] if DepInfo in dep else None, + build_info = dep[BuildInfo] if BuildInfo in dep else None, + cc_info = dep[CcInfo] if CcInfo in dep else None, + ) for dep in ctx.attr.deps] + + rust_lib = ctx.actions.declare_file(rust_lib_name) + return rustc_compile_action( + ctx = ctx, + attr = ctx.attr, + toolchain = toolchain, + crate_info = rust_common.create_crate_info( + name = crate_name, + type = crate_type, + root = rs_file, + srcs = depset([rs_file]), + deps = depset(deps), + proc_macro_deps = depset([]), + aliases = {}, + output = rust_lib, + owner = ctx.label, + edition = "2018", + compile_data = depset([]), + rustc_env = {}, + is_test = False, + ), + output_hash = output_hash, + force_all_deps_direct = True, + ) + +generator = rule( + implementation = _generator_impl, + attrs = { + "deps": attr.label_list(), + "_cc_toolchain": attr.label( + default = "@bazel_tools//tools/cpp:current_cc_toolchain", + ), + "_error_format": attr.label(default = "@rules_rust//:error_format"), + "_process_wrapper": attr.label( + default = Label("@rules_rust//util/process_wrapper"), + executable = True, + allow_single_file = True, + cfg = "exec", + ), + }, + toolchains = ["@rules_rust//rust:toolchain", "@bazel_tools//tools/cpp:toolchain_type"], + incompatible_use_toolchain_transition = True, + fragments = ["cpp"], +) diff --git a/test/unit/force_all_deps_direct/transitive.rs b/test/unit/force_all_deps_direct/transitive.rs new file mode 100644 index 0000000000..2ac85dfb21 --- /dev/null +++ b/test/unit/force_all_deps_direct/transitive.rs @@ -0,0 +1 @@ +pub fn transitive_fn() {}