Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add per-target link flags in the right place #2963

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindgen/private/bindgen.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def _rust_bindgen_impl(ctx):
open_arg = True
continue

_, _, linker_env = get_linker_and_args(ctx, ctx.attr, "bin", cc_toolchain, feature_configuration, None)
_, _, linker_env = get_linker_and_args(ctx, "bin", cc_toolchain, feature_configuration, None)
env.update(**linker_env)

# Set the dynamic linker search path so that clang uses the libstdcxx from the toolchain.
Expand Down
2 changes: 1 addition & 1 deletion cargo/private/cargo_build_script.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def _cargo_build_script_impl(ctx):
# Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version).
# We hope that the linker env is sufficient for the whole cc_toolchain.
cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
linker, link_args, linker_env = get_linker_and_args(ctx, ctx.attr, "bin", cc_toolchain, feature_configuration, None)
linker, link_args, linker_env = get_linker_and_args(ctx, "bin", cc_toolchain, feature_configuration, None)
env.update(**linker_env)
env["LD"] = linker
env["LDFLAGS"] = " ".join(_pwd_flags(link_args))
Expand Down
2 changes: 1 addition & 1 deletion examples/hello_world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ use hello_lib::greeter;

fn main() {
let hello = greeter::Greeter::new("Hello");
hello.greet("world");
hello.greet("world!");
}
18 changes: 8 additions & 10 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,11 @@ def get_cc_user_link_flags(ctx):
"""
return ctx.fragments.cpp.linkopts

def get_linker_and_args(ctx, attr, crate_type, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary = False):
def get_linker_and_args(ctx, crate_type, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary = False):
"""Gathers cc_common linker information

Args:
ctx (ctx): The current target's context object
attr (struct): Attributes to use in gathering linker args
crate_type (str): The target crate's type (i.e. "bin", "proc-macro", etc.).
cc_toolchain (CcToolchain): cc_toolchain for which we are creating build variables.
feature_configuration (FeatureConfiguration): Feature configuration to be queried.
Expand Down Expand Up @@ -437,13 +436,6 @@ def get_linker_and_args(ctx, attr, crate_type, cc_toolchain, feature_configurati
else:
fail("Unknown `crate_type`: {}".format(crate_type))

# Add linkopts from dependencies. This includes linkopts from transitive
# dependencies since they get merged up.
for dep in getattr(attr, "deps", []):
if CcInfo in dep and dep[CcInfo].linking_context:
for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list():
for flag in linker_input.user_link_flags:
user_link_flags.append(flag)
link_variables = cc_common.create_link_variables(
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
Expand Down Expand Up @@ -1013,7 +1005,7 @@ def construct_arguments(
else:
rpaths = depset()

ld, link_args, link_env = get_linker_and_args(ctx, attr, crate_info.type, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary = add_flags_for_binary)
ld, link_args, link_env = get_linker_and_args(ctx, crate_info.type, cc_toolchain, feature_configuration, rpaths, add_flags_for_binary = add_flags_for_binary)

env.update(link_env)
rustc_flags.add(ld, format = "--codegen=linker=%s")
Expand Down Expand Up @@ -1957,6 +1949,9 @@ def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows

return []

def _add_user_link_flags(ret, linker_input):
ret.extend(["--codegen=link-arg={}".format(flag) for flag in linker_input.user_link_flags])

def _make_link_flags_windows(make_link_flags_args, flavor_msvc):
linker_input, use_pic, ambiguous_libs, include_link_flags = make_link_flags_args
ret = []
Expand All @@ -1975,6 +1970,7 @@ def _make_link_flags_windows(make_link_flags_args, flavor_msvc):
])
elif include_link_flags:
ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True, flavor_msvc = flavor_msvc))
_add_user_link_flags(ret, linker_input)
return ret

def _make_link_flags_windows_msvc(make_link_flags_args):
Expand All @@ -1994,6 +1990,7 @@ def _make_link_flags_darwin(make_link_flags_args):
])
elif include_link_flags:
ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_darwin = True))
_add_user_link_flags(ret, linker_input)
return ret

def _make_link_flags_default(make_link_flags_args):
Expand All @@ -2011,6 +2008,7 @@ def _make_link_flags_default(make_link_flags_args):
])
elif include_link_flags:
ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default))
_add_user_link_flags(ret, linker_input)
return ret

def _libraries_dirnames(make_link_flags_args):
Expand Down
80 changes: 46 additions & 34 deletions test/linker_inputs_propagation/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import", "cc_library", "cc_test")
load(
"@rules_rust//rust:defs.bzl",
"rust_binary",
"rust_library",
"rust_shared_library",
"rust_static_library",
Expand All @@ -20,9 +21,15 @@ cc_library(
"foo_shared.cc",
],
hdrs = ["foo_shared.h"],
target_compatible_with = [
"@platforms//os:linux",
)

cc_library(
name = "foo_with_linkopts",
srcs = [
"foo_shared.cc",
],
hdrs = ["foo_shared.h"],
linkopts = ["-L/doesnotexist"],
)

cc_binary(
Expand Down Expand Up @@ -74,82 +81,87 @@ rust_static_library(
name = "staticlib_uses_foo",
srcs = ["bar_uses_foo.rs"],
edition = "2018",
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
deps = [":foo"],
)

rust_library(
name = "rlib_uses_foo_with_redundant_linkopts",
srcs = ["bar_uses_shared_foo_for_rust.rs"],
edition = "2018",
deps = [":foo_with_linkopts"],
)

rust_shared_library(
name = "sharedlib_uses_foo",
srcs = ["bar_uses_foo.rs"],
edition = "2018",
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
deps = [":foo"],
)

rust_static_library(
name = "staticlib_uses_shared_foo",
srcs = ["bar_uses_shared_foo.rs"],
edition = "2018",
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
deps = [":import_foo_shared"],
)

rust_static_library(
name = "sharedlib_uses_shared_foo",
srcs = ["bar_uses_shared_foo.rs"],
edition = "2018",
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
deps = [":import_foo_shared"],
)

cc_test(
name = "depends_on_foo_via_staticlib",
srcs = ["baz.cc"],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
target_compatible_with = select({
"@platforms//os:linux": ["@platforms//:incompatible"],
"@platforms//os:macos": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [":staticlib_uses_foo"],
)

cc_test(
name = "depends_on_foo_via_sharedlib",
srcs = ["baz.cc"],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
target_compatible_with = select({
"@platforms//os:linux": ["@platforms//:incompatible"],
"@platforms//os:macos": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [":sharedlib_uses_foo"],
)

cc_test(
name = "depends_on_shared_foo_via_sharedlib",
srcs = ["baz.cc"],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
target_compatible_with = select({
"@platforms//os:macos": ["@platforms//:incompatible"],
"@platforms//os:windows": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [":sharedlib_uses_shared_foo"],
)

cc_test(
name = "depends_on_shared_foo_via_staticlib",
srcs = ["baz.cc"],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//os:windows",
],
target_compatible_with = select({
"@platforms//os:macos": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [":staticlib_uses_shared_foo"],
)

rust_binary(
name = "depends_on_foo_with_redundant_linkopts",
srcs = ["main_uses_foo.rs"],
edition = "2021",
target_compatible_with = select({
"@platforms//os:windows": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [":rlib_uses_foo_with_redundant_linkopts"],
)
12 changes: 12 additions & 0 deletions test/linker_inputs_propagation/bar_uses_shared_foo_for_rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
extern "C" {
pub fn foo() -> i32;
}

/** Safety doc.

# Safety

*/
pub fn double_foo() -> i32 {
2 * unsafe { foo() }
}
5 changes: 5 additions & 0 deletions test/linker_inputs_propagation/main_uses_foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use rlib_uses_foo_with_redundant_linkopts::double_foo;

fn main() {
println!("{}", double_foo());
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Unittests for propagation of linker inputs through Rust libraries"""

load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest")

def _shared_lib_is_propagated_test_impl(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]

lib_name = _get_lib_name(ctx, name = "foo_shared")
asserts.true(env, _contains_input(link_action.inputs, lib_name))
_assert_contains_input(env, link_action.inputs, lib_name)

return analysistest.end(env)

Expand All @@ -18,17 +18,34 @@ def _static_lib_is_not_propagated_test_impl(ctx):
link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]

lib_name = _get_lib_name(ctx, name = "foo")
asserts.false(env, _contains_input(link_action.inputs, lib_name))
asserts.false(env, _assert_contains_input(env, link_action.inputs, lib_name))

return analysistest.end(env)

def _contains_input(inputs, name):
def _dependency_linkopts_are_propagated_test_impl(ctx):
env = analysistest.begin(ctx)
tut = analysistest.target_under_test(env)
link_action = [action for action in tut.actions if action.mnemonic == "Rustc"][0]

# Expect a library's own linkopts to come after the flags we create to link them.
# This is required, because linkopts are ordered and the linker will only apply later ones when resolving symbols required for earlier ones.
# This means that if one of our transitive deps has a linkopt like `-lfoo`, the dep will see the symbols of foo at link time.
_assert_contains_in_order(env, link_action.argv, ["-lstatic=foo_with_linkopts", "-Clink-arg=-lfoo_with_linkopts", "--codegen=link-arg=-L/doesnotexist"])
return analysistest.end(env)

def _assert_contains_input(env, inputs, name):
for input in inputs.to_list():
# We cannot check for name equality because rlib outputs contain
# a hash in their name.
if input.basename.startswith(name):
return True
return False
return
unittest.fail(env, "Expected {} to contain a library starting with {}".format(inputs.to_list(), name))

def _assert_contains_in_order(env, haystack, needle):
for i in range(len(haystack)):
if haystack[i:i + len(needle)] == needle:
return
unittest.fail(env, "Expected {} to contain {}".format(haystack, needle))

def _get_lib_name(ctx, name):
if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
Expand All @@ -51,6 +68,10 @@ shared_lib_is_propagated_test = analysistest.make(
},
)

dependency_linkopts_are_propagated_test = analysistest.make(
_dependency_linkopts_are_propagated_test_impl,
)

def _linker_inputs_propagation_test():
static_lib_is_not_propagated_test(
name = "depends_on_foo_via_staticlib",
Expand All @@ -72,6 +93,11 @@ def _linker_inputs_propagation_test():
target_under_test = "//test/linker_inputs_propagation:depends_on_shared_foo_via_sharedlib",
)

dependency_linkopts_are_propagated_test(
name = "dependency_linkopts_are_propagated",
target_under_test = "//test/linker_inputs_propagation:depends_on_foo_with_redundant_linkopts",
)

def linker_inputs_propagation_test_suite(name):
"""Entry-point macro called from the BUILD file.

Expand All @@ -87,5 +113,6 @@ def linker_inputs_propagation_test_suite(name):
":depends_on_shared_foo_via_staticlib",
":depends_on_foo_via_sharedlib",
":depends_on_shared_foo_via_sharedlib",
":dependency_linkopts_are_propagated",
],
)
Loading