Skip to content

Commit

Permalink
ccinfo: when providing ccinfo, optionally include libstd and alloc
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
durin42 committed Mar 5, 2021
1 parent 2de6496 commit eda64bf
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 6 deletions.
4 changes: 4 additions & 0 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,10 @@ def establish_cc_info(ctx, crate_info, toolchain, cc_toolchain, feature_configur
if CcInfo in dep:
cc_infos.append(dep[CcInfo])

if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
# TODO: if we already have an rlib in our deps, we could skip this
cc_infos.append(toolchain.libstd_and_allocator_ccinfo)

return [cc_common.merge_cc_infos(cc_infos = cc_infos)]

def add_edition_flags(args, crate):
Expand Down
114 changes: 114 additions & 0 deletions rust/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,111 @@
"""The rust_toolchain rule definition and implementation."""

load(
"//rust/private:utils.bzl",
"find_cc_toolchain",
)

def _make_dota(ctx, f):
"""Add a symlink for a file that ends in .a, so it can be used as a staticlib.
Args:
ctx (ctx): The rule's context object.
f (File): The file to symlink.
Returns:
The symlink's File.
"""
dot_a = ctx.actions.declare_file(f.basename + ".a", sibling = f)
ctx.actions.symlink(output = dot_a, target_file = f)
return dot_a

def _make_libstd_and_allocator_ccinfo(ctx, rust_lib, allocator_library):
"""Make the CcInfo (if possible) for libstd and allocator libraries.
Args:
ctx (ctx): The rule's context object.
rust_lib: The rust standard library.
allocator_library: The target to use for providing allocator functions.
Returns:
A CcInfo object for the required libraries, or None if no such libraries are available.
"""
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
link_inputs = []
std_rlibs = [f for f in rust_lib.files.to_list() if f.basename.endswith(".rlib")]
if std_rlibs:
# std depends on everything
#
# core only depends on alloc, but we poke adler in there
# because that needs to be before miniz_oxide
#
# alloc depends on the allocator_library if it's configured, but we
# do that later.
dot_a_files = [_make_dota(ctx, f) for f in std_rlibs]

alloc_inputs = depset([
cc_common.create_library_to_link(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
static_library = f,
pic_static_library = f,
)
for f in dot_a_files
if "alloc" in f.basename
])
core_inputs = depset([
cc_common.create_library_to_link(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
static_library = f,
pic_static_library = f,
)
for f in dot_a_files
if "core" in f.basename or "adler" in f.basename
], transitive = [alloc_inputs], order = "topological")
between_core_and_std_inputs = depset([
cc_common.create_library_to_link(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
static_library = f,
pic_static_library = f,
)
for f in dot_a_files
if "core" not in f.basename and "alloc" not in f.basename and "std" not in f.basename and not "adler" in f.basename
], transitive = [core_inputs], order = "topological")
std_inputs = depset([
cc_common.create_library_to_link(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
static_library = f,
pic_static_library = f,
)
for f in dot_a_files
if "std" in f.basename
], transitive = [between_core_and_std_inputs], order = "topological")
link_inputs.append(cc_common.create_linker_input(
owner = rust_lib.label,
libraries = std_inputs,
))

allocator_inputs = None
if allocator_library:
allocator_inputs = [allocator_library[CcInfo].linking_context.linker_inputs]

libstd_and_allocator_ccinfo = None
if link_inputs:
return CcInfo(linking_context = cc_common.create_linking_context(linker_inputs = depset(
link_inputs,
transitive = allocator_inputs,
order = "topological",
)))
return None

def _rust_toolchain_impl(ctx):
"""The rust_toolchain implementation
Expand Down Expand Up @@ -38,12 +144,17 @@ def _rust_toolchain_impl(ctx):
default_edition = ctx.attr.default_edition,
compilation_mode_opts = compilation_mode_opts,
crosstool_files = ctx.files._crosstool,
libstd_and_allocator_ccinfo = _make_libstd_and_allocator_ccinfo(ctx, ctx.attr.rust_lib, ctx.attr.allocator_library),
)
return [toolchain]

rust_toolchain = rule(
implementation = _rust_toolchain_impl,
fragments = ["cpp"],
attrs = {
"allocator_library": attr.label(
doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.",
),
"binary_ext": attr.string(
doc = "The extension for binaries created from rustc.",
mandatory = True,
Expand Down Expand Up @@ -128,6 +239,9 @@ rust_toolchain = rule(
"For more details see: https://docs.bazel.build/versions/master/skylark/rules.html#configurations"
),
),
"_cc_toolchain": attr.label(
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
),
"_crosstool": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
Expand Down
10 changes: 5 additions & 5 deletions test/unit/cc_info/cc_info_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_proc_macro", "rust_
def _is_dylib_on_windows(ctx):
return ctx.target_platform_has_constraint(ctx.attr._windows[platform_common.ConstraintValueInfo])

def _assert_cc_info_has_library_to_link(env, tut, type):
def _assert_cc_info_has_library_to_link(env, tut, type, ccinfo_count):
asserts.true(env, CcInfo in tut, "rust_library should provide CcInfo")
cc_info = tut[CcInfo]
linker_inputs = cc_info.linking_context.linker_inputs.to_list()
asserts.equals(env, len(linker_inputs), 1)
asserts.equals(env, len(linker_inputs), ccinfo_count)
library_to_link = linker_inputs[0].libraries[0]
asserts.equals(env, False, library_to_link.alwayslink)

Expand Down Expand Up @@ -42,7 +42,7 @@ def _assert_cc_info_has_library_to_link(env, tut, type):
def _rlib_provides_cc_info_test_impl(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
_assert_cc_info_has_library_to_link(env, tut, "rlib")
_assert_cc_info_has_library_to_link(env, tut, "rlib", 2)
return analysistest.end(env)

def _bin_does_not_provide_cc_info_test_impl(ctx):
Expand All @@ -60,13 +60,13 @@ def _proc_macro_does_not_provide_cc_info_test_impl(ctx):
def _cdylib_provides_cc_info_test_impl(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
_assert_cc_info_has_library_to_link(env, tut, "cdylib")
_assert_cc_info_has_library_to_link(env, tut, "cdylib", 1)
return analysistest.end(env)

def _staticlib_provides_cc_info_test_impl(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
_assert_cc_info_has_library_to_link(env, tut, "staticlib")
_assert_cc_info_has_library_to_link(env, tut, "staticlib", 1)
return analysistest.end(env)

rlib_provides_cc_info_test = analysistest.make(_rlib_provides_cc_info_test_impl)
Expand Down
24 changes: 24 additions & 0 deletions test/unit/native_deps/alloc_shims.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <stddef.h>
#include <stdlib.h>

void* __rdl_alloc(size_t, size_t);
void __rdl_dealloc(void*);
void* __rdl_realloc(void*, size_t, size_t, size_t);
void* __rdl_alloc_zeroed(size_t, size_t);
void* __attribute__((weak)) __rust_alloc(size_t a, size_t b) {
return __rdl_alloc(a, b);
}
void __attribute__((weak)) __rust_dealloc(void* a) {
__rdl_dealloc(a);
}
void* __attribute__((weak))
__rust_realloc(void* a, size_t b, size_t c, size_t d) {
return __rdl_realloc(a, b, c, d);
}
void* __attribute__((weak)) __rust_alloc_zeroed(size_t a, size_t b) {
return __rdl_alloc_zeroed(a, b);
}

void __rust_alloc_error_handler(void*whatever) {
abort();
}
7 changes: 7 additions & 0 deletions test/unit/native_deps/cc_bin_uses_rust_library.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extern "C" {
void hello_from_rust();
}

int main(int argc, char** argv){
hello_from_rust();
}
21 changes: 20 additions & 1 deletion test/unit/native_deps/native_deps_test.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Unittests for rust rules."""

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

def _assert_argv_contains_not(env, action, flag):
Expand Down Expand Up @@ -227,6 +227,25 @@ def _native_dep_test():
deps = [":native_dep", ":alwayslink"],
)

cc_library(
name = "alloc_shims",
srcs = ["alloc_shims.c"],
)

rust_library(
name = "print_hi_rust",
srcs = ["print_hi_rust.rs"],
)

cc_binary(
name = "cc_bin_uses_rust_library",
srcs = ["cc_bin_uses_rust_library.cc"],
deps = [
":print_hi_rust",
":alloc_shims", # TODO: this should come in through the toolchain configuration
],
)

rlib_has_no_native_libs_test(
name = "rlib_has_no_native_libs_test",
target_under_test = ":rlib_has_no_native_dep",
Expand Down
4 changes: 4 additions & 0 deletions test/unit/native_deps/print_hi_rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[no_mangle]
pub extern "C" fn hello_from_rust() {
println!("hello from rust");
}

0 comments on commit eda64bf

Please sign in to comment.