rustc_session/config/
native_libs.rs

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
7use rustc_feature::UnstableFeatures;
8
9use crate::EarlyDiagCtxt;
10use crate::config::UnstableOptions;
11use crate::utils::{NativeLib, NativeLibKind};
12
13#[cfg(test)]
14mod tests;
15
16/// Parses all `-l` options.
17pub(crate) fn parse_native_libs(
18    early_dcx: &EarlyDiagCtxt,
19    unstable_opts: &UnstableOptions,
20    unstable_features: UnstableFeatures,
21    matches: &getopts::Matches,
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(),
27    };
28    matches.opt_strs("l").into_iter().map(|value| parse_native_lib(&cx, &value)).collect()
29}
30
31struct ParseNativeLibCx<'a> {
32    early_dcx: &'a EarlyDiagCtxt,
33    unstable_options_enabled: bool,
34    is_nightly: bool,
35}
36
37impl 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}
49
50/// Parses the value of a single `-l` option.
51fn 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 {
55        "static" => NativeLibKind::Static { bundle: None, whole_archive: None },
56        "dylib" => NativeLibKind::Dylib { as_needed: None },
57        "framework" => NativeLibKind::Framework { as_needed: None },
58        "link-arg" => {
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            );
64            NativeLibKind::LinkArg
65        }
66        _ => cx.early_dcx.early_fatal(format!(
67            "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg"
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,
77    };
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        }
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
91}
92
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.
98fn 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)
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        )),
154    }
155}
156
157#[derive(Debug, PartialEq, Eq)]
158struct 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.
168fn 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    };
190
191    NativeLibParts { kind, modifiers, name, new_name }
192}