about summary refs log tree commit diff
diff options
context:
space:
mode:
authorZalathar <Zalathar@users.noreply.github.com>2024-11-11 21:42:42 +1100
committerZalathar <Zalathar@users.noreply.github.com>2024-11-18 15:55:12 +1100
commit78edefea9d8e4820d7ce7c1d846f89e60fe3b81c (patch)
tree8df22229547bf2feb3566e96a648b65c5bb480c2
parent478db489b3074126862cb71cbf924402121f99cb (diff)
downloadrust-78edefea9d8e4820d7ce7c1d846f89e60fe3b81c.tar.gz
rust-78edefea9d8e4820d7ce7c1d846f89e60fe3b81c.zip
Overhaul the `-l` option parser (for linking to native libs)
-rw-r--r--compiler/rustc_session/src/config.rs11
-rw-r--r--compiler/rustc_session/src/config/native_libs.rs284
-rw-r--r--compiler/rustc_session/src/config/native_libs/tests.rs50
-rw-r--r--tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr2
4 files changed, 225 insertions, 122 deletions
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 61859466a2c..f6e6fd33c48 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -31,7 +31,7 @@ use rustc_target::spec::{
 use tracing::debug;
 
 pub use crate::config::cfg::{Cfg, CheckCfg, ExpectedValues};
-use crate::config::native_libs::parse_libs;
+use crate::config::native_libs::parse_native_libs;
 use crate::errors::FileWriteFail;
 pub use crate::options::*;
 use crate::search_paths::SearchPath;
@@ -2508,7 +2508,10 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
     let debuginfo = select_debuginfo(matches, &cg);
     let debuginfo_compression = unstable_opts.debuginfo_compression;
 
-    let libs = parse_libs(early_dcx, matches);
+    let crate_name = matches.opt_str("crate-name");
+    let unstable_features = UnstableFeatures::from_environment(crate_name.as_deref());
+    // Parse any `-l` flags, which link to native libraries.
+    let libs = parse_native_libs(early_dcx, &unstable_opts, unstable_features, matches);
 
     let test = matches.opt_present("test");
 
@@ -2523,8 +2526,6 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
 
     let externs = parse_externs(early_dcx, matches, &unstable_opts);
 
-    let crate_name = matches.opt_str("crate-name");
-
     let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches, &unstable_opts);
 
     let pretty = parse_pretty(early_dcx, &unstable_opts);
@@ -2598,7 +2599,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M
         error_format,
         diagnostic_width,
         externs,
-        unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()),
+        unstable_features,
         crate_name,
         libs,
         debug_assertions,
diff --git a/compiler/rustc_session/src/config/native_libs.rs b/compiler/rustc_session/src/config/native_libs.rs
index 288756925f2..f1f0aeb5e59 100644
--- a/compiler/rustc_session/src/config/native_libs.rs
+++ b/compiler/rustc_session/src/config/native_libs.rs
@@ -1,140 +1,192 @@
+//! 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 crate::EarlyDiagCtxt;
-use crate::config::nightly_options;
+use crate::config::UnstableOptions;
 use crate::utils::{NativeLib, NativeLibKind};
 
-pub(crate) fn parse_libs(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Vec<NativeLib> {
-    matches
-        .opt_strs("l")
-        .into_iter()
-        .map(|s| {
-            // Parse string of the form "[KIND[:MODIFIERS]=]lib[:new_name]",
-            // where KIND is one of "dylib", "framework", "static", "link-arg" and
-            // where MODIFIERS are a comma separated list of supported modifiers
-            // (bundle, verbatim, whole-archive, as-needed). Each modifier is prefixed
-            // with either + or - to indicate whether it is enabled or disabled.
-            // The last value specified for a given modifier wins.
-            let (name, kind, verbatim) = match s.split_once('=') {
-                None => (s, NativeLibKind::Unspecified, None),
-                Some((kind, name)) => {
-                    let (kind, verbatim) = parse_native_lib_kind(early_dcx, matches, kind);
-                    (name.to_string(), kind, verbatim)
-                }
-            };
-
-            let (name, new_name) = match name.split_once(':') {
-                None => (name, None),
-                Some((name, new_name)) => (name.to_string(), Some(new_name.to_owned())),
-            };
-            if name.is_empty() {
-                early_dcx.early_fatal("library name must not be empty");
-            }
-            NativeLib { name, new_name, kind, verbatim }
-        })
-        .collect()
-}
+#[cfg(test)]
+mod tests;
 
-fn parse_native_lib_kind(
+/// Parses all `-l` options.
+pub(crate) fn parse_native_libs(
     early_dcx: &EarlyDiagCtxt,
+    unstable_opts: &UnstableOptions,
+    unstable_features: UnstableFeatures,
     matches: &getopts::Matches,
-    kind: &str,
-) -> (NativeLibKind, Option<bool>) {
-    let (kind, modifiers) = match kind.split_once(':') {
-        None => (kind, None),
-        Some((kind, modifiers)) => (kind, Some(modifiers)),
+) -> 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}"));
+    }
+}
 
