Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ load(
"get_edition",
"get_import_macro_deps",
"transform_deps",
"transform_link_deps",
"transform_sources",
)

Expand All @@ -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]
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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")),
Expand Down
17 changes: 17 additions & 0 deletions rust/private/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
40 changes: 40 additions & 0 deletions test/unit/link_deps/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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"],
)
Comment thread
Suyashagarw marked this conversation as resolved.

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"],
)
1 change: 1 addition & 0 deletions test/unit/link_deps/c_lib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int c_function() { return 42; }
1 change: 1 addition & 0 deletions test/unit/link_deps/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

31 changes: 31 additions & 0 deletions test/unit/link_deps/link_deps_test.bzl
Original file line number Diff line number Diff line change
@@ -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")
Comment thread
Suyashagarw marked this conversation as resolved.

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)
9 changes: 9 additions & 0 deletions test/unit/link_deps/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extern "C" {
fn c_function() -> i32;
}

fn main() {
unsafe {
assert_eq!(c_function(), 42);
}
}