Skip to content

Commit d86e06a

Browse files
authored
Don't propagate non-shared transitive linker inputs from rust_static|shared_library (#1299)
A Rust `staticlib` or `cdylib` doesn't need to propagate linker inputs of its dependencies, except for shared libraries. - The tests are currently disabled on osx because the CI [Toolchain does not support dynamic linking]( https://buildkite.com/bazel/rules-rust-rustlang/builds/6126#a83dbb56-50b0-4a95-bb39-09e3a78ed0d0). - I also had to link to `Bcrypt.lib` => rust-lang/rust#91974
1 parent 5abeb93 commit d86e06a

File tree

12 files changed

+283
-2
lines changed

12 files changed

+283
-2
lines changed

.bazelci/presubmit.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ default_linux_targets: &default_linux_targets
77
# TODO: Switch manual tag to platform constraint after bazel 4.0.
88
- "//test/versioned_dylib:versioned_dylib_test"
99
default_macos_targets: &default_macos_targets
10+
- "--"
1011
- "//..."
12+
# TODO: CI toolchain does not support dynamic linking.
13+
- "-//test/linker_inputs_propagation/..."
1114
default_windows_targets: &default_windows_targets
1215
- "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
1316
- "//..."

rust/platform/triple_mappings.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ _SYSTEM_TO_STDLIB_LINKFLAGS = {
163163
"unknown": [],
164164
"uwp": ["ws2_32.lib"],
165165
"wasi": [],
166-
"windows": ["advapi32.lib", "ws2_32.lib", "userenv.lib"],
166+
"windows": ["advapi32.lib", "ws2_32.lib", "userenv.lib", "Bcrypt.lib"],
167167
}
168168

169169
def cpu_arch_to_constraints(cpu_arch):

