Skip to content

Commit 78edefe

Browse files
committed
Overhaul the -l option parser (for linking to native libs)
1 parent 478db48 commit 78edefe

File tree

4 files changed

+225
-122
lines changed

4 files changed

+225
-122
lines changed

compiler/rustc_session/src/config.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use rustc_target::spec::{
3131
use tracing::debug;
3232

3333
pub use crate::config::cfg::{Cfg, CheckCfg, ExpectedValues};
34-
use crate::config::native_libs::parse_libs;
34+
use crate::config::native_libs::parse_native_libs;
3535
use crate::errors::FileWriteFail;
3636
pub use crate::options::*;
3737
use crate::search_paths::SearchPath;
@@ -2508,7 +2508,10 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
25082508
let debuginfo = select_debuginfo(matches, &cg);
25092509
let debuginfo_compression = unstable_opts.debuginfo_compression;
25102510

2511-
let libs = parse_libs(early_dcx, matches);
2511+
let crate_name = matches.opt_str("crate-name");
2512+
let unstable_features = UnstableFeatures::from_environment(crate_name.as_deref());
2513+
// Parse any `-l` flags, which link to native libraries.
2514+
let libs = parse_native_libs(early_dcx, &unstable_opts, unstable_features, matches);
25122515

25132516
let test = matches.opt_present("test");
25142517

@@ -2523,8 +2526,6 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
25232526

25242527
let externs = parse_externs(early_dcx, matches, &unstable_opts);
25252528

2526-
let crate_name = matches.opt_str("crate-name");
2527-
25282529
let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches, &unstable_opts);
25292530

25302531
let pretty = parse_pretty(early_dcx, &unstable_opts);
@@ -2598,7 +2599,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
25982599
error_format,
25992600
diagnostic_width,
26002601
externs,
2601-
unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()),
2602+
unstable_features,
26022603
crate_name,
26032604
libs,
26042605
debug_assertions,
+168-116
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,192 @@
1+
//! Parser for the `-l` command-line option, which links the generated crate to
2+
//! a native library.
3+
//!
4+
//! (There is also a similar but separate syntax for `#[link]` attributes,
5+
//! which have their own parser in `rustc_metadata`.)
6+
7+
use rustc_feature::UnstableFeatures;
8+
19
use crate::EarlyDiagCtxt;
2-
use crate::config::nightly_options;
10+
use crate::config::UnstableOptions;
311
use crate::utils::{NativeLib, NativeLibKind};
412