-    let kind = match kind {
+/// 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 },
         "dylib" => NativeLibKind::Dylib { as_needed: None },
         "framework" => NativeLibKind::Framework { as_needed: None },
         "link-arg" => {
-            if !nightly_options::is_unstable_enabled(matches) {
-                let why = if nightly_options::match_is_nightly_build(matches) {
-                    " and only accepted on the nightly compiler"
-                } else {
-                    ", the `-Z unstable-options` flag must also be passed to use it"
-                };
-                early_dcx.early_fatal(format!("library kind `link-arg` is unstable{why}"))
-            }
+            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
         }
-        _ => early_dcx.early_fatal(format!(
+        _ => 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,
     };
-    match modifiers {
-        None => (kind, None),
-        Some(modifiers) => parse_native_lib_modifiers(early_dcx, kind, modifiers, matches),
+
+    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
 }
 
-fn parse_native_lib_modifiers(
-    early_dcx: &EarlyDiagCtxt,
-    mut kind: NativeLibKind,
-    modifiers: &str,
-    matches: &getopts::Matches,
-) -> (NativeLibKind, Option<bool>) {
-    let mut verbatim = None;
-    for modifier in modifiers.split(',') {
-        let (modifier, value) = match modifier.strip_prefix(['+', '-']) {
-            Some(m) => (m, modifier.starts_with('+')),
-            None => early_dcx.early_fatal(
-                "invalid linking modifier syntax, expected '+' or '-' prefix \
-                 before one of: bundle, verbatim, whole-archive, as-needed",
-            ),
-        };
-
-        let report_unstable_modifier = || {
-            if !nightly_options::is_unstable_enabled(matches) {
-                let why = if nightly_options::match_is_nightly_build(matches) {
-                    " and only accepted on the nightly compiler"
-                } else {
-                    ", the `-Z unstable-options` flag must also be passed to use it"
-                };
-                early_dcx.early_fatal(format!("linking modifier `{modifier}` is unstable{why}"))
-            }
-        };
-        let assign_modifier = |dst: &mut Option<bool>| {
-            if dst.is_some() {
-                let msg = format!("multiple `{modifier}` modifiers in a single `-l` option");
-                early_dcx.early_fatal(msg)
-            } else {
-                *dst = Some(value);
-            }
-        };
-        match (modifier, &mut kind) {
-            ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle),
-            ("bundle", _) => early_dcx.early_fatal(
-                "linking modifier `bundle` is only compatible with `static` linking kind",
-            ),
-
-            ("verbatim", _) => assign_modifier(&mut 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 }) => {
-                report_unstable_modifier();
-                assign_modifier(as_needed)
-            }
-            ("as-needed", _) => early_dcx.early_fatal(
-                "linking modifier `as-needed` is only compatible with \
-                 `dylib` and `framework` linking kinds",
-            ),
-
-            // Note: this error also excludes the case with empty modifier
-            // string, like `modifiers = ""`.
-            _ => early_dcx.early_fatal(format!(
-                "unknown linking modifier `{modifier}`, expected one \
-                     of: bundle, verbatim, whole-archive, as-needed"
-            )),
+/// 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",
+        ),
+    };
+
+    // 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"),
+
+        ("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 }) => {
+            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"
+        )),
     }
+}
+
+#[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),
+    };
 
-    (kind, verbatim)
+    NativeLibParts { kind, modifiers, name, new_name }
 }
diff --git a/compiler/rustc_session/src/config/native_libs/tests.rs b/compiler/rustc_session/src/config/native_libs/tests.rs
new file mode 100644
index 00000000000..3bcab93ef4b
--- /dev/null
+++ b/compiler/rustc_session/src/config/native_libs/tests.rs
@@ -0,0 +1,50 @@
+use crate::config::native_libs::{NativeLibParts, split_native_lib_value};
+
+#[test]
+fn split() {
+    // This is a unit test for some implementation details, so consider deleting
+    // it if it gets in the way.
+    use NativeLibParts as P;
+
+    let examples = &[
+        ("", P { kind: None, modifiers: None, name: "", new_name: None }),
+        ("foo", P { kind: None, modifiers: None, name: "foo", new_name: None }),
+        ("foo:", P { kind: None, modifiers: None, name: "foo", new_name: Some("") }),
+        ("foo:bar", P { kind: None, modifiers: None, name: "foo", new_name: Some("bar") }),
+        (":bar", P { kind: None, modifiers: None, name: "", new_name: Some("bar") }),
+        ("kind=foo", P { kind: Some("kind"), modifiers: None, name: "foo", new_name: None }),
+        (":mods=foo", P { kind: Some(""), modifiers: Some("mods"), name: "foo", new_name: None }),
+        (":mods=:bar", P {
+            kind: Some(""),
+            modifiers: Some("mods"),
+            name: "",
+            new_name: Some("bar"),
+        }),
+        ("kind=foo:bar", P {
+            kind: Some("kind"),
+            modifiers: None,
+            name: "foo",
+            new_name: Some("bar"),
+        }),
+        ("kind:mods=foo", P {
+            kind: Some("kind"),
+            modifiers: Some("mods"),
+            name: "foo",
+            new_name: None,
+        }),
+        ("kind:mods=foo:bar", P {
+            kind: Some("kind"),
+            modifiers: Some("mods"),
+            name: "foo",
+            new_name: Some("bar"),
+        }),
+        ("::==::", P { kind: Some(""), modifiers: Some(":"), name: "=", new_name: Some(":") }),
+        ("==::==", P { kind: Some(""), modifiers: None, name: "=", new_name: Some(":==") }),
+    ];
+
+    for &(value, ref expected) in examples {
+        println!("{value:?}");
+        let actual = split_native_lib_value(value);
+        assert_eq!(&actual, expected);
+    }
+}
diff --git a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr
index 1f0b9d04ef6..8f74e9d6f16 100644
--- a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr
+++ b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr
@@ -1,2 +1,2 @@
-error: linking modifier `as-needed` is unstable and only accepted on the nightly compiler
+error: linking modifier `as-needed` is unstable, the `-Z unstable-options` flag must also be passed to use it