Skip to content

Commit 21f0a83

Browse files
committed
feat: introduce link_deps attribute for native library linkage
This change adds a dedicated link_deps attribute to Rust rules, allowing native symbols to be linked without exposing them as Rust crates. Key changes: - Added link_deps to _common_attrs for CcInfo/CrateInfo targets. - Filtered link_deps out of rust_proc_macro to avoid transition complexity. - Implemented transform_link_deps to extract linkage context while hiding crate metadata. - Added a proactive warning for native libraries incorrectly listed in deps. - Added unit tests in test/unit/link_deps to verify correct linkage.
1 parent 175bf94 commit 21f0a83

5 files changed

Lines changed: 111 additions & 10 deletions

File tree

rust/private/rust.bzl

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ load(
5252
"get_edition",
5353
"get_import_macro_deps",
5454
"transform_deps",
55+
"transform_link_deps",
5556
"transform_sources",
5657
)
5758

@@ -66,20 +67,49 @@ def _assert_no_deprecated_attributes(_ctx):
6667
pass
6768

6869
def _assert_correct_dep_mapping(ctx):
69-
"""Forces a failure if proc_macro_deps and deps are mixed inappropriately
70+
"""Ensures dependencies are correctly mapped between 'deps', 'proc_macro_deps', and 'link_deps'.
71+
72+
This function validates that procedural macros and native libraries are listed in
73+
their appropriate attributes to maintain the rules_rust dependency model.
7074
7175
Args:
7276
ctx (ctx): The current rule's context object
7377
"""
7478
for dep in ctx.attr.deps:
75-
if rust_common.crate_info in dep:
76-
if dep[rust_common.crate_info].type == "proc-macro":
79+
# Identify if this is a Rust-related target using any known Rust provider.
80+
is_rust_target = (
81+
rust_common.crate_info in dep or
82+
rust_common.crate_group_info in dep or
83+
rust_common.test_crate_info in dep or
84+
rust_common.dep_info in dep or
85+
BuildInfo in dep
86+
)
87+
88+
if is_rust_target:
89+
if rust_common.crate_info in dep and dep[rust_common.crate_info].type == "proc-macro":
7790
fail(
7891
"{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
7992
ctx.label,
8093
dep.label,
8194
),
8295
)
96+
97+
continue
98+
99+
# If it's not a known Rust target but provides CcInfo, it's a native library
100+
# that should ideally be in 'link_deps'.
101+
if CcInfo in dep:
102+
# buildifier: disable=print
103+
print(
104+
("\nWARNING: Target {dep} in 'deps' of {target} is a C++ library. " +
105+
"Only Rust targets are allowed in 'deps'. " +
106+
"Please use 'link_deps' for manual FFI linkage or 'cc_deps' for binding generation. " +
107+
"Support for C++ libraries in 'deps' is deprecated and will be removed in a future release.").format(
108+
dep = dep.label,
109+
target = ctx.label,
110+
),
111+
)
112+
83113
for dep in ctx.attr.proc_macro_deps:
84114
if CrateInfo in dep:
85115
types = [dep[CrateInfo].type]
@@ -217,6 +247,8 @@ def _rust_library_common(ctx, crate_type):
217247
)
218248

219249
deps = transform_deps(ctx.attr.deps)
250+
if hasattr(ctx.attr, "link_deps"):
251+
deps += transform_link_deps(ctx.attr.link_deps)
220252
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
221253

222254
return rustc_compile_action(
@@ -269,6 +301,8 @@ def _rust_binary_impl(ctx):
269301
output = ctx.actions.declare_file(output_filename + toolchain.binary_ext)
270302

271303
deps = transform_deps(ctx.attr.deps)
304+
if hasattr(ctx.attr, "link_deps"):
305+
deps += transform_link_deps(ctx.attr.link_deps)
272306
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
273307

274308
crate_root = getattr(ctx.file, "crate_root", None)
@@ -356,6 +390,8 @@ def _rust_test_impl(ctx):
356390

357391
crate_type = "bin"
358392
deps = transform_deps(ctx.attr.deps)
393+
if hasattr(ctx.attr, "link_deps"):
394+
deps += transform_link_deps(ctx.attr.link_deps)
359395
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
360396

361397
if ctx.attr.crate and ctx.attr.srcs:
@@ -713,15 +749,22 @@ _COMMON_ATTRS = {
713749
),
714750
"deps": attr.label_list(
715751
doc = dedent("""\
716-
List of other libraries to be linked to this library target.
752+
List of other Rust libraries to be linked to this library target.
717753
718-
These can be either other `rust_library` targets or `cc_library` targets if
719-
linking a native library.
754+
These must be targets that provide `CrateInfo`, such as `rust_library`.
720755
"""),
721756
),
722757
"edition": attr.string(
723758
doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.",
724759
),
760+
"link_deps": attr.label_list(
761+
doc = dedent("""\
762+
List of other native libraries to be linked to this library target.
763+
764+
These are typically `cc_library` targets.
765+
"""),
766+
providers = [[CcInfo], [rust_common.crate_info]],
767+
),
725768
"lint_config": attr.label(
726769
doc = "Set of lints to apply when building this crate.",
727770
providers = [LintsInfo],
@@ -1128,16 +1171,15 @@ rust_proc_macro = rule(
11281171
# need to declare `_allowlist_function_transition`, see
11291172
# https://docs.bazel.build/versions/main/skylark/config.html#user-defined-transitions.
11301173
attrs = dict(
1131-
_COMMON_ATTRS.items(),
1174+
{name: value for name, value in _COMMON_ATTRS.items() if name != "link_deps"}.items(),
11321175
_allowlist_function_transition = attr.label(
11331176
default = Label("//tools/allowlists/function_transition_allowlist"),
11341177
),
11351178
deps = attr.label_list(
11361179
doc = dedent("""\
1137-
List of other libraries to be linked to this library target.
1180+
List of other Rust libraries to be linked to this library target.
11381181
1139-
These can be either other `rust_library` targets or `cc_library` targets if
1140-
linking a native library.
1182+
These must be targets that provide `CrateInfo`, such as `rust_library`.
11411183
"""),
11421184
cfg = _proc_macro_dep_transition,
11431185
),

rust/private/utils.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,23 @@ def transform_deps(deps):
530530
crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None,
531531
) for dep in deps]
532532

533+
def transform_link_deps(link_deps):
534+
"""Transforms a [Target] into [DepVariantInfo] for native symbol linkage.
535+
536+
Args:
537+
link_deps (list of Targets): Dependencies coming from ctx.attr.link_deps
538+
539+
Returns:
540+
list of DepVariantInfos with only CcInfo populated.
541+
"""
542+
return [DepVariantInfo(
543+
crate_info = None,
544+
dep_info = None,
545+
build_info = None,
546+
cc_info = dep[CcInfo] if CcInfo in dep else None,
547+
crate_group_info = None,
548+
) for dep in link_deps]
549+
533550
def get_import_macro_deps(ctx):
534551
"""Returns a list of targets to be added to proc_macro_deps.
535552

