Skip to content

Commit 802bcf9

Browse files
durin42hlopko
andauthored
ccinfo: when providing ccinfo, optionally include libstd and alloc (#624)
* 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. * Add verification that all files were properly partitioned * Disable print * stdlib_ordering: new tests to ensure check stdlib rlib ordering * native_deps: give up on testing cc_binary using rust_library This only works on clang today, and can only work on clang for the near-term. It's not clear how to write a clang-only test in this case, so we just give up for now. "I promise it works!" etc Co-authored-by: Augie Fackler <[email protected]> Co-authored-by: Marcel Hlopko <[email protected]>
1 parent 52d72db commit 802bcf9

File tree

6 files changed

+200
-5
lines changed

6 files changed

+200
-5
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: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,118 @@
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 _ltl(library, ctx, cc_toolchain, feature_configuration):
23+
return cc_common.create_library_to_link(
24+
actions = ctx.actions,
25+
feature_configuration = feature_configuration,
26+
cc_toolchain = cc_toolchain,
27+
static_library = library,
28+
pic_static_library = library,
29+
)
30+
31+
def _make_libstd_and_allocator_ccinfo(ctx, rust_lib, allocator_library):
32+
"""Make the CcInfo (if possible) for libstd and allocator libraries.
33+
34+
Args:
35+
ctx (ctx): The rule's context object.
36+
rust_lib: The rust standard library.
37+
allocator_library: The target to use for providing allocator functions.
38+
39+
40+
Returns:
41+
A CcInfo object for the required libraries, or None if no such libraries are available.
42+
"""
43+
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
44+
link_inputs = []
45+
std_rlibs = [f for f in rust_lib.files.to_list() if f.basename.endswith(".rlib")]
46+
if std_rlibs:
47+
# std depends on everything
48+
#
49+
# core only depends on alloc, but we poke adler in there
50+
# because that needs to be before miniz_oxide
51+
#
52+
# alloc depends on the allocator_library if it's configured, but we
53+
# do that later.
54+
dot_a_files = [_make_dota(ctx, f) for f in std_rlibs]
55+
56+
alloc_files = [f for f in dot_a_files if "alloc" in f.basename and "std" not in f.basename]
57+
between_alloc_and_core_files = [f for f in dot_a_files if "compiler_builtins" in f.basename]
58+
core_files = [f for f in dot_a_files if ("core" in f.basename or "adler" in f.basename) and "std" not in f.basename]
59+
between_core_and_std_files = [
60+
f
61+
for f in dot_a_files
62+
if "alloc" not in f.basename and "compiler_builtins" not in f.basename and "core" not in f.basename and "adler" not in f.basename and "std" not in f.basename
63+
]
64+
std_files = [f for f in dot_a_files if "std" in f.basename]
65+
66+
partitioned_files_len = len(alloc_files) + len(between_alloc_and_core_files) + len(core_files) + len(between_core_and_std_files) + len(std_files)
67+
if partitioned_files_len != len(dot_a_files):
68+
partitioned = alloc_files + between_alloc_and_core_files + core_files + between_core_and_std_files + std_files
69+
for f in sorted(partitioned):
70+
# buildifier: disable=print
71+
print("File partitioned: {}".format(f.basename))
72+
fail("rust_toolchain couldn't properly partition rlibs in rust_lib. Partitioned {} out of {} files. This is probably a bug in the rule implementation.".format(partitioned_files_len, len(dot_a_files)))
73+
74+
alloc_inputs = depset(
75+
[_ltl(f, ctx, cc_toolchain, feature_configuration) for f in alloc_files],
76+
)
77+
between_alloc_and_core_inputs = depset(
78+
[_ltl(f, ctx, cc_toolchain, feature_configuration) for f in between_alloc_and_core_files],
79+
transitive = [alloc_inputs],
80+
order = "topological",
81+
)
82+
core_inputs = depset(
83+
[_ltl(f, ctx, cc_toolchain, feature_configuration) for f in core_files],
84+
transitive = [between_alloc_and_core_inputs],
85+
order = "topological",
86+
)
87+
between_core_and_std_inputs = depset(
88+
[_ltl(f, ctx, cc_toolchain, feature_configuration) for f in between_core_and_std_files],
89+
transitive = [core_inputs],
90+
order = "topological",
91+
)
92+
std_inputs = depset(
93+
[_ltl(f, ctx, cc_toolchain, feature_configuration) for f in std_files],
94+
transitive = [between_core_and_std_inputs],
95+
order = "topological",
96+
)
97+
98+
link_inputs.append(cc_common.create_linker_input(
99+
owner = rust_lib.label,
100+
libraries = std_inputs,
101+
))
102+
103+
allocator_inputs = None
104+
if allocator_library:
105+
allocator_inputs = [allocator_library[CcInfo].linking_context.linker_inputs]
106+
107+
libstd_and_allocator_ccinfo = None
108+
if link_inputs:
109+
return CcInfo(linking_context = cc_common.create_linking_context(linker_inputs = depset(
110+
link_inputs,
111+
transitive = allocator_inputs,
112+
order = "topological",
113+
)))
114+
return None
115+
3116
def _rust_toolchain_impl(ctx):
4117
"""The rust_toolchain implementation
5118
@@ -38,12 +151,17 @@ def _rust_toolchain_impl(ctx):
38151
default_edition = ctx.attr.default_edition,
39152
compilation_mode_opts = compilation_mode_opts,
40153
crosstool_files = ctx.files._crosstool,
154+
libstd_and_allocator_ccinfo = _make_libstd_and_allocator_ccinfo(ctx, ctx.attr.rust_lib, ctx.attr.allocator_library),
41155
)
42156
return [toolchain]
43157

44158
rust_toolchain = rule(
45159
implementation = _rust_toolchain_impl,
160+
fragments = ["cpp"],
46161
attrs = {
162+
"allocator_library": attr.label(
163+
doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
164+
),
47165
"binary_ext": attr.string(
48166
doc = "The extension for binaries created from rustc.",
49167
mandatory = True,
@@ -128,6 +246,9 @@ rust_toolchain = rule(
128246
"For more details see: https://docs.bazel.build/versions/master/skylark/rules.html#configurations"
129247
),
130248
),
249+
"_cc_toolchain": attr.label(
250+
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
251+
),
131252
"_crosstool": attr.label(
132253
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
133254
),

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)

test/unit/stdlib_ordering/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
load(":stdlib_ordering.bzl", "stdlib_ordering_suite")
2+
3+
############################ UNIT TESTS #############################
4+
stdlib_ordering_suite(name = "stdlib_ordering_suite")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn print_stuff() {
2+
println!("stuff");
3+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Unittest to verify ordering of rust stdlib in rust_library() CcInfo"""
2+
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
3+
load("//rust:defs.bzl", "rust_library")
4+
5+
def _categorize_library(name):
6+
"""Given an rlib name, guess if it's std, core, or alloc."""
7+
if "std" in name:
8+
return "std"
9+
if "core" in name:
10+
return "core"
11+
if "alloc" in name:
12+
return "alloc"
13+
if "compiler_builtins" in name:
14+
return "compiler_builtins"
15+
return "other"
16+
17+
def _dedup_preserving_order(list):
18+
"""Given a list, deduplicate its elements preserving order."""
19+
r = []
20+
seen = {}
21+
for e in list:
22+
if e in seen:
23+
continue
24+
seen[e] = 1
25+
r.append(e)
26+
return r
27+
28+
def _libstd_ordering_test_impl(ctx):
29+
env = analysistest.begin(ctx)
30+
tut = analysistest.target_under_test(env)
31+
libs = [lib.static_library for li in tut[CcInfo].linking_context.linker_inputs.to_list() for lib in li.libraries]
32+
rlibs = [_categorize_library(lib.basename) for lib in libs if ".rlib" in lib.basename]
33+
set_to_check = _dedup_preserving_order([lib for lib in rlibs if lib != "other"])
34+
asserts.equals(env, ["std", "core", "compiler_builtins", "alloc"], set_to_check)
35+
return analysistest.end(env)
36+
37+
libstd_ordering_test = analysistest.make(_libstd_ordering_test_impl)
38+
39+
def _native_dep_test():
40+
rust_library(
41+
name = "some_rlib",
42+
srcs = ["some_rlib.rs"],
43+
)
44+
45+
libstd_ordering_test(
46+
name = "libstd_ordering_test",
47+
target_under_test = ":some_rlib",
48+
)
49+
50+
def stdlib_ordering_suite(name):
51+
"""Entry-point macro called from the BUILD file.
52+
53+
Args:
54+
name: Name of the macro.
55+
"""
56+
_native_dep_test()
57+
58+
native.test_suite(
59+
name = name,
60+
tests = [
61+
":libstd_ordering_test",
62+
],
63+
)

0 commit comments

Comments
 (0)