5-
pub(crate) fn parse_libs(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Vec<NativeLib> {
6-
matches
7-
.opt_strs("l")
8-
.into_iter()
9-
.map(|s| {
10-
// Parse string of the form "[KIND[:MODIFIERS]=]lib[:new_name]",
11-
// where KIND is one of "dylib", "framework", "static", "link-arg" and
12-
// where MODIFIERS are a comma separated list of supported modifiers
13-
// (bundle, verbatim, whole-archive, as-needed). Each modifier is prefixed
14-
// with either + or - to indicate whether it is enabled or disabled.
15-
// The last value specified for a given modifier wins.
16-
let (name, kind, verbatim) = match s.split_once('=') {
17-
None => (s, NativeLibKind::Unspecified, None),
18-
Some((kind, name)) => {
19-
let (kind, verbatim) = parse_native_lib_kind(early_dcx, matches, kind);
20-
(name.to_string(), kind, verbatim)
21-
}
22-
};
23-
24-
let (name, new_name) = match name.split_once(':') {
25-
None => (name, None),
26-
Some((name, new_name)) => (name.to_string(), Some(new_name.to_owned())),
27-
};
28-
if name.is_empty() {
29-
early_dcx.early_fatal("library name must not be empty");
30-
}
31-
NativeLib { name, new_name, kind, verbatim }
32-
})
33-
.collect()
34-
}
13+
#[cfg(test)]
14+
mod tests;
3515

36-
fn parse_native_lib_kind(
16+
/// Parses all `-l` options.
17+
pub(crate) fn parse_native_libs(
3718
early_dcx: &EarlyDiagCtxt,
19+
unstable_opts: &UnstableOptions,
20+
unstable_features: UnstableFeatures,
3821
matches: &getopts::Matches,
39-
kind: &str,
40-
) -> (NativeLibKind, Option<bool>) {
41-
let (kind, modifiers) = match kind.split_once(':') {
42-
None => (kind, None),
43-
Some((kind, modifiers)) => (kind, Some(modifiers)),
22+
) -> Vec<NativeLib> {
23+
let cx = ParseNativeLibCx {
24+
early_dcx,
25+
unstable_options_enabled: unstable_opts.unstable_options,
26+
is_nightly: unstable_features.is_nightly_build(),
4427
};
28+
matches.opt_strs("l").into_iter().map(|value| parse_native_lib(&cx, &value)).collect()
29+
}
30+
31+
struct ParseNativeLibCx<'a> {
32+
early_dcx: &'a EarlyDiagCtxt,
33+
unstable_options_enabled: bool,
34+
is_nightly: bool,
35+
}
36+
37+
impl ParseNativeLibCx<'_> {
38+
/// If unstable values are not permitted, exits with a fatal error made by
39+
/// combining the given strings.
40+
fn on_unstable_value(&self, message: &str, if_nightly: &str, if_stable: &str) {
41+
if self.unstable_options_enabled {
42+
return;
43+
}
44+
45+
let suffix = if self.is_nightly { if_nightly } else { if_stable };
46+
self.early_dcx.early_fatal(format!("{message}{suffix}"));
47+
}
48+
}
4549

46-
let kind = match kind {
50+
/// Parses the value of a single `-l` option.
51+
fn parse_native_lib(cx: &ParseNativeLibCx<'_>, value: &str) -> NativeLib {
52+
let NativeLibParts { kind, modifiers, name, new_name } = split_native_lib_value(value);
53+
54+
let kind = kind.map_or(NativeLibKind::Unspecified, |kind| match kind {
4755
"static" => NativeLibKind::Static { bundle: None, whole_archive: None },
4856
"dylib" => NativeLibKind::Dylib { as_needed: None },
4957
"framework" => NativeLibKind::Framework { as_needed: None },
5058
"link-arg" => {
51-
if !nightly_options::is_unstable_enabled(matches) {
52-
let why = if nightly_options::match_is_nightly_build(matches) {
53-
" and only accepted on the nightly compiler"
54-
} else {
55-
", the `-Z unstable-options` flag must also be passed to use it"
56-
};
57-
early_dcx.early_fatal(format!("library kind `link-arg` is unstable{why}"))
58-
}
59+
cx.on_unstable_value(
60+
"library kind `link-arg` is unstable",
61+
", the `-Z unstable-options` flag must also be passed to use it",
62+
" and only accepted on the nightly compiler",
63+
);
5964
NativeLibKind::LinkArg
6065
}
61-
_ => early_dcx.early_fatal(format!(
66+
_ => cx.early_dcx.early_fatal(format!(
6267
"unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg"
6368
)),
69+
});
70+
71+
// Provisionally create the result, so that modifiers can modify it.
72+
let mut native_lib = NativeLib {
73+
name: name.to_owned(),
74+
new_name: new_name.map(str::to_owned),
75+
kind,
76+
verbatim: None,
6477
};
65-
match modifiers {
66-
None => (kind, None),
67-
Some(modifiers) => parse_native_lib_modifiers(early_dcx, kind, modifiers, matches),
78+
79+
if let Some(modifiers) = modifiers {
80+
// If multiple modifiers are present, they are separated by commas.
81+
for modifier in modifiers.split(',') {
82+
parse_and_apply_modifier(cx, modifier, &mut native_lib);
83+
}
6884
}
85+
86+
if native_lib.name.is_empty() {
87+
cx.early_dcx.early_fatal("library name must not be empty");
88+
}
89+
90+
native_lib
6991
}
7092

71-
fn parse_native_lib_modifiers(
72-
early_dcx: &EarlyDiagCtxt,
73-
mut kind: NativeLibKind,
74-
modifiers: &str,
75-
matches: &getopts::Matches,
76-
) -> (NativeLibKind, Option<bool>) {
77-
let mut verbatim = None;
78-
for modifier in modifiers.split(',') {
79-
let (modifier, value) = match modifier.strip_prefix(['+', '-']) {
80-
Some(m) => (m, modifier.starts_with('+')),
81-
None => early_dcx.early_fatal(
82-
"invalid linking modifier syntax, expected '+' or '-' prefix \
83-
before one of: bundle, verbatim, whole-archive, as-needed",
84-
),
85-
};
86-
87-
let report_unstable_modifier = || {
88-
if !nightly_options::is_unstable_enabled(matches) {
89-
let why = if nightly_options::match_is_nightly_build(matches) {
90-
" and only accepted on the nightly compiler"
91-
} else {
92-
", the `-Z unstable-options` flag must also be passed to use it"
93-
};
94-
early_dcx.early_fatal(format!("linking modifier `{modifier}` is unstable{why}"))
95-
}
96-
};
97-
let assign_modifier = |dst: &mut Option<bool>| {
98-
if dst.is_some() {
99-
let msg = format!("multiple `{modifier}` modifiers in a single `-l` option");
100-
early_dcx.early_fatal(msg)
101-
} else {
102-
*dst = Some(value);
103-
}
104-
};
105-
match (modifier, &mut kind) {
106-
("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle),
107-
("bundle", _) => early_dcx.early_fatal(
108-
"linking modifier `bundle` is only compatible with `static` linking kind",
109-
),
110-
111-
("verbatim", _) => assign_modifier(&mut verbatim),
112-
113-
("whole-archive", NativeLibKind::Static { whole_archive, .. }) => {
114-
assign_modifier(whole_archive)
115-
}
116-
("whole-archive", _) => early_dcx.early_fatal(
117-
"linking modifier `whole-archive` is only compatible with `static` linking kind",
118-
),
119-
120-
("as-needed", NativeLibKind::Dylib { as_needed })
121-
| ("as-needed", NativeLibKind::Framework { as_needed }) => {
122-
report_unstable_modifier();
123-
assign_modifier(as_needed)
124-
}
125-
("as-needed", _) => early_dcx.early_fatal(
126-
"linking modifier `as-needed` is only compatible with \
127-
`dylib` and `framework` linking kinds",
128-
),
129-
130-
// Note: this error also excludes the case with empty modifier
131-
// string, like `modifiers = ""`.
132-
_ => early_dcx.early_fatal(format!(
133-
"unknown linking modifier `{modifier}`, expected one \
134-
of: bundle, verbatim, whole-archive, as-needed"
135-
)),
93+
/// Parses one of the comma-separated modifiers (prefixed by `+` or `-`), and
94+
/// modifies `native_lib` appropriately.
95+
///
96+
/// Exits with a fatal error if a malformed/unknown/inappropriate modifier is
97+
/// found.
98+
fn parse_and_apply_modifier(cx: &ParseNativeLibCx<'_>, modifier: &str, native_lib: &mut NativeLib) {
99+
let early_dcx = cx.early_dcx;
100+
101+
// Split off the leading `+` or `-` into a boolean value.
102+
let (modifier, value) = match modifier.split_at_checked(1) {
103+
Some(("+", m)) => (m, true),
104+
Some(("-", m)) => (m, false),
105+
_ => cx.early_dcx.early_fatal(
106+
"invalid linking modifier syntax, expected '+' or '-' prefix \
107+
before one of: bundle, verbatim, whole-archive, as-needed",
108+
),
109+
};
110+
111+
// Assigns the value (from `+` or `-`) to an empty `Option<bool>`, or emits
112+
// a fatal error if the option has already been set.
113+
let assign_modifier = |opt_bool: &mut Option<bool>| {
114+
if opt_bool.is_some() {
115+
let msg = format!("multiple `{modifier}` modifiers in a single `-l` option");
116+
early_dcx.early_fatal(msg)
136117
}
118+
*opt_bool = Some(value);
119+
};
120+
121+
// Check that the modifier is applicable to the native lib kind, and apply it.
122+
match (modifier, &mut native_lib.kind) {
123+
("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle),
124+
("bundle", _) => early_dcx
125+
.early_fatal("linking modifier `bundle` is only compatible with `static` linking kind"),
126+
127+
("verbatim", _) => assign_modifier(&mut native_lib.verbatim),
128+
129+
("whole-archive", NativeLibKind::Static { whole_archive, .. }) => {
130+
assign_modifier(whole_archive)
131+
}
132+
("whole-archive", _) => early_dcx.early_fatal(
133+
"linking modifier `whole-archive` is only compatible with `static` linking kind",
134+
),
135+
136+
("as-needed", NativeLibKind::Dylib { as_needed })
137+
| ("as-needed", NativeLibKind::Framework { as_needed }) => {
138+
cx.on_unstable_value(
139+
"linking modifier `as-needed` is unstable",
140+
", the `-Z unstable-options` flag must also be passed to use it",
141+
" and only accepted on the nightly compiler",
142+
);
143+
assign_modifier(as_needed)
144+
}
145+
("as-needed", _) => early_dcx.early_fatal(
146+
"linking modifier `as-needed` is only compatible with \
147+
`dylib` and `framework` linking kinds",
148+
),
149+
150+
_ => early_dcx.early_fatal(format!(
151+
"unknown linking modifier `{modifier}`, expected one \
152+
of: bundle, verbatim, whole-archive, as-needed"
153+
)),
137154
}
155+
}
156+
157+
#[derive(Debug, PartialEq, Eq)]
158+
struct NativeLibParts<'a> {
159+
kind: Option<&'a str>,
160+
modifiers: Option<&'a str>,
161+
name: &'a str,
162+
new_name: Option<&'a str>,
163+
}
164+
165+
/// Splits a string of the form `[KIND[:MODIFIERS]=]NAME[:NEW_NAME]` into those
166+
/// individual parts. This cannot fail, but the resulting strings require
167+
/// further validation.
168+
fn split_native_lib_value(value: &str) -> NativeLibParts<'_> {
169+
// Split the initial value into `[KIND=]NAME`.
170+
let name = value;
171+
let (kind, name) = match name.split_once('=') {
172+
Some((prefix, name)) => (Some(prefix), name),
173+
None => (None, name),
174+
};
175+
176+
// Split the kind part, if present, into `KIND[:MODIFIERS]`.
177+
let (kind, modifiers) = match kind {
178+
Some(kind) => match kind.split_once(':') {
179+
Some((kind, modifiers)) => (Some(kind), Some(modifiers)),
180+
None => (Some(kind), None),
181+
},
182+
None => (None, None),
183+
};
184+
185+
// Split the name part into `NAME[:NEW_NAME]`.
186+
let (name, new_name) = match name.split_once(':') {
187+
Some((name, new_name)) => (name, Some(new_name)),
188+
None => (name, None),
189+
};
138190

139-
(kind, verbatim)
191+
NativeLibParts { kind, modifiers, name, new_name }
140192
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::config::native_libs::{NativeLibParts, split_native_lib_value};
2+
3+
#[test]
4+
fn split() {
5+
// This is a unit test for some implementation details, so consider deleting
6+
// it if it gets in the way.
7+
use NativeLibParts as P;
8+
9+
let examples = &[
10+
("", P { kind: None, modifiers: None, name: "", new_name: None }),
11+
("foo", P { kind: None, modifiers: None, name: "foo", new_name: None }),
12+
("foo:", P { kind: None, modifiers: None, name: "foo", new_name: Some("") }),
13+
("foo:bar", P { kind: None, modifiers: None, name: "foo", new_name: Some("bar") }),
14+
(":bar", P { kind: None, modifiers: None, name: "", new_name: Some("bar") }),
15+
("kind=foo", P { kind: Some("kind"), modifiers: None, name: "foo", new_name: None }),
16+
(":mods=foo", P { kind: Some(""), modifiers: Some("mods"), name: "foo", new_name: None }),
17+
(":mods=:bar", P {
18+
kind: Some(""),
19+
modifiers: Some("mods"),
20+
name: "",
21+
new_name: Some("bar"),
22+
}),
23+
("kind=foo:bar", P {
24+
kind: Some("kind"),
25+
modifiers: None,
26+
name: "foo",
27+
new_name: Some("bar"),
28+
}),
29+
("kind:mods=foo", P {
30+
kind: Some("kind"),
31+
modifiers: Some("mods"),
32+
name: "foo",
33+
new_name: None,
34+
}),
35+
("kind:mods=foo:bar", P {
36+
kind: Some("kind"),
37+
modifiers: Some("mods"),
38+
name: "foo",
39+
new_name: Some("bar"),
40+
}),
41+
("::==::", P { kind: Some(""), modifiers: Some(":"), name: "=", new_name: Some(":") }),
42+
("==::==", P { kind: Some(""), modifiers: None, name: "=", new_name: Some(":==") }),
43+
];
44+
45+
for &(value, ref expected) in examples {
46+
println!("{value:?}");
47+
let actual = split_native_lib_value(value);
48+
assert_eq!(&actual, expected);
49+
}
50+
}

0 commit comments

Comments
 (0)