Skip to content

Commit

Permalink
Rollup merge of #132934 - Zalathar:native-libs, r=jieyouxu
Browse files Browse the repository at this point in the history
Overhaul the `-l` option parser (for linking to native libs)

The current parser for `-l` options has accumulated over time, making it hard to follow. This PR tries to clean it up in several ways.

Key changes:
- This code now gets its own submodule, to slightly reduce clutter in `rustc_session::config`.
- Cleaner division between iterating over multiple `-l` options, and processing each individual one.
- Separate “split” step that breaks up the value string into `[KIND[:MODIFIERS]=]NAME[:NEW_NAME]`, but leaves parsing/validating those parts to later steps.
  - This step also gets its own (disposable) unit test, to make sure it works as expected.
- A context struct reduces the burden of parameter passing, and makes it easier to write error messages that adapt to nightly/stable compilers.
- Fewer calls to `nightly_options` helper functions, because at this point we can get the same information from `UnstableOptions` and `UnstableFeatures` (which are downstream of earlier calls to those helper functions).

There should be no overall change in compiler behaviour.
  • Loading branch information
jhpratt authored Nov 18, 2024
2 parents bf6adec + 78edefe commit 21654a2
Show file tree
Hide file tree
Showing 14 changed files with 286 additions and 148 deletions.
153 changes: 9 additions & 144 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -2134,143 +2135,6 @@ fn parse_assert_incr_state(
}
}

fn parse_native_lib_kind(
early_dcx: &EarlyDiagCtxt,
matches: &getopts::Matches,
kind: &str,
) -> (NativeLibKind, Option<bool>) {
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<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"
)),
}
}

(kind, verbatim)
}

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()
}

pub fn parse_externs(
early_dcx: &EarlyDiagCtxt,
matches: &getopts::Matches,
Expand Down Expand Up @@ -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");

Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
192 changes: 192 additions & 0 deletions compiler/rustc_session/src/config/native_libs.rs
Original file line number Diff line number Diff line change
@@ -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<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 },
"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<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),
};

NativeLibParts { kind, modifiers, name, new_name }
}
Loading

0 comments on commit 21654a2

Please sign in to comment.