diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 44721bd889a0c..f6e6fd33c48ea 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -30,17 +30,18 @@ use rustc_target::spec::{ }; use tracing::debug; +pub use crate::config::cfg::{Cfg, CheckCfg, ExpectedValues}; +use crate::config::native_libs::parse_native_libs; use crate::errors::FileWriteFail; pub use crate::options::*; use crate::search_paths::SearchPath; -use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; +use crate::utils::CanonicalizedPath; use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint}; mod cfg; +mod native_libs; pub mod sigpipe; -pub use cfg::{Cfg, CheckCfg, ExpectedValues}; - /// The different settings that the `-C strip` flag can have. #[derive(Clone, Copy, PartialEq, Hash, Debug)] pub enum Strip { @@ -2134,143 +2135,6 @@ fn parse_assert_incr_state( } } -fn parse_native_lib_kind( - early_dcx: &EarlyDiagCtxt, - matches: &getopts::Matches, - kind: &str, -) -> (NativeLibKind, Option) { - let (kind, modifiers) = match kind.split_once(':') { - None => (kind, None), - Some((kind, modifiers)) => (kind, Some(modifiers)), - }; - - let 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}")) - } - NativeLibKind::LinkArg - } - _ => early_dcx.early_fatal(format!( - "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" - )), - }; - match modifiers { - None => (kind, None), - Some(modifiers) => parse_native_lib_modifiers(early_dcx, kind, modifiers, matches), - } -} - -fn parse_native_lib_modifiers( - early_dcx: &EarlyDiagCtxt, - mut kind: NativeLibKind, - modifiers: &str, - matches: &getopts::Matches, -) -> (NativeLibKind, Option) { - 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| { - 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" - )), - } - } - - (kind, verbatim) -} - -fn parse_libs(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Vec { - 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() -} - pub fn parse_externs( early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches, @@ -2644,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"); @@ -2659,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); @@ -2734,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 new file mode 100644 index 0000000000000..f1f0aeb5e599b --- /dev/null +++ b/compiler/rustc_session/src/config/native_libs.rs @@ -0,0 +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::UnstableOptions; +use crate::utils::{NativeLib, NativeLibKind}; + +#[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 { + 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 }, + "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", + ), + }; + + // Assigns the value (from `+` or `-`) to an empty `Option`, or emits + // a fatal error if the option has already been set. + let assign_modifier = |opt_bool: &mut Option| { + 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), + }; + + 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 0000000000000..3bcab93ef4b8f --- /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-link-arg-attribute.stderr b/tests/ui/feature-gates/feature-gate-link-arg-attribute.in_attr.stderr similarity index 91% rename from tests/ui/feature-gates/feature-gate-link-arg-attribute.stderr rename to tests/ui/feature-gates/feature-gate-link-arg-attribute.in_attr.stderr index 8cbad78478e18..3ea8c89bf3ae4 100644 --- a/tests/ui/feature-gates/feature-gate-link-arg-attribute.stderr +++ b/tests/ui/feature-gates/feature-gate-link-arg-attribute.in_attr.stderr @@ -1,5 +1,5 @@ error[E0658]: link kind `link-arg` is unstable - --> $DIR/feature-gate-link-arg-attribute.rs:1:15 + --> $DIR/feature-gate-link-arg-attribute.rs:5:15 | LL | #[link(kind = "link-arg", name = "foo")] | ^^^^^^^^^^ diff --git a/tests/ui/feature-gates/feature-gate-link-arg-attribute.in_flag.stderr b/tests/ui/feature-gates/feature-gate-link-arg-attribute.in_flag.stderr new file mode 100644 index 0000000000000..4d65db3c66d0b --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-link-arg-attribute.in_flag.stderr @@ -0,0 +1,2 @@ +error: unknown linking modifier `link-arg`, expected one of: bundle, verbatim, whole-archive, as-needed + diff --git a/tests/ui/feature-gates/feature-gate-link-arg-attribute.rs b/tests/ui/feature-gates/feature-gate-link-arg-attribute.rs index 9036095fbc495..c12ff5b04dc30 100644 --- a/tests/ui/feature-gates/feature-gate-link-arg-attribute.rs +++ b/tests/ui/feature-gates/feature-gate-link-arg-attribute.rs @@ -1,5 +1,9 @@ +//@ revisions: in_attr in_flag +//@[in_flag] compile-flags: -l dylib:+link-arg=foo + +#[cfg(in_attr)] #[link(kind = "link-arg", name = "foo")] -//~^ ERROR link kind `link-arg` is unstable +//[in_attr]~^ ERROR link kind `link-arg` is unstable extern "C" {} fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.stderr b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_attr.stderr similarity index 90% rename from tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.stderr rename to tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_attr.stderr index 216477828e739..11b7abc883e15 100644 --- a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.stderr +++ b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_attr.stderr @@ -1,5 +1,5 @@ error[E0658]: linking modifier `as-needed` is unstable - --> $DIR/feature-gate-native_link_modifiers_as_needed.rs:1:50 + --> $DIR/feature-gate-native_link_modifiers_as_needed.rs:5:50 | LL | #[link(name = "foo", kind = "dylib", modifiers = "+as-needed")] | ^^^^^^^^^^^^ 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 new file mode 100644 index 0000000000000..8f74e9d6f169c --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.in_flag.stderr @@ -0,0 +1,2 @@ +error: linking modifier `as-needed` is unstable, the `-Z unstable-options` flag must also be passed to use it + diff --git a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.rs b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.rs index 132bc6ab04af2..c2965e42f276d 100644 --- a/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.rs +++ b/tests/ui/feature-gates/feature-gate-native_link_modifiers_as_needed.rs @@ -1,5 +1,9 @@ +//@ revisions: in_attr in_flag +//@[in_flag] compile-flags: -l dylib:+as-needed=foo + +#[cfg(in_attr)] #[link(name = "foo", kind = "dylib", modifiers = "+as-needed")] -//~^ ERROR: linking modifier `as-needed` is unstable +//[in_attr]~^ ERROR: linking modifier `as-needed` is unstable extern "C" {} fn main() {} diff --git a/tests/ui/native-library-link-flags/modifiers-bad.blank.stderr b/tests/ui/native-library-link-flags/modifiers-bad.blank.stderr new file mode 100644 index 0000000000000..ea36af0b4cfa9 --- /dev/null +++ b/tests/ui/native-library-link-flags/modifiers-bad.blank.stderr @@ -0,0 +1,2 @@ +error: invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed + diff --git a/tests/ui/native-library-link-flags/modifiers-bad.no-prefix.stderr b/tests/ui/native-library-link-flags/modifiers-bad.no-prefix.stderr new file mode 100644 index 0000000000000..ea36af0b4cfa9 --- /dev/null +++ b/tests/ui/native-library-link-flags/modifiers-bad.no-prefix.stderr @@ -0,0 +1,2 @@ +error: invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed + diff --git a/tests/ui/native-library-link-flags/modifiers-bad.prefix-only.stderr b/tests/ui/native-library-link-flags/modifiers-bad.prefix-only.stderr new file mode 100644 index 0000000000000..1e701374688fe --- /dev/null +++ b/tests/ui/native-library-link-flags/modifiers-bad.prefix-only.stderr @@ -0,0 +1,2 @@ +error: unknown linking modifier ``, expected one of: bundle, verbatim, whole-archive, as-needed + diff --git a/tests/ui/native-library-link-flags/modifiers-bad.rs b/tests/ui/native-library-link-flags/modifiers-bad.rs new file mode 100644 index 0000000000000..185201e0d8429 --- /dev/null +++ b/tests/ui/native-library-link-flags/modifiers-bad.rs @@ -0,0 +1,11 @@ +//@ edition: 2021 +//@ revisions: blank no-prefix prefix-only unknown + +//@[blank] compile-flags: -l static:=foo +//@[no-prefix] compile-flags: -l static:bundle=foo +//@[prefix-only] compile-flags: -l static:+=foo +//@[unknown] compile-flags: -l static:+ferris=foo + +// Tests various illegal values for the "modifier" part of an `-l` flag. + +fn main() {} diff --git a/tests/ui/native-library-link-flags/modifiers-bad.unknown.stderr b/tests/ui/native-library-link-flags/modifiers-bad.unknown.stderr new file mode 100644 index 0000000000000..75950ad9c64c7 --- /dev/null +++ b/tests/ui/native-library-link-flags/modifiers-bad.unknown.stderr @@ -0,0 +1,2 @@ +error: unknown linking modifier `ferris`, expected one of: bundle, verbatim, whole-archive, as-needed +