|
| 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 | + |
1 | 9 | use crate::EarlyDiagCtxt;
|
2 |
| -use crate::config::nightly_options; |
| 10 | +use crate::config::UnstableOptions; |
3 | 11 | use crate::utils::{NativeLib, NativeLibKind};
|
4 | 12 |
|
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; |
35 | 15 |
|
36 |
| -fn parse_native_lib_kind( |
| 16 | +/// Parses all `-l` options. |
| 17 | +pub(crate) fn parse_native_libs( |
37 | 18 | early_dcx: &EarlyDiagCtxt,
|
| 19 | + unstable_opts: &UnstableOptions, |
| 20 | + unstable_features: UnstableFeatures, |
38 | 21 | 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(), |
44 | 27 | };
|
| 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 | +} |
45 | 49 |
|
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 { |
47 | 55 | "static" => NativeLibKind::Static { bundle: None, whole_archive: None },
|
48 | 56 | "dylib" => NativeLibKind::Dylib { as_needed: None },
|
49 | 57 | "framework" => NativeLibKind::Framework { as_needed: None },
|
50 | 58 | "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 | + ); |
59 | 64 | NativeLibKind::LinkArg
|
60 | 65 | }
|
61 |
| - _ => early_dcx.early_fatal(format!( |
| 66 | + _ => cx.early_dcx.early_fatal(format!( |
62 | 67 | "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg"
|
63 | 68 | )),
|
| 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, |
64 | 77 | };
|
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 | + } |
68 | 84 | }
|
| 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 |
69 | 91 | }
|
70 | 92 |
|
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) |
136 | 117 | }
|
| 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 | + )), |
137 | 154 | }
|
| 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 | + }; |
138 | 190 |
|
139 |
| - (kind, verbatim) |
| 191 | + NativeLibParts { kind, modifiers, name, new_name } |
140 | 192 | }
|
0 commit comments