rust/private/rustc.bzl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,21 @@ def rustc_compile_action(
10151015
def _is_dylib(dep):
10161016
return not bool(dep.static_library or dep.pic_static_library)
10171017

1018+
def _collect_nonstatic_linker_inputs(cc_info):
1019+
shared_linker_inputs = []
1020+
for linker_input in cc_info.linking_context.linker_inputs.to_list():
1021+
dylibs = [
1022+
lib
1023+
for lib in linker_input.libraries
1024+
if _is_dylib(lib)
1025+
]
1026+
if dylibs:
1027+
shared_linker_inputs.append(cc_common.create_linker_input(
1028+
owner = linker_input.owner,
1029+
libraries = depset(dylibs),
1030+
))
1031+
return shared_linker_inputs
1032+
10181033
def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
10191034
"""If the produced crate is suitable yield a CcInfo to allow for interop with cc rules
10201035
@@ -1093,9 +1108,20 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co
10931108
CcInfo(linking_context = linking_context),
10941109
toolchain.stdlib_linkflags,
10951110
]
1111+
10961112
for dep in getattr(attr, "deps", []):
10971113
if CcInfo in dep:
1098-
cc_infos.append(dep[CcInfo])
1114+
# A Rust staticlib or shared library doesn't need to propagate linker inputs
1115+
# of its dependencies, except for shared libraries.
1116+
if crate_info.type in ["cdylib", "staticlib"]:
1117+
shared_linker_inputs = _collect_nonstatic_linker_inputs(dep[CcInfo])
1118+
if shared_linker_inputs:
1119+
linking_context = cc_common.create_linking_context(
1120+
linker_inputs = depset(shared_linker_inputs),
1121+
)
1122+
cc_infos.append(CcInfo(linking_context = linking_context))
1123+
else:
1124+
cc_infos.append(dep[CcInfo])
10991125

11001126
if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
11011127
# TODO: if we already have an rlib in our deps, we could skip this
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import", "cc_library", "cc_test")
2+
load(
3+
"@rules_rust//rust:defs.bzl",
4+
"rust_library",
5+
"rust_shared_library",
6+
"rust_static_library",
7+
)
8+
9+
package(default_visibility = ["//test/unit/linker_inputs_propagation:__pkg__"])
10+
11+
rust_library(
12+
name = "foo",
13+
srcs = ["foo.rs"],
14+
edition = "2018",
15+
)
16+
17+
cc_library(
18+
name = "foo_shared",
19+
srcs = [
20+
"foo_shared.cc",
21+
],
22+
hdrs = ["foo_shared.h"],
23+
)
24+
25+
cc_binary(
26+
name = "foo_shared.dll",
27+
srcs = [
28+
"foo_shared.cc",
29+
"foo_shared.h",
30+
],
31+
features = ["windows_export_all_symbols"],
32+
linkshared = True,
33+
)
34+
35+
filegroup(
36+
name = "shared_library_file",
37+
srcs = [":foo_shared"],
38+
output_group = "dynamic_library",
39+
)
40+
41+
filegroup(
42+
name = "interface_library_file",
43+
srcs = [":foo_shared.dll"],
44+
output_group = "interface_library",
45+
)
46+
47+
cc_import(
48+
name = "import_foo_shared",
49+
hdrs = ["foo_shared.h"],
50+
interface_library = select({
51+
"@platforms//os:windows": "interface_library_file",
52+
"//conditions:default": "shared_library_file",
53+
}),
54+
shared_library = select({
55+
"@platforms//os:windows": "foo_shared.dll",
56+
"//conditions:default": "shared_library_file",
57+
}),
58+
)
59+
60+
rust_static_library(
61+
name = "staticlib_uses_foo",
62+
srcs = ["bar_uses_foo.rs"],
63+
edition = "2018",
64+
deps = [":foo"],
65+
)
66+
67+
rust_shared_library(
68+
name = "sharedlib_uses_foo",
69+
srcs = ["bar_uses_foo.rs"],
70+
edition = "2018",
71+
deps = [":foo"],
72+
)
73+
74+
rust_static_library(
75+
name = "staticlib_uses_shared_foo",
76+
srcs = ["bar_uses_shared_foo.rs"],
77+
edition = "2018",
78+
deps = [":import_foo_shared"],
79+
)
80+
81+
rust_static_library(
82+
name = "sharedlib_uses_shared_foo",
83+
srcs = ["bar_uses_shared_foo.rs"],
84+
edition = "2018",
85+
deps = [":import_foo_shared"],
86+
)
87+
88+
cc_test(
89+
name = "depends_on_foo_via_staticlib",
90+
srcs = ["baz.cc"],
91+
deps = [":staticlib_uses_foo"],
92+
)
93+
94+
cc_test(
95+
name = "depends_on_foo_via_sharedlib",
96+
srcs = ["baz.cc"],
97+
deps = [":sharedlib_uses_foo"],
98+
)
99+
100+
cc_test(
101+
name = "depends_on_shared_foo_via_sharedlib",
102+
srcs = ["baz.cc"],
103+
deps = [":sharedlib_uses_shared_foo"],
104+
)
105+
106+
cc_test(
107+
name = "depends_on_shared_foo_via_staticlib",
108+
srcs = ["baz.cc"],
109+
deps = [":staticlib_uses_shared_foo"],
110+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** Safety doc.
2+
3+
# Safety
4+
5+
*/
6+
#[no_mangle]
7+
pub unsafe extern "C" fn double_foo() -> i32 {
8+
2 * foo::foo()
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extern "C" {
2+
pub fn foo() -> i32;
3+
}
4+
5+
/** Safety doc.
6+
7+
# Safety
8+
9+
*/
10+
#[no_mangle]
11+
pub unsafe extern "C" fn double_foo() -> i32 {
12+
2 * foo()
13+
}

test/linker_inputs_propagation/baz.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <assert.h>
2+
#include <inttypes.h>
3+
#include <stdlib.h>
4+
5+
extern "C" int32_t double_foo();
6+
7+
int main(int argc, char** argv) {
8+
assert(double_foo() == 84);
9+
return EXIT_SUCCESS;
10+
}

test/linker_inputs_propagation/foo.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn foo() -> i32 {
2+
42
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include <stdint.h>
2+
3+
extern "C" {
4+
int32_t foo() {
5+
return 42;
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <stdint.h>
2+
3+
extern "C" {
4+
int32_t foo();
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
load(":linker_inputs_propagation_test.bzl", "linker_inputs_propagation_test_suite")
2+
3+
############################ UNIT TESTS #############################
4+
linker_inputs_propagation_test_suite(name = "linker_inputs_propagation_test_suite")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""Unittests for propagation of linker inputs through Rust libraries"""
2+
3+
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
4+
5+
def _shared_lib_is_propagated_test_impl(ctx):
6+
env = analysistest.begin(ctx)
7+
tut = analysistest.target_under_test(env)
8+
link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]
9+
10+
lib_name = _get_lib_name(ctx, name = "foo_shared")
11+
asserts.true(env, _contains_input(link_action.inputs, lib_name))
12+
13+
return analysistest.end(env)
14+
15+
def _static_lib_is_not_propagated_test_impl(ctx):
16+
env = analysistest.begin(ctx)
17+
tut = analysistest.target_under_test(env)
18+
link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]
19+
20+
lib_name = _get_lib_name(ctx, name = "foo")
21+
asserts.false(env, _contains_input(link_action.inputs, lib_name))
22+
23+
return analysistest.end(env)
24+
25+
def _contains_input(inputs, name):
26+
for input in inputs.to_list():
27+
# We cannot check for name equality because rlib outputs contain
28+
# a hash in their name.
29+
if input.basename.startswith(name):
30+
return True
31+
return False
32+
33+
def _get_lib_name(ctx, name):
34+
if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
35+
return name
36+
else:
37+
return "lib{}".format(name)
38+
39+
static_lib_is_not_propagated_test = analysistest.make(
40+
_static_lib_is_not_propagated_test_impl,
41+
attrs = {
42+
"_windows_constraint": attr.label(default = Label("@platforms//os:windows")),
43+
},
44+
)
45+
46+
shared_lib_is_propagated_test = analysistest.make(
47+
_shared_lib_is_propagated_test_impl,
48+
attrs = {
49+
"_macos_constraint": attr.label(default = Label("@platforms//os:macos")),
50+
"_windows_constraint": attr.label(default = Label("@platforms//os:windows")),
51+
},
52+
)
53+
54+
def _linker_inputs_propagation_test():
55+
static_lib_is_not_propagated_test(
56+
name = "depends_on_foo_via_staticlib",
57+
target_under_test = "//test/linker_inputs_propagation:depends_on_foo_via_staticlib",
58+
)
59+
60+
shared_lib_is_propagated_test(
61+
name = "depends_on_shared_foo_via_staticlib",
62+
target_under_test = "//test/linker_inputs_propagation:depends_on_shared_foo_via_staticlib",
63+
)
64+
65+
static_lib_is_not_propagated_test(
66+
name = "depends_on_foo_via_sharedlib",
67+
target_under_test = "//test/linker_inputs_propagation:depends_on_foo_via_sharedlib",
68+
)
69+
70+
shared_lib_is_propagated_test(
71+
name = "depends_on_shared_foo_via_sharedlib",
72+
target_under_test = "//test/linker_inputs_propagation:depends_on_shared_foo_via_sharedlib",
73+
)
74+
75+
def linker_inputs_propagation_test_suite(name):
76+
"""Entry-point macro called from the BUILD file.
77+
78+
Args:
79+
name: Name of the macro.
80+
"""
81+
_linker_inputs_propagation_test()
82+
83+
native.test_suite(
84+
name = name,
85+
tests = [
86+
":depends_on_foo_via_staticlib",
87+
":depends_on_shared_foo_via_staticlib",
88+
":depends_on_foo_via_sharedlib",
89+
":depends_on_shared_foo_via_sharedlib",
90+
],
91+
)

0 commit comments

Comments
 (0)