| //! Parser for the `-l` command-line option, which links the generated crate to | |
| //! a native library. | |
| //! | |
| //! (There is also a similar but separate syntax for `#[link]` attributes, | |
| //! which have their own parser in `rustc_metadata`.) | |
| use rustc_feature::UnstableFeatures; | |
| use rustc_hir::attrs::NativeLibKind; | |
| use crate::EarlyDiagCtxt; | |
| use crate::config::UnstableOptions; | |
| use crate::utils::NativeLib; | |
| #[cfg(test)] | |
| mod tests; | |
| /// Parses all `-l` options. | |
| pub(crate) fn parse_native_libs( | |
| early_dcx: &EarlyDiagCtxt, | |
| unstable_opts: &UnstableOptions, | |
| unstable_features: UnstableFeatures, | |
| matches: &getopts::Matches, | |
| ) -> Vec<NativeLib> { | |
| let cx = ParseNativeLibCx { | |
| early_dcx, | |
| unstable_options_enabled: unstable_opts.unstable_options, | |
| is_nightly: unstable_features.is_nightly_build(), | |
| }; | |
| matches.opt_strs("l").into_iter().map(|value| parse_native_lib(&cx, &value)).collect() | |
| } | |
| struct ParseNativeLibCx<'a> { | |
| early_dcx: &'a EarlyDiagCtxt, | |
| unstable_options_enabled: bool, | |
| is_nightly: bool, | |
| } | |
| impl ParseNativeLibCx<'_> { | |
| /// If unstable values are not permitted, exits with a fatal error made by | |
| /// combining the given strings. | |
| fn on_unstable_value(&self, message: &str, if_nightly: &str, if_stable: &str) { | |
| if self.unstable_options_enabled { | |
| return; | |
| } | |
| let suffix = if self.is_nightly { if_nightly } else { if_stable }; | |
| self.early_dcx.early_fatal(format!("{message}{suffix}")); | |
| } | |
| } | |
| /// Parses the value of a single `-l` option. | |
| fn parse_native_lib(cx: &ParseNativeLibCx<'_>, value: &str) -> NativeLib { | |
| let NativeLibParts { kind, modifiers, name, new_name } = split_native_lib_value(value); | |
| let kind = kind.map_or(NativeLibKind::Unspecified, |kind| match kind { | |
| "static" => { | |
| NativeLibKind::Static { bundle: None, whole_archive: None, export_symbols: None } | |
| } | |
| "dylib" => NativeLibKind::Dylib { as_needed: None }, | |
| "framework" => NativeLibKind::Framework { as_needed: None }, | |
| "link-arg" => { | |
| cx.on_unstable_value( | |
| "library kind `link-arg` is unstable", | |
| ", the `-Z unstable-options` flag must also be passed to use it", | |
| " and only accepted on the nightly compiler", | |
| ); | |
| NativeLibKind::LinkArg | |
| } | |
| _ => cx.early_dcx.early_fatal(format!( | |
| "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" | |
| )), | |
| }); | |
| // Provisionally create the result, so that modifiers can modify it. | |
| let mut native_lib = NativeLib { | |
| name: name.to_owned(), | |
| new_name: new_name.map(str::to_owned), | |
| kind, | |
| verbatim: None, | |
| }; | |
| if let Some(modifiers) = modifiers { | |
| // If multiple modifiers are present, they are separated by commas. | |
| for modifier in modifiers.split(',') { | |
| parse_and_apply_modifier(cx, modifier, &mut native_lib); | |
| } | |
| } | |
| if native_lib.name.is_empty() { | |
| cx.early_dcx.early_fatal("library name must not be empty"); | |
| } | |
| native_lib | |
| } | |
| /// Parses one of the comma-separated modifiers (prefixed by `+` or `-`), and | |
| /// modifies `native_lib` appropriately. | |
| /// | |
| /// Exits with a fatal error if a malformed/unknown/inappropriate modifier is | |
| /// found. | |
| fn parse_and_apply_modifier(cx: &ParseNativeLibCx<'_>, modifier: &str, native_lib: &mut NativeLib) { | |
| let early_dcx = cx.early_dcx; | |
| // Split off the leading `+` or `-` into a boolean value. | |
| let (modifier, value) = match modifier.split_at_checked(1) { | |
| Some(("+", m)) => (m, true), | |
| Some(("-", m)) => (m, false), | |
| _ => cx.early_dcx.early_fatal( | |
| "invalid linking modifier syntax, expected '+' or '-' prefix \ | |
| before one of: bundle, verbatim, whole-archive, as-needed, export-symbols", | |
| ), | |
| }; | |
| // Assigns the value (from `+` or `-`) to an empty `Option<bool>`, or emits | |
| // a fatal error if the option has already been set. | |
| let assign_modifier = |opt_bool: &mut Option<bool>| { | |
| if opt_bool.is_some() { | |
| let msg = format!("multiple `{modifier}` modifiers in a single `-l` option"); | |
| early_dcx.early_fatal(msg) | |
| } | |
| *opt_bool = Some(value); | |
| }; | |
| // Check that the modifier is applicable to the native lib kind, and apply it. | |
| match (modifier, &mut native_lib.kind) { | |
| ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle), | |
| ("bundle", _) => early_dcx | |
| .early_fatal("linking modifier `bundle` is only compatible with `static` linking kind"), | |
| ("export-symbols", NativeLibKind::Static { export_symbols, .. }) => { | |
| assign_modifier(export_symbols) | |
| } | |
| ("export-symbols", _) => early_dcx.early_fatal( | |
| "linking modifier `export-symbols` is only compatible with `static` linking kind", | |
| ), | |
| ("verbatim", _) => assign_modifier(&mut native_lib.verbatim), | |
| ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => { | |
| assign_modifier(whole_archive) | |
| } | |
| ("whole-archive", _) => early_dcx.early_fatal( | |
| "linking modifier `whole-archive` is only compatible with `static` linking kind", | |
| ), | |
| ("as-needed", NativeLibKind::Dylib { as_needed }) | |
| | ("as-needed", NativeLibKind::Framework { as_needed }) | |
| | ("as-needed", NativeLibKind::RawDylib { as_needed }) => { | |
| cx.on_unstable_value( | |
| "linking modifier `as-needed` is unstable", | |
| ", the `-Z unstable-options` flag must also be passed to use it", | |
| " and only accepted on the nightly compiler", | |
| ); | |
| assign_modifier(as_needed) | |
| } | |
| ("as-needed", _) => early_dcx.early_fatal( | |
| "linking modifier `as-needed` is only compatible with \ | |
| `dylib` and `framework` linking kinds", | |
| ), | |
| _ => early_dcx.early_fatal(format!( | |
| "unknown linking modifier `{modifier}`, expected one \ | |
| of: bundle, verbatim, whole-archive, as-needed, export-symbols" | |
| )), | |
| } | |
| } | |
| #[derive(Debug, PartialEq, Eq)] | |
| struct NativeLibParts<'a> { | |
| kind: Option<&'a str>, | |
| modifiers: Option<&'a str>, | |
| name: &'a str, | |
| new_name: Option<&'a str>, | |
| } | |
| /// Splits a string of the form `[KIND[:MODIFIERS]=]NAME[:NEW_NAME]` into those | |
| /// individual parts. This cannot fail, but the resulting strings require | |
| /// further validation. | |
| fn split_native_lib_value(value: &str) -> NativeLibParts<'_> { | |
| // Split the initial value into `[KIND=]NAME`. | |
| let name = value; | |
| let (kind, name) = match name.split_once('=') { | |
| Some((prefix, name)) => (Some(prefix), name), | |
| None => (None, name), | |
| }; | |
| // Split the kind part, if present, into `KIND[:MODIFIERS]`. | |
| let (kind, modifiers) = match kind { | |
| Some(kind) => match kind.split_once(':') { | |
| Some((kind, modifiers)) => (Some(kind), Some(modifiers)), | |
| None => (Some(kind), None), | |
| }, | |
| None => (None, None), | |
| }; | |
| // Split the name part into `NAME[:NEW_NAME]`. | |
| let (name, new_name) = match name.split_once(':') { | |
| Some((name, new_name)) => (name, Some(new_name)), | |
| None => (name, None), | |
| }; | |
| NativeLibParts { kind, modifiers, name, new_name } | |
| } |