test/unit/link_deps/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("//rust:defs.bzl", "rust_library", "rust_proc_macro")
2+
load(":link_deps_test.bzl", "link_deps_test", "link_deps_failure_test")
3+
4+
# A dummy library to be used as a dependency
5+
rust_library(
6+
name = "leaf_lib",
7+
srcs = ["lib.rs"],
8+
)
9+
10+
# Test 1: rust_library supports link_deps
11+
rust_library(
12+
name = "main_lib",
13+
srcs = ["lib.rs"],
14+
link_deps = [":leaf_lib"],
15+
tags = ["manual"],
16+
)
17+
18+
link_deps_test(
19+
name = "link_deps_success_test",
20+
target_under_test = ":main_lib",
21+
)

test/unit/link_deps/lib.rs

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("//rust:rust_common.bzl", "CrateInfo")
2+
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
3+
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
4+
5+
def _link_deps_test_impl(ctx):
6+
env = analysistest.begin(ctx)
7+
target = analysistest.target_under_test(env)
8+
9+
# Verify that CcInfo (symbols) is present
10+
asserts.true(env, CcInfo in target, "Target should provide CcInfo from link_deps")
11+
12+
return analysistest.end(env)
13+
14+
link_deps_test = analysistest.make(_link_deps_test_impl)
15+
16+
def _link_deps_failure_test_impl(ctx):
17+
env = analysistest.begin(ctx)
18+
asserts.expect_failure(env, "no such attribute 'link_deps' in 'rust_proc_macro' rule")
19+
return analysistest.end(env)
20+
21+
link_deps_failure_test = analysistest.make(_link_deps_failure_test_impl, expect_failure = True)

0 commit comments

Comments
 (0)