Skip to content

Commit d0a304b

Browse files
committed
ccinfo: when providing ccinfo, optionally include libstd and alloc
The new attribute on RustToolchain is the label of a target that provides __rust_realloc et al, which allows ld(1) to use the .rlib files directly without needing to involve rustc in the linking step. This means Rust and C++ can be mixed in a cc_binary freely without needing any staticlib-type crates, which avoids problems if you have a cc_binary -> rust_library -> cc_library -> rust_library situation.
1 parent 2de6496 commit d0a304b

File tree

6 files changed

+144
-6
lines changed

6 files changed

+144
-6
lines changed

rust/private/rustc.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,10 @@ def establish_cc_info(ctx, crate_info, toolchain, cc_toolchain, feature_configur
693693
if CcInfo in dep:
694694
cc_infos.append(dep[CcInfo])
695695

696+
if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
697+
# TODO: if we already have an rlib in our deps, we could skip this
698+
cc_infos.append(toolchain.libstd_and_allocator_ccinfo)
699+
696700
return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
697701

698702
def add_edition_flags(args, crate):

rust/toolchain.bzl

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,111 @@
11
"""The rust_toolchain rule definition and implementation."""
22

3+
load(
4+
"//rust/private:utils.bzl",
5+
"find_cc_toolchain",
6+
)
7+
8+
def _make_dota(ctx, f):
9+
"""Add a symlink for a file that ends in .a, so it can be used as a staticlib.
10+
11+
Args:
12+
ctx (ctx): The rule's context object.
13+
f (File): The file to symlink.
14+
15+
Returns:
16+
The symlink's File.
17+
"""
18+
dot_a = ctx.actions.declare_file(f.basename + ".a", sibling = f)
19+
ctx.actions.symlink(output = dot_a, target_file = f)
20+
return dot_a
21+
22+
def _make_libstd_and_allocator_ccinfo(ctx, rust_lib, allocator_library):
23+
"""Make the CcInfo (if possible) for libstd and allocator libraries.
24+
25+
Args:
26+
ctx (ctx): The rule's context object.
27+
rust_lib: The rust standard library.
28+
allocator_library: The target to use for providing allocator functions.
29+
30+
31+
Returns:
32+
A CcInfo object for the required libraries, or None if no such libraries are available.
33+
"""
34+
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
35+
link_inputs = []
36+
std_rlibs = [f for f in rust_lib.files.to_list() if f.basename.endswith(".rlib")]
37+
if std_rlibs:
38+
# std depends on everything
39+
#
40+
# core only depends on alloc, but we poke adler in there
41+
# because that needs to be before miniz_oxide
42+
#
43+
# alloc depends on the allocator_library if it's configured, but we
44+
# do that later.
45+
dot_a_files = [_make_dota(ctx, f) for f in std_rlibs]
46+
47+
alloc_inputs = depset([
48+
cc_common.create_library_to_link(
49+
actions = ctx.actions,
50+
feature_configuration = feature_configuration,
51+
cc_toolchain = cc_toolchain,
52+
static_library = f,
53+
pic_static_library = f,
54+
)
55+
for f in dot_a_files
56+
if "alloc" in f.basename
57+
])
58+
core_inputs = depset([
59+
cc_common.create_library_to_link(
60+
actions = ctx.actions,
61+
feature_configuration = feature_configuration,
62+
cc_toolchain = cc_toolchain,
63+
static_library = f,
64+
pic_static_library = f,
65+
)
66+
for f in dot_a_files
67+
if "core" in f.basename or "adler" in f.basename
68+
], transitive = [alloc_inputs], order = "topological")
69+
between_core_and_std_inputs = depset([
70+
cc_common.create_library_to_link(
71+
actions = ctx.actions,
72+
feature_configuration = feature_configuration,
73+
cc_toolchain = cc_toolchain,
74+
static_library = f,
75+
pic_static_library = f,
76+
)
77+
for f in dot_a_files
78+
if "core" not in f.basename and "alloc" not in f.basename and "std" not in f.basename and not "adler" in f.basename
79+
], transitive = [core_inputs], order = "topological")
80+
std_inputs = depset([
81+
cc_common.create_library_to_link(
82+
actions = ctx.actions,
83+
feature_configuration = feature_configuration,
84+
cc_toolchain = cc_toolchain,
85+
static_library = f,
86+
pic_static_library = f,
87+
)
88+
for f in dot_a_files
89+
if "std" in f.basename
90+
], transitive = [between_core_and_std_inputs], order = "topological")
91+
link_inputs.append(cc_common.create_linker_input(
92+
owner = rust_lib.label,
93+
libraries = std_inputs,
94+
))
95+
96+
allocator_inputs = None
97+
if allocator_library:
98+
allocator_inputs = [allocator_library[CcInfo].linking_context.linker_inputs]
99+
100+
libstd_and_allocator_ccinfo = None
101+
if link_inputs:
102+
return CcInfo(linking_context = cc_common.create_linking_context(linker_inputs = depset(
103+
link_inputs,
104+
transitive = allocator_inputs,
105+
order = "topological",
106+
)))
107+
return None
108+
3109
def _rust_toolchain_impl(ctx):
4110
"""The rust_toolchain implementation
5111
@@ -38,11 +144,13 @@ def _rust_toolchain_impl(ctx):
38144
default_edition = ctx.attr.default_edition,
39145
compilation_mode_opts = compilation_mode_opts,
40146
crosstool_files = ctx.files._crosstool,
147+
libstd_and_allocator_ccinfo = _make_libstd_and_allocator_ccinfo(ctx, ctx.attr.rust_lib, ctx.attr.allocator_library),
41148
)
42149
return [toolchain]
43150

44151
rust_toolchain = rule(
45152
implementation = _rust_toolchain_impl,
153+
fragments = ["cpp"],
46154
attrs = {
47155
"binary_ext": attr.string(
48156
doc = "The extension for binaries created from rustc.",
@@ -131,6 +239,12 @@ rust_toolchain = rule(
131239
"_crosstool": attr.label(
132240
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
133241
),
242+
"_cc_toolchain": attr.label(
243+
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
244+
),
245+
"allocator_library": attr.label(
246+
doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
247+
),
134248
},
135249
doc = """Declares a Rust toolchain for use.
136250

test/unit/cc_info/cc_info_test.bzl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro", "rust_
66
def _is_dylib_on_windows(ctx):
77
return ctx.target_platform_has_constraint(ctx.attr._windows[platform_common.ConstraintValueInfo])
88

9-
def _assert_cc_info_has_library_to_link(env, tut, type):
9+
def _assert_cc_info_has_library_to_link(env, tut, type, ccinfo_count):
1010
asserts.true(env, CcInfo in tut, "rust_library should provide CcInfo")
1111
cc_info = tut[CcInfo]
1212
linker_inputs = cc_info.linking_context.linker_inputs.to_list()
13-
asserts.equals(env, len(linker_inputs), 1)
13+
asserts.equals(env, len(linker_inputs), ccinfo_count)
1414
library_to_link = linker_inputs[0].libraries[0]
1515
asserts.equals(env, False, library_to_link.alwayslink)
1616

@@ -42,7 +42,7 @@ def _assert_cc_info_has_library_to_link(env, tut, type):
4242
def _rlib_provides_cc_info_test_impl(ctx):
4343
env = analysistest.begin(ctx)
4444
tut = analysistest.target_under_test(env)
45-
_assert_cc_info_has_library_to_link(env, tut, "rlib")
45+
_assert_cc_info_has_library_to_link(env, tut, "rlib", 2)
4646
return analysistest.end(env)
4747

4848
def _bin_does_not_provide_cc_info_test_impl(ctx):
@@ -60,13 +60,13 @@ def _proc_macro_does_not_provide_cc_info_test_impl(ctx):
6060
def _cdylib_provides_cc_info_test_impl(ctx):
6161
env = analysistest.begin(ctx)
6262
tut = analysistest.target_under_test(env)
63-
_assert_cc_info_has_library_to_link(env, tut, "cdylib")
63+
_assert_cc_info_has_library_to_link(env, tut, "cdylib", 1)
6464
return analysistest.end(env)
6565

6666
def _staticlib_provides_cc_info_test_impl(ctx):
6767
env = analysistest.begin(ctx)
6868
tut = analysistest.target_under_test(env)
69-
_assert_cc_info_has_library_to_link(env, tut, "staticlib")
69+
_assert_cc_info_has_library_to_link(env, tut, "staticlib", 1)
7070
return analysistest.end(env)
7171

7272
rlib_provides_cc_info_test = analysistest.make(_rlib_provides_cc_info_test_impl)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
void hello_from_rust();
2+
3+
int main(int argc, char** argv){
4+
hello_from_rust();
5+
}

test/unit/native_deps/native_deps_test.bzl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Unittests for rust rules."""
22

33
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest")
4-
load("@rules_cc//cc:defs.bzl", "cc_library")
4+
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
55
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro", "rust_shared_library", "rust_static_library")
66

77
def _assert_argv_contains_not(env, action, flag):
@@ -227,6 +227,17 @@ def _native_dep_test():
227227
deps = [":native_dep", ":alwayslink"],
228228
)
229229

230+
rust_library(
231+
name = "print_hi_rust",
232+
srcs = ["print_hi_rust.rs"],
233+
)
234+
235+
cc_binary(
236+
name = "cc_bin_uses_rust_library",
237+
srcs = ["cc_bin_uses_rust_library.cc"],
238+
deps = [":print_hi_rust"],
239+
)
240+
230241
rlib_has_no_native_libs_test(
231242
name = "rlib_has_no_native_libs_test",
232243
target_under_test = ":rlib_has_no_native_dep",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[no_mangle]
2+
pub extern "C" fn hello_from_rust() {
3+
println!("hello from rust");
4+
}

0 commit comments

Comments
 (0)