Skip to content

Commit b0105ac

Browse files
committed
cargo_build_script: propagate cdylib and binary link args
A build script's cargo::rustc-cdylib-link-arg and cargo::rustc-link-arg-bins directives were dropped with a warning, so flags like napi-rs's `-undefined dynamic_lookup` never reached the linker. Route them through BuildInfo to the targets cargo applies them to: * rustc-cdylib-link-arg is collected into BuildInfo.cdylib_link_flags and applied to cdylib crates. Matching cargo, it propagates transitively: a cdylib picks up the cdylib link args of every build script in its transitive dependencies (rust-lang/cargo#9562). * rustc-link-arg-bins is collected into BuildInfo.bin_link_flags and applied to binary crates. Each set is written to its own flag file, added to the relevant rustc action as an --arg-file, so a non-matching crate type never reads it. The per-binary cargo::rustc-link-arg-bin=BIN=FLAG stays unsupported: a build script's outputs are shared by every target that depends on it, so a flag cannot be scoped to one named binary. Fixes #1062.
1 parent fb01b69 commit b0105ac

10 files changed

Lines changed: 255 additions & 3 deletions

File tree

cargo/private/cargo_build_script.bzl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,8 @@ def _cargo_build_script_impl(ctx):
335335
flags_out = ctx.actions.declare_file(ctx.label.name + ".flags")
336336
link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags")
337337
link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths") # rustc-link-search, propagated from transitive dependencies
338+
cdylib_link_flags = ctx.actions.declare_file(ctx.label.name + ".cdyliblinkflags") # rustc-cdylib-link-arg, applied to cdylib crates (incl. transitively)
339+
bin_link_flags = ctx.actions.declare_file(ctx.label.name + ".binlinkflags") # rustc-link-arg-bins, applied to binary crates
338340
compilation_mode_opt_level = get_compilation_mode_opts(ctx, toolchain).opt_level
339341

340342
script_data = []
@@ -549,6 +551,8 @@ def _cargo_build_script_impl(ctx):
549551
args.add(flags_out, format = "--flags_out=%s")
550552
args.add(link_flags, format = "--link_flags=%s")
551553
args.add(link_search_paths, format = "--link_search_paths=%s")
554+
args.add(cdylib_link_flags, format = "--cdylib_link_flags=%s")
555+
args.add(bin_link_flags, format = "--bin_link_flags=%s")
552556
args.add(dep_env_out, format = "--dep_env_out=%s")
553557
args.add(ctx.attr.rundir, format = "--rundir=%s")
554558

@@ -613,6 +617,8 @@ def _cargo_build_script_impl(ctx):
613617
flags_out,
614618
link_flags,
615619
link_search_paths,
620+
cdylib_link_flags,
621+
bin_link_flags,
616622
dep_env_out,
617623
runfiles_dir,
618624
] + extra_output,
@@ -640,6 +646,8 @@ def _cargo_build_script_impl(ctx):
640646
flags = flags_out,
641647
linker_flags = link_flags,
642648
link_search_paths = link_search_paths,
649+
cdylib_link_flags = cdylib_link_flags,
650+
bin_link_flags = bin_link_flags,
643651
compile_data = depset([runfiles_dir] + extra_output, transitive = script_data),
644652
),
645653
OutputGroupInfo(

cargo/private/cargo_build_script_runner/bin.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ fn run_buildrs() -> Result<(), String> {
4343
compile_flags_file,
4444
link_flags_file,
4545
link_search_paths_file,
46+
cdylib_link_flags_file,
47+
bin_link_flags_file,
4648
output_dep_env_path,
4749
stdout_path,
4850
stderr_path,
@@ -215,6 +217,8 @@ fn run_buildrs() -> Result<(), String> {
215217
compile_flags,
216218
link_flags,
217219
link_search_paths,
220+
cdylib_link_flags,
221+
bin_link_flags,
218222
} = BuildScriptOutput::outputs_to_flags(
219223
&buildrs_outputs,
220224
&exec_root.to_string_lossy(),
@@ -231,6 +235,14 @@ fn run_buildrs() -> Result<(), String> {
231235
link_search_paths_file, e
232236
)
233237
});
238+
write(&cdylib_link_flags_file, cdylib_link_flags.as_bytes()).unwrap_or_else(|e| {
239+
panic!(
240+
"Unable to write file {:?}: {:#?}",
241+
cdylib_link_flags_file, e
242+
)
243+
});
244+
write(&bin_link_flags_file, bin_link_flags.as_bytes())
245+
.unwrap_or_else(|e| panic!("Unable to write file {:?}: {:#?}", bin_link_flags_file, e));
234246

235247
if !exec_root_links.is_empty() {
236248
for link in exec_root_links {
@@ -397,6 +409,8 @@ struct Args {
397409
compile_flags_file: String,
398410
link_flags_file: String,
399411
link_search_paths_file: String,
412+
cdylib_link_flags_file: String,
413+
bin_link_flags_file: String,
400414
output_dep_env_path: String,
401415
stdout_path: Option<String>,
402416
stderr_path: Option<String>,
@@ -420,6 +434,10 @@ impl Args {
420434
Err("Argument `link_flags_file` not provided".to_owned());
421435
let mut link_search_paths_file: Result<String, String> =
422436
Err("Argument `link_search_paths_file` not provided".to_owned());
437+
let mut cdylib_link_flags_file: Result<String, String> =
438+
Err("Argument `cdylib_link_flags_file` not provided".to_owned());
439+
let mut bin_link_flags_file: Result<String, String> =
440+
Err("Argument `bin_link_flags_file` not provided".to_owned());
423441
let mut output_dep_env_path: Result<String, String> =
424442
Err("Argument `output_dep_env_path` not provided".to_owned());
425443
let mut stdout_path = None;
@@ -444,6 +462,10 @@ impl Args {
444462
link_flags_file = Ok(arg.split_off("--link_flags=".len()));
445463
} else if arg.starts_with("--link_search_paths=") {
446464
link_search_paths_file = Ok(arg.split_off("--link_search_paths=".len()));
465+
} else if arg.starts_with("--cdylib_link_flags=") {
466+
cdylib_link_flags_file = Ok(arg.split_off("--cdylib_link_flags=".len()));
467+
} else if arg.starts_with("--bin_link_flags=") {
468+
bin_link_flags_file = Ok(arg.split_off("--bin_link_flags=".len()));
447469
} else if arg.starts_with("--dep_env_out=") {
448470
output_dep_env_path = Ok(arg.split_off("--dep_env_out=".len()));
449471
} else if arg.starts_with("--stdout=") {
@@ -469,6 +491,8 @@ impl Args {
469491
compile_flags_file: compile_flags_file.unwrap(),
470492
link_flags_file: link_flags_file.unwrap(),
471493
link_search_paths_file: link_search_paths_file.unwrap(),
494+
cdylib_link_flags_file: cdylib_link_flags_file.unwrap(),
495+
bin_link_flags_file: bin_link_flags_file.unwrap(),
472496
output_dep_env_path: output_dep_env_path.unwrap(),
473497
stdout_path,
474498
stderr_path,

cargo/private/cargo_build_script_runner/lib.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ pub struct CompileAndLinkFlags {
2424
pub compile_flags: String,
2525
pub link_flags: String,
2626
pub link_search_paths: String,
27+
/// `-Clink-arg`s that apply only when the crate is built as a cdylib
28+
/// (`cargo::rustc-cdylib-link-arg`). The consuming rule gates these on the
29+
/// crate type, and propagates them transitively to cdylibs (matching cargo).
30+
pub cdylib_link_flags: String,
31+
/// `-Clink-arg`s that apply only when the crate is built as a binary
32+
/// (`cargo::rustc-link-arg-bins`). Gated on the crate type by the rule.
33+
pub bin_link_flags: String,
2734
}
2835

2936
/// Enum containing all the considered return value from the script
@@ -39,6 +46,10 @@ pub enum BuildScriptOutput {
3946
Flags(String),
4047
/// cargo::rustc-link-arg
4148
LinkArg(String),
49+
/// cargo::rustc-cdylib-link-arg
50+
CdylibLinkArg(String),
51+
/// cargo::rustc-link-arg-bins
52+
BinLinkArg(String),
4253
/// cargo::rustc-env
4354
Env(String),
4455
/// cargo::VAR=VALUE
@@ -100,10 +111,12 @@ impl BuildScriptOutput {
100111
Some(BuildScriptOutput::DepEnv(format!("METADATA={}", param)))
101112
}
102113
}
103-
"rustc-cdylib-link-arg" | "rustc-link-arg-bin" | "rustc-link-arg-bins" => {
104-
// cargo::rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
114+
// cargo::rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
115+
"rustc-cdylib-link-arg" => Some(BuildScriptOutput::CdylibLinkArg(param)),
116+
// cargo::rustc-link-arg-bins=FLAG – Passes custom flags to a linker for binaries.
117+
"rustc-link-arg-bins" => Some(BuildScriptOutput::BinLinkArg(param)),
118+
"rustc-link-arg-bin" => {
105119
// cargo::rustc-link-arg-bin=BIN=FLAG – Passes custom flags to a linker for the binary BIN.
106-
// cargo::rustc-link-arg-bins=FLAG – Passes custom flags to a linker for binaries.
107120
eprint!(
108121
"Warning: build script returned unsupported directive `{}`",
109122
split[0]
@@ -208,12 +221,18 @@ impl BuildScriptOutput {
208221
let mut compile_flags = Vec::new();
209222
let mut link_flags = Vec::new();
210223
let mut link_search_paths = Vec::new();
224+
let mut cdylib_link_flags = Vec::new();
225+
let mut bin_link_flags = Vec::new();
211226

212227
for flag in outputs {
213228
match flag {
214229
BuildScriptOutput::Cfg(e) => compile_flags.push(format!("--cfg={e}")),
215230
BuildScriptOutput::Flags(e) => compile_flags.push(e.to_owned()),
216231
BuildScriptOutput::LinkArg(e) => compile_flags.push(format!("-Clink-arg={e}")),
232+
BuildScriptOutput::CdylibLinkArg(e) => {
233+
cdylib_link_flags.push(format!("-Clink-arg={e}"))
234+
}
235+
BuildScriptOutput::BinLinkArg(e) => bin_link_flags.push(format!("-Clink-arg={e}")),
217236
BuildScriptOutput::LinkLib(e) => link_flags.push(format!("-l{e}")),
218237
BuildScriptOutput::LinkSearch(e) => link_search_paths.push(format!("-L{e}")),
219238
_ => {}
@@ -228,6 +247,12 @@ impl BuildScriptOutput {
228247
exec_root,
229248
out_dir,
230249
),
250+
cdylib_link_flags: Self::redact_flags(
251+
&cdylib_link_flags.join("\n"),
252+
exec_root,
253+
out_dir,
254+
),
255+
bin_link_flags: Self::redact_flags(&bin_link_flags.join("\n"), exec_root, out_dir),
231256
}
232257
}
233258

@@ -336,6 +361,8 @@ mod tests {
336361
.to_owned(),
337362
link_flags: "-lsdfsdf".to_owned(),
338363
link_search_paths: "-L${pwd}/bleh".to_owned(),
364+
cdylib_link_flags: "".to_owned(),
365+
bin_link_flags: "".to_owned(),
339366
}
340367
);
341368
}
@@ -364,6 +391,33 @@ non-assignment-instructions-are-ignored",
364391
from_read_buffer_to_env_and_flags_test_impl(buff);
365392
}
366393

394+
#[test]
395+
fn test_cdylib_and_bin_link_args() {
396+
let buff = Cursor::new(
397+
"
398+
cargo::rustc-cdylib-link-arg=-undefined
399+
cargo::rustc-link-arg-bins=-Wl,--whole-archive
400+
cargo::rustc-link-arg-bin=mybin=-Wl,--per-bin",
401+
);
402+
let result = BuildScriptOutput::outputs_from_reader(BufReader::new(buff));
403+
404+
// `rustc-link-arg-bin` (the per-binary form) is unsupported and dropped.
405+
assert_eq!(
406+
result,
407+
vec![
408+
BuildScriptOutput::CdylibLinkArg("-undefined".to_owned()),
409+
BuildScriptOutput::BinLinkArg("-Wl,--whole-archive".to_owned()),
410+
]
411+
);
412+
413+
let flags = BuildScriptOutput::outputs_to_flags(&result, "/some/absolute/path", "");
414+
assert_eq!(flags.cdylib_link_flags, "-Clink-arg=-undefined".to_owned());
415+
assert_eq!(
416+
flags.bin_link_flags,
417+
"-Clink-arg=-Wl,--whole-archive".to_owned()
418+
);
419+
}
420+
367421
/// Demonstrate that the old style single colon flags are all parsable
368422
#[test]
369423
fn test_legacy_from_read_buffer_to_env_and_flags() {
@@ -489,6 +543,8 @@ cargo::rustc-link-search=/abs/exec_root/other/path
489543
link_flags: "".to_owned(),
490544
link_search_paths:
491545
"-L${pwd}/${bazel-out/cfg/bin/pkg/_bs.out_dir}\n-L${pwd}/other/path".to_owned(),
546+
cdylib_link_flags: "".to_owned(),
547+
bin_link_flags: "".to_owned(),
492548
}
493549
);
494550
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
load(":cdylib_bin_link_args_test.bzl", "cdylib_bin_link_args_test_suite")
2+
3+
cdylib_bin_link_args_test_suite(name = "cdylib_bin_link_args_test_suite")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Intentionally emits nothing. The test asserts that this build script's
2+
// cdylib/bin link-arg files are wired to the matching crate type (and propagate
3+
// transitively to cdylibs); that holds regardless of the files' contents, and
4+
// emitting real link args here would break linking of the test binaries.
5+
// Parsing of the directives themselves is covered by the runner's unit tests.
6+
fn main() {}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""Tests for `cargo::rustc-cdylib-link-arg` and `cargo::rustc-link-arg-bins`."""
2+
3+
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
4+
load("//cargo:defs.bzl", "cargo_build_script")
5+
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_shared_library")
6+
load("//test/unit:common.bzl", "assert_action_mnemonic")
7+
8+
# The build script's `.cdyliblinkflags` / `.binlinkflags` outputs are passed to
9+
# rustc as `--arg-file <path>` (see `_process_build_scripts`). A flag only reaches
10+
# a consumer when its crate type matches, so the presence of a specific build
11+
# script's arg-file in the Rustc command line is what distinguishes a correctly
12+
# gated (and, for cdylibs, transitively propagated) consumer.
13+
def _has_arg_file(argv, suffix):
14+
for i in range(len(argv) - 1):
15+
if argv[i] == "--arg-file" and argv[i + 1].endswith(suffix):
16+
return True
17+
return False
18+
19+
def _link_args_test_impl(ctx):
20+
env = analysistest.begin(ctx)
21+
tut = analysistest.target_under_test(env)
22+
23+
action = None
24+
for a in tut.actions:
25+
if a.mnemonic == "Rustc":
26+
action = a
27+
break
28+
asserts.true(env, action != None, "Expected a Rustc action")
29+
assert_action_mnemonic(env, action, "Rustc")
30+
31+
for suffix in ctx.attr.expect_arg_files:
32+
asserts.true(env, _has_arg_file(action.argv, suffix), "expected --arg-file ending with '{}'".format(suffix))
33+
for suffix in ctx.attr.unexpected_arg_files:
34+
asserts.false(env, _has_arg_file(action.argv, suffix), "unexpected --arg-file ending with '{}'".format(suffix))
35+
36+
return analysistest.end(env)
37+
38+
_link_args_test = analysistest.make(
39+
_link_args_test_impl,
40+
attrs = {
41+
"expect_arg_files": attr.string_list(),
42+
"unexpected_arg_files": attr.string_list(),
43+
},
44+
)
45+
46+
def cdylib_bin_link_args_test_suite(name):
47+
"""Test that build-script cdylib/bin link args reach only the matching crate type.
48+
49+
Args:
50+
name: Name of the test suite.
51+
"""
52+
53+
# A build script reached only transitively, via `dep_lib`.
54+
cargo_build_script(
55+
name = "transitive_build_script",
56+
srcs = ["build.rs"],
57+
tags = ["manual"],
58+
)
59+
60+
rust_library(
61+
name = "dep_lib",
62+
srcs = ["lib.rs"],
63+
deps = [":transitive_build_script"],
64+
tags = ["manual"],
65+
)
66+
67+
# The consumers' own (direct) build script.
68+
cargo_build_script(
69+
name = "direct_build_script",
70+
srcs = ["build.rs"],
71+
tags = ["manual"],
72+
)
73+
74+
rust_shared_library(
75+
name = "cdylib",
76+
srcs = ["lib.rs"],
77+
deps = [":direct_build_script", ":dep_lib"],
78+
tags = ["manual"],
79+
)
80+
81+
rust_binary(
82+
name = "bin",
83+
srcs = ["bin.rs"],
84+
deps = [":direct_build_script", ":dep_lib"],
85+
tags = ["manual"],
86+
)
87+
88+
rust_library(
89+
name = "lib",
90+
srcs = ["lib.rs"],
91+
deps = [":direct_build_script"],
92+
tags = ["manual"],
93+
)
94+
95+
# A cdylib gets its own cdylib link args AND those of a transitive build
96+
# script, but not the bin link args.
97+
_link_args_test(
98+
name = "cdylib_consumer_test",
99+
target_under_test = ":cdylib",
100+
expect_arg_files = [
101+
"direct_build_script.cdyliblinkflags",
102+
"transitive_build_script.cdyliblinkflags",
103+
],
104+
unexpected_arg_files = ["direct_build_script.binlinkflags"],
105+
)
106+
107+
# A binary gets its own bin link args, but no cdylib link args (direct or
108+
# transitive).
109+
_link_args_test(
110+
name = "bin_consumer_test",
111+
target_under_test = ":bin",
112+
expect_arg_files = ["direct_build_script.binlinkflags"],
113+
unexpected_arg_files = [
114+
"direct_build_script.cdyliblinkflags",
115+
"transitive_build_script.cdyliblinkflags",
116+
],
117+
)
118+
119+
# An rlib gets neither.
120+
_link_args_test(
121+
name = "lib_consumer_test",
122+
target_under_test = ":lib",
123+
unexpected_arg_files = [
124+
"direct_build_script.cdyliblinkflags",
125+
"direct_build_script.binlinkflags",
126+
],
127+
)
128+
129+
native.test_suite(
130+
name = name,
131+
tests = [
132+
":cdylib_consumer_test",
133+
":bin_consumer_test",
134+
":lib_consumer_test",
135+
],
136+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub fn noop() {}

rust/private/providers.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ CrateGroupInfo = provider(
7979
BuildInfo = provider(
8080
doc = "A provider containing `rustc` build settings for a given Crate.",
8181
fields = {
82+
"bin_link_flags": "Optional[File]: file of `-Clink-arg`s to pass to rustc only when the crate is built as a binary (`cargo::rustc-link-arg-bins`).",
83+
"cdylib_link_flags": "Optional[File]: file of `-Clink-arg`s to pass to rustc only when the crate is built as a cdylib (`cargo::rustc-cdylib-link-arg`); propagated transitively to cdylibs.",
8284
"compile_data": "Depset[File]: Compile data provided by the build script that was not copied into `out_dir`.",
8385
"dep_env": "Optional[File]: extra build script environment variables to be set to direct dependencies.",
8486
"flags": "Optional[File]: file containing additional flags to pass to rustc",

0 commit comments

Comments
 (0)