Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load(":duplicate_link_flags_test.bzl", "duplicate_link_flags_test_suite")

duplicate_link_flags_test_suite(name = "duplicate_link_flags_test_suite")
17 changes: 17 additions & 0 deletions cargo/tests/cargo_build_script/duplicate_link_flags/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fn main() {
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("missing OUT_DIR"));
std::fs::write(
out_dir.join("generated.rs"),
"const GENERATED_VALUE: &str = \"from_build_script\";\n",
)
.expect("could not write the generated fixture");
println!("cargo:rustc-cfg=build_script_cfg");
println!("cargo:rustc-env=BUILD_SCRIPT_VALUE=from_build_script");

let library = if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
"kernel32"
} else {
"c"
};
println!("cargo:rustc-link-lib=dylib={library}");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""Regression coverage for https://github.com/bazelbuild/rules_rust/issues/4291."""

load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("//cargo:defs.bzl", "cargo_build_script")
load("//rust:defs.bzl", "rust_binary", "rust_library")

def _link_flags_test_impl(ctx):
env = analysistest.begin(ctx)
target = analysistest.target_under_test(env)
rustc_actions = [action for action in target.actions if action.mnemonic == "Rustc"]
asserts.equals(env, 1, len(rustc_actions))

if rustc_actions:
argv = rustc_actions[0].argv
link_flags = [
argv[i + 1]
for i in range(len(argv) - 1)
if argv[i] == "--arg-file" and argv[i + 1].endswith("shared_script.linkflags")
]
asserts.equals(env, ctx.attr.expected_count, len(link_flags))

# Both the library and binary still need the script's non-link outputs.
for flag, suffix in [
("--arg-file", "shared_script.flags"),
("--arg-file", "shared_script.linksearchpaths"),
("--env-file", "shared_script.env"),
("--out-dir", "shared_script.out_dir"),
]:
matches = [
argv[i + 1]
for i in range(len(argv) - 1)
if argv[i] == flag and argv[i + 1].endswith(suffix)
]
asserts.equals(env, 1, len(matches), "missing build-script input: " + suffix)

return analysistest.end(env)

_link_flags_test = analysistest.make(
_link_flags_test_impl,
attrs = {"expected_count": attr.int(mandatory = True)},
)

def duplicate_link_flags_test_suite(name):
"""Verify a direct library owns its package build script's native flags.

Args:
name: Name of the test suite.
"""
cargo_build_script(
name = "shared_script",
srcs = ["build.rs"],
)

rust_library(
name = "lib",
srcs = ["lib.rs"],
deps = [":shared_script"],
)

rust_binary(
name = "bin_with_lib",
srcs = ["main.rs"],
deps = [":lib", ":shared_script"],
)

rust_binary(
name = "bin_without_lib",
srcs = ["main.rs"],
deps = [":shared_script"],
)

rust_library(
name = "intermediate_lib",
srcs = ["lib.rs"],
deps = [":lib", ":shared_script"],
)

rust_binary(
name = "bin_with_intermediate_lib",
srcs = ["main.rs"],
deps = [":intermediate_lib", ":shared_script"],
)

cargo_build_script(
name = "other_script",
srcs = ["build.rs"],
)

rust_library(
name = "unrelated_lib",
srcs = ["lib.rs"],
deps = [":other_script"],
)

rust_binary(
name = "bin_with_unrelated_lib",
srcs = ["main.rs"],
deps = [":shared_script", ":unrelated_lib"],
)

_link_flags_test(
name = "lib_link_flags_test",
target_under_test = ":lib",
expected_count = 1,
)

_link_flags_test(
name = "bin_with_lib_link_flags_test",
target_under_test = ":bin_with_lib",
expected_count = 0,
)

_link_flags_test(
name = "bin_without_lib_link_flags_test",
target_under_test = ":bin_without_lib",
expected_count = 1,
)

_link_flags_test(
name = "bin_with_intermediate_lib_link_flags_test",
target_under_test = ":bin_with_intermediate_lib",
expected_count = 0,
)

