| 
 | 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