diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 8f2aa464af..dd089e9d2e 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -52,6 +52,7 @@ load( "get_edition", "get_import_macro_deps", "transform_deps", + "transform_link_deps", "transform_sources", ) @@ -66,20 +67,49 @@ def _assert_no_deprecated_attributes(_ctx): pass def _assert_correct_dep_mapping(ctx): - """Forces a failure if proc_macro_deps and deps are mixed inappropriately + """Ensures dependencies are correctly mapped between 'deps', 'proc_macro_deps', and 'link_deps'. + + This function validates that procedural macros and native libraries are listed in + their appropriate attributes to maintain the rules_rust dependency model. Args: ctx (ctx): The current rule's context object """ for dep in ctx.attr.deps: - if rust_common.crate_info in dep: - if dep[rust_common.crate_info].type == "proc-macro": + # Identify if this is a Rust-related target using any known Rust provider. + is_rust_target = ( + rust_common.crate_info in dep or + rust_common.crate_group_info in dep or + rust_common.test_crate_info in dep or + rust_common.dep_info in dep or + BuildInfo in dep + ) + + if is_rust_target: + if rust_common.crate_info in dep and dep[rust_common.crate_info].type == "proc-macro": fail( "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format( ctx.label, dep.label, ), ) + + continue + + # If it's not a known Rust target but provides CcInfo, it's a native library + # that should ideally be in 'link_deps'. + if CcInfo in dep: + # buildifier: disable=print + print( + ("\nWARNING: Target {dep} in 'deps' of {target} is a C++ library. " + + "Only Rust targets are allowed in 'deps'. " + + "Please use 'link_deps' for manual FFI linkage or 'cc_deps' for binding generation. " + + "Support for C++ libraries in 'deps' is deprecated and will be removed in a future release.").format( + dep = dep.label, + target = ctx.label, + ), + ) + for dep in ctx.attr.proc_macro_deps: if CrateInfo in dep: types = [dep[CrateInfo].type] @@ -217,6 +247,8 @@ def _rust_library_common(ctx, crate_type): ) deps = transform_deps(ctx.attr.deps) + if hasattr(ctx.attr, "link_deps"): + deps += transform_link_deps(ctx.attr.link_deps) proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx)) return rustc_compile_action( @@ -269,6 +301,8 @@ def _rust_binary_impl(ctx): output = ctx.actions.declare_file(output_filename + toolchain.binary_ext) deps = transform_deps(ctx.attr.deps) + if hasattr(ctx.attr, "link_deps"): + deps += transform_link_deps(ctx.attr.link_deps) proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx)) crate_root = getattr(ctx.file, "crate_root", None) @@ -356,6 +390,8 @@ def _rust_test_impl(ctx): crate_type = "bin" deps = transform_deps(ctx.attr.deps) + if hasattr(ctx.attr, "link_deps"): + deps += transform_link_deps(ctx.attr.link_deps) proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx)) if ctx.attr.crate and ctx.attr.srcs: @@ -707,15 +743,22 @@ _COMMON_ATTRS = { ), "deps": attr.label_list( doc = dedent("""\ - List of other libraries to be linked to this library target. + List of other Rust libraries to be linked to this library target. - These can be either other `rust_library` targets or `cc_library` targets if - linking a native library. + These must be targets that provide `CrateInfo`, such as `rust_library`. """), ), "edition": attr.string( doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.", ), + "link_deps": attr.label_list( + doc = dedent("""\ + List of other native libraries to be linked to this library target. + + These are typically `cc_library` targets. + """), + providers = [[CcInfo], [rust_common.crate_info]], + ), "lint_config": attr.label( doc = "Set of lints to apply when building this crate.", providers = [LintsInfo], @@ -1105,7 +1148,7 @@ rust_shared_library = rule( rust_proc_macro = rule( implementation = _rust_proc_macro_impl, provides = COMMON_PROVIDERS, - attrs = _COMMON_ATTRS, + attrs = {name: value for name, value in _COMMON_ATTRS.items() if name != "link_deps"}, fragments = ["cpp"], toolchains = [ str(Label("//rust:toolchain_type")), diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl index 9c2b4dd9b6..42e57e55aa 100644 --- a/rust/private/utils.bzl +++ b/rust/private/utils.bzl @@ -530,6 +530,23 @@ def transform_deps(deps): crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None, ) for dep in deps] +def transform_link_deps(link_deps): + """Transforms a [Target] into [DepVariantInfo] for native symbol linkage. + + Args: + link_deps (list of Targets): Dependencies coming from ctx.attr.link_deps + + Returns: + list of DepVariantInfos with only CcInfo populated. + """ + return [DepVariantInfo( + crate_info = None, + dep_info = None, + build_info = None, + cc_info = dep[CcInfo] if CcInfo in dep else None, + crate_group_info = None, + ) for dep in link_deps] + def get_import_macro_deps(ctx): """Returns a list of targets to be added to proc_macro_deps. diff --git a/test/unit/link_deps/BUILD.bazel b/test/unit/link_deps/BUILD.bazel new file mode 100644 index 0000000000..4dc4bb6316 --- /dev/null +++ b/test/unit/link_deps/BUILD.bazel @@ -0,0 +1,40 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@rules_cc//cc:defs.bzl", "cc_library") +load("//rust:defs.bzl", "rust_binary", "rust_library") +load(":link_deps_test.bzl", "link_deps_test") + +# A dummy library to be used as a dependency +rust_library( + name = "leaf_lib", + srcs = ["lib.rs"], +) + +# Test 1: rust_library supports link_deps +rust_library( + name = "main_lib", + srcs = ["lib.rs"], + link_deps = [":leaf_lib"], + tags = ["manual"], +) + +link_deps_test( + name = "link_deps_success_test", + target_under_test = ":main_lib", +) + +# Test 2: Build test linking a C library via link_deps +cc_library( + name = "c_lib", + srcs = ["c_lib.c"], +) + +rust_binary( + name = "rust_bin", + srcs = ["main.rs"], + link_deps = [":c_lib"], +) + +build_test( + name = "rust_bin_link_deps_test", + targets = [":rust_bin"], +) diff --git a/test/unit/link_deps/c_lib.c b/test/unit/link_deps/c_lib.c new file mode 100644 index 0000000000..7eac38c392 --- /dev/null +++ b/test/unit/link_deps/c_lib.c @@ -0,0 +1 @@ +int c_function() { return 42; } diff --git a/test/unit/link_deps/lib.rs b/test/unit/link_deps/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/test/unit/link_deps/lib.rs @@ -0,0 +1 @@ + diff --git a/test/unit/link_deps/link_deps_test.bzl b/test/unit/link_deps/link_deps_test.bzl new file mode 100644 index 0000000000..95049151f4 --- /dev/null +++ b/test/unit/link_deps/link_deps_test.bzl @@ -0,0 +1,31 @@ +"""Unit tests for link_deps.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("//rust:defs.bzl", "rust_common") + +def _link_deps_test_impl(ctx): + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + + # Verify that CcInfo (symbols) is present + asserts.true(env, CcInfo in target, "Target should provide CcInfo from link_deps") + + dep_info = target[rust_common.dep_info] + + # Verify that leaf_lib is NOT in direct_crates + for direct_crate in dep_info.direct_crates.to_list(): + asserts.not_equals(env, "leaf_lib", direct_crate.name, "link_deps should not be added to direct_crates") + + # Verify that transitive_noncrates specifically contains leaf_lib as a linking input + linker_inputs = dep_info.transitive_noncrates.to_list() + found_leaf_lib = False + for linker_input in linker_inputs: + if linker_input.owner == Label("//test/unit/link_deps:leaf_lib"): + found_leaf_lib = True + break + asserts.true(env, found_leaf_lib, "link_deps should pass leaf_lib as a linker input") + + return analysistest.end(env) + +link_deps_test = analysistest.make(_link_deps_test_impl) diff --git a/test/unit/link_deps/main.rs b/test/unit/link_deps/main.rs new file mode 100644 index 0000000000..5c094bd785 --- /dev/null +++ b/test/unit/link_deps/main.rs @@ -0,0 +1,9 @@ +extern "C" { + fn c_function() -> i32; +} + +fn main() { + unsafe { + assert_eq!(c_function(), 42); + } +}