Skip to content

Commit

Permalink
rustc: correctly handle alwayslink staticlibs
Browse files Browse the repository at this point in the history
In order to do this, we have to inline get_libs_for_static_executable so we
can interact with the LibraryToLink entity as we're categorizing staticlibs.

Fixes #325.
  • Loading branch information
durin42 committed Feb 25, 2021
1 parent 77675ae commit 27ed182
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 30 deletions.
5 changes: 3 additions & 2 deletions bindgen/bindgen.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
load("//rust:rust.bzl", "rust_library")

# buildifier: disable=bzl-visibility
load("//rust/private:utils.bzl", "find_toolchain", "get_libs_for_static_executable")
load("//rust/private:utils.bzl", "find_toolchain", "get_preferred_artifact")

def rust_bindgen_library(
name,
Expand Down Expand Up @@ -84,7 +84,8 @@ def _rust_bindgen_impl(ctx):
output = ctx.outputs.out

# libclang should only have 1 output file
libclang_dir = get_libs_for_static_executable(libclang).to_list()[0].dirname
linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
libclang_dir = depset([get_preferred_artifact(lib) for li in linker_inputs for lib in li.libraries]).to_list()[0].dirname
include_directories = cc_lib[CcInfo].compilation_context.includes.to_list()
quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes.to_list()
system_include_directories = cc_lib[CcInfo].compilation_context.system_includes.to_list()
Expand Down
52 changes: 37 additions & 15 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ load(
"expand_locations",
"find_cc_toolchain",
"get_lib_name",
"get_libs_for_static_executable",
"get_preferred_artifact",
"relativize",
"rule_attrs",
)
Expand Down Expand Up @@ -57,6 +57,7 @@ DepInfo = provider(
"transitive_dylibs": "depset[File]",
"transitive_libs": "List[File]: All transitive dependencies, not filtered by type.",
"transitive_staticlibs": "depset[File]",
"transitive_staticlibs_alwayslink": "depset[File]",
},
)

Expand Down Expand Up @@ -151,6 +152,7 @@ def collect_deps(label, deps, proc_macro_deps, aliases, toolchain):
transitive_crates = []
transitive_dylibs = []
transitive_staticlibs = []
transitive_staticlibs_alwayslink = []
transitive_build_infos = []
build_info = None

Expand All @@ -167,24 +169,29 @@ def collect_deps(label, deps, proc_macro_deps, aliases, toolchain):
transitive_crates.append(depset([dep[rust_common.crate_info]], transitive = [dep[DepInfo].transitive_crates]))
transitive_dylibs.append(dep[DepInfo].transitive_dylibs)
transitive_staticlibs.append(dep[DepInfo].transitive_staticlibs)
transitive_staticlibs_alwayslink.append(dep[DepInfo].transitive_staticlibs_alwayslink)
transitive_build_infos.append(dep[DepInfo].transitive_build_infos)
elif CcInfo in dep:
# This dependency is a cc_library

# TODO: We could let the user choose how to link, instead of always preferring to link static libraries.
libs = get_libs_for_static_executable(dep)

transitive_dylibs.append(depset([
lib
for lib in libs.to_list()
# Dynamic libraries may have a version number nowhere, or before (macos) or after (linux) the extension.
if lib.basename.endswith(toolchain.dylib_ext) or lib.basename.split(".", 2)[1] == toolchain.dylib_ext[1:]
]))
transitive_staticlibs.append(depset([
lib
for lib in libs.to_list()
if lib.basename.endswith(toolchain.staticlib_ext)
]))
linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
for li in linker_inputs:
always = []
static = []
dylib = []
for lib in li.libraries:
art = get_preferred_artifact(lib)
if lib.alwayslink:
always.append(lib.static_library or lib.pic_static_library)
# Dynamic libraries may have a version number nowhere, or before (macos) or after (linux) the extension.
elif art.basename.endswith(toolchain.dylib_ext) or art.basename.split(".", 2)[1] == toolchain.dylib_ext[1:]:
dylib.append(art)
elif art.basename.endswith(toolchain.staticlib_ext):
static.append(art)
transitive_staticlibs_alwayslink.append(depset(always))
transitive_dylibs.append(depset(dylib))
transitive_staticlibs.append(depset(static))
elif BuildInfo in dep:
if build_info:
fail("Several deps are providing build information, only one is allowed in the dependencies", "deps")
Expand All @@ -196,7 +203,7 @@ def collect_deps(label, deps, proc_macro_deps, aliases, toolchain):
transitive_crates_depset = depset(transitive = transitive_crates)
transitive_libs = depset(
[c.output for c in transitive_crates_depset.to_list()],
transitive = transitive_staticlibs + transitive_dylibs,
transitive = transitive_staticlibs + transitive_dylibs + transitive_staticlibs_alwayslink,
)

return (
Expand All @@ -208,6 +215,7 @@ def collect_deps(label, deps, proc_macro_deps, aliases, toolchain):
order = "topological", # dylib link flag ordering matters.
),
transitive_staticlibs = depset(transitive = transitive_staticlibs),
transitive_staticlibs_alwayslink = depset(transitive = transitive_staticlibs_alwayslink),
transitive_libs = transitive_libs.to_list(),
transitive_build_infos = depset(transitive = transitive_build_infos),
dep_env = build_info.dep_env if build_info else None,
Expand Down Expand Up @@ -822,6 +830,20 @@ def _add_native_link_flags(args, dep_info, crate_type, cc_toolchain, feature_con
args.add_all(dep_info.transitive_dylibs, map_each = get_lib_name, format_each = "-ldylib=%s")
args.add_all(dep_info.transitive_staticlibs, map_each = get_lib_name, format_each = "-lstatic=%s")

# alwayslink=True libraries need special handling so the alwayslink args
# make it to the linker. Eventually we might be able to use a first-class
# rustc flag for this (https://github.com/rust-lang/cargo/issues/7586),
# but until then we have to do it this way.
for lib in dep_info.transitive_staticlibs_alwayslink.to_list():
args.add_all([
"-C",
"link-arg=-Wl,--whole-archive",
"-C",
("link-arg=-l%s" % get_lib_name(lib)),
"-C",
"link-arg=-Wl,--no-whole-archive",
])

if crate_type in ["dylib", "cdylib"]:
# For shared libraries we want to link C++ runtime library dynamically
# (for example libstdc++.so or libc++.so).
Expand Down
14 changes: 1 addition & 13 deletions rust/private/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,7 @@ def determine_output_hash(crate_root):
"""
return repr(hash(crate_root.path))

def get_libs_for_static_executable(dep):
"""find the libraries used for linking a static executable.
Args:
dep (Target): A cc_library target.
Returns:
depset: A depset[File]
"""
linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
return depset([_get_preferred_artifact(lib) for li in linker_inputs for lib in li.libraries])

def _get_preferred_artifact(library_to_link):
def get_preferred_artifact(library_to_link):
"""Get the first available library to link from a LibraryToLink object.
Args:
Expand Down

0 comments on commit 27ed182

Please sign in to comment.