_link_flags_test(
name = "bin_with_unrelated_lib_link_flags_test",
target_under_test = ":bin_with_unrelated_lib",
expected_count = 1,
)

native.test_suite(
name = name,
tests = [
":lib_link_flags_test",
":bin_with_lib_link_flags_test",
":bin_without_lib_link_flags_test",
":bin_with_intermediate_lib_link_flags_test",
":bin_with_unrelated_lib_link_flags_test",
],
)
3 changes: 3 additions & 0 deletions cargo/tests/cargo_build_script/duplicate_link_flags/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn value() -> &'static str {
env!("BUILD_SCRIPT_VALUE")
}
8 changes: 8 additions & 0 deletions cargo/tests/cargo_build_script/duplicate_link_flags/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[cfg(not(build_script_cfg))]
compile_error!("the binary must receive its build script's cfg flags");

include!(concat!(env!("OUT_DIR"), "/generated.rs"));

fn main() {
assert_eq!(env!("BUILD_SCRIPT_VALUE"), GENERATED_VALUE);
}
2 changes: 2 additions & 0 deletions rust/private/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ CrateInfo = provider(
DepInfo = provider(
doc = "A provider containing information about a Crate's dependencies.",
fields = {
"build_script_linker_flags": "File, optional: Direct build-script native link flags not already supplied by a direct Rust library.",
"dep_env": "File: File with environment variables direct dependencies build scripts rely upon.",
"direct_build_info": "BuildInfo, optional: The build script directly attached to this crate, before deduplicating its native link flags.",
"direct_crates": "depset[AliasableDepInfo]",
"link_search_path_files": "depset[File]: All transitive files containing search paths to pass to the linker",
"transitive_build_infos": "depset[BuildInfo]",
Expand Down
22 changes: 19 additions & 3 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def collect_deps(

direct_build_infos = []
transitive_build_infos = []
library_build_infos = []

direct_link_search_paths = []
transitive_link_search_paths = []
Expand Down Expand Up @@ -252,6 +253,11 @@ def collect_deps(

is_proc_macro = _is_proc_macro(crate_info)

if crate_info.type in ("lib", "rlib", "dylib") and not crate_info.is_test:
library_build_info = getattr(dep_info, "direct_build_info", None)
if library_build_info:
library_build_infos.append(library_build_info)

direct_crates.append(crate_info)
if not is_proc_macro:
transitive_crates.append(dep_info.transitive_crates)
Expand Down Expand Up @@ -314,8 +320,17 @@ def collect_deps(
fail("rust targets can only depend on rust_library, rust_*_library or cc_library " +
"targets.")

# Cargo sends rustc-link-lib only to the package library when one exists.
# Keep the original BuildInfo so dependents can compare the same script,
# and retain its cfg, environment, OUT_DIR and search paths for this crate.
build_script_linker_flags = None
if build_info and build_info not in library_build_infos:
build_script_linker_flags = build_info.linker_flags

return (
rust_common.dep_info(
direct_build_info = build_info,
build_script_linker_flags = build_script_linker_flags,
direct_crates = depset(
direct_deps,
transitive = [extra_named_deps] if extra_named_deps else [],
Expand Down Expand Up @@ -2662,9 +2677,10 @@ def _process_build_scripts(
build_env_file = build_info.rustc_env
if build_info.flags:
build_flags_files.append(build_info.flags)
if build_info.linker_flags and include_link_flags:
build_flags_files.append(build_info.linker_flags)
direct_inputs.append(build_info.linker_flags)
linker_flags = getattr(dep_info, "build_script_linker_flags", build_info.linker_flags)
if linker_flags and include_link_flags:
build_flags_files.append(linker_flags)
direct_inputs.append(linker_flags)

# `cargo::rustc-link-arg-bins` applies only to binary targets, and (like
# cargo) only from the crate's own build script — not transitively.
Expand Down
Loading