| //! Contains code for selecting features | |
| #![deny(unused_extern_crates)] | |
| #![deny(clippy::missing_docs_in_private_items)] | |
| #![allow(deprecated)] | |
| use std::str::FromStr; | |
| use std::{fmt, io}; | |
| /// Represents the version of the Rust language to target. | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | |
| #[repr(transparent)] | |
| pub struct RustTarget(Version); | |
| impl RustTarget { | |
| /// Create a new [`RustTarget`] for a stable release of Rust. | |
| pub fn stable(minor: u64, patch: u64) -> Result<Self, InvalidRustTarget> { | |
| let target = Self(Version::Stable(minor, patch)); | |
| if target < EARLIEST_STABLE_RUST { | |
| return Err(InvalidRustTarget::TooEarly); | |
| } | |
| Ok(target) | |
| } | |
| const fn minor(&self) -> Option<u64> { | |
| match self.0 { | |
| Version::Nightly => None, | |
| Version::Stable(minor, _) => Some(minor), | |
| } | |
| } | |
| const fn is_compatible(&self, other: &Self) -> bool { | |
| match (self.0, other.0) { | |
| (Version::Stable(minor, _), Version::Stable(other_minor, _)) => { | |
| // We ignore the patch version number as they only include backwards compatible bug | |
| // fixes. | |
| minor >= other_minor | |
| } | |
| // Nightly is compatible with everything | |
| (Version::Nightly, _) => true, | |
| // No stable release is compatible with nightly | |
| (Version::Stable { .. }, Version::Nightly) => false, | |
| } | |
| } | |
| } | |
| impl Default for RustTarget { | |
| fn default() -> Self { | |
| // Bindgen from build script: default to generating bindings compatible | |
| // with the Rust version currently performing this build. | |
| #[cfg(not(feature = "__cli"))] | |
| { | |
| use std::env; | |
| use std::iter; | |
| use std::process::Command; | |
| use std::sync::OnceLock; | |
| static CURRENT_RUST: OnceLock<Option<RustTarget>> = OnceLock::new(); | |
| if let Some(current_rust) = *CURRENT_RUST.get_or_init(|| { | |
| let is_build_script = | |
| env::var_os("CARGO_CFG_TARGET_ARCH").is_some(); | |
| if !is_build_script { | |
| return None; | |
| } | |
| let rustc = env::var_os("RUSTC")?; | |
| let rustc_wrapper = env::var_os("RUSTC_WRAPPER") | |
| .filter(|wrapper| !wrapper.is_empty()); | |
| let wrapped_rustc = | |
| rustc_wrapper.iter().chain(iter::once(&rustc)); | |
| let mut is_clippy_driver = false; | |
| loop { | |
| let mut wrapped_rustc = wrapped_rustc.clone(); | |
| let mut command = | |
| Command::new(wrapped_rustc.next().unwrap()); | |
| command.args(wrapped_rustc); | |
| if is_clippy_driver { | |
| command.arg("--rustc"); | |
| } | |
| command.arg("--version"); | |
| let output = command.output().ok()?; | |
| let string = String::from_utf8(output.stdout).ok()?; | |
| // Version string like "rustc 1.100.0-beta.5 (f0e1d2c3b 2026-10-17)" | |
| let last_line = string.lines().last().unwrap_or(&string); | |
| let (program, rest) = last_line.trim().split_once(' ')?; | |
| if program != "rustc" { | |
| if program.starts_with("clippy") && !is_clippy_driver { | |
| is_clippy_driver = true; | |
| continue; | |
| } | |
| return None; | |
| } | |
| let number = rest.split([' ', '-', '+']).next()?; | |
| break RustTarget::from_str(number).ok(); | |
| } | |
| }) { | |
| return current_rust; | |
| } | |
| } | |
| // Bindgen from CLI, or cannot determine compiler version: default to | |
| // generating bindings compatible with the latest stable release of Rust | |
| // that Bindgen knows about. | |
| LATEST_STABLE_RUST | |
| } | |
| } | |
| impl fmt::Display for RustTarget { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self.0 { | |
| Version::Stable(minor, patch) => write!(f, "1.{minor}.{patch}"), | |
| Version::Nightly => "nightly".fmt(f), | |
| } | |
| } | |
| } | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | |
| enum Version { | |
| Stable(u64, u64), | |
| Nightly, | |
| } | |
| /// The reason why a given rust target is not supported. | |
| #[derive(Debug, PartialEq, Eq, Hash)] | |
| pub enum InvalidRustTarget { | |
| /// The rust target is not supported by bindgen (is too old). | |
| TooEarly, | |
| } | |
| impl fmt::Display for InvalidRustTarget { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| Self::TooEarly => write!(f, "the earliest Rust version supported by bindgen is {EARLIEST_STABLE_RUST}"), | |
| } | |
| } | |
| } | |
| /// This macro defines the Rust editions supported by bindgen. | |
| macro_rules! define_rust_editions { | |
| ($($variant:ident($value:literal) => $minor:literal,)*) => { | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | |
| #[doc = "Represents Rust Edition for the generated bindings"] | |
| pub enum RustEdition { | |
| $( | |
| #[doc = concat!("The ", stringify!($value), " edition of Rust.")] | |
| $variant, | |
| )* | |
| } | |
| impl FromStr for RustEdition { | |
| type Err = InvalidRustEdition; | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| match s { | |
| $(stringify!($value) => Ok(Self::$variant),)* | |
| _ => Err(InvalidRustEdition(s.to_owned())), | |
| } | |
| } | |
| } | |
| impl fmt::Display for RustEdition { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| $(Self::$variant => stringify!($value).fmt(f),)* | |
| } | |
| } | |
| } | |
| impl RustEdition { | |
| pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*]; | |
| pub(crate) fn is_available(self, target: RustTarget) -> bool { | |
| let Some(minor) = target.minor() else { | |
| return true; | |
| }; | |
| match self { | |
| $(Self::$variant => $minor <= minor,)* | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// This error is returned when a rust edition is unknown. | |
| #[derive(Debug)] | |
| pub struct InvalidRustEdition(String); | |
| impl fmt::Display for InvalidRustEdition { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "\"{}\" is not a valid Rust edition", self.0) | |
| } | |
| } | |
| impl std::error::Error for InvalidRustEdition {} | |
| define_rust_editions! { | |
| Edition2018(2018) => 31, | |
| Edition2021(2021) => 56, | |
| Edition2024(2024) => 85, | |
| } | |
| impl RustTarget { | |
| /// Returns the latest edition supported by this target. | |
| pub(crate) fn latest_edition(self) -> RustEdition { | |
| RustEdition::ALL | |
| .iter() | |
| .rev() | |
| .find(|edition| edition.is_available(self)) | |
| .copied() | |
| .expect("bindgen should always support at least one edition") | |
| } | |
| } | |
| impl Default for RustEdition { | |
| fn default() -> Self { | |
| RustTarget::default().latest_edition() | |
| } | |
| } | |
| /// This macro defines the [`RustTarget`] and [`RustFeatures`] types. | |
| macro_rules! define_rust_targets { | |
| ( | |
| Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))|* $(: #$issue:literal)?),* $(,)?} $(,)? | |
| $( | |
| $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))|* $(: #$pull:literal)?),* $(,)?}, | |
| )* | |
| $(,)? | |
| ) => { | |
| impl RustTarget { | |
| /// The nightly version of Rust, which introduces the following features:" | |
| $(#[doc = concat!( | |
| "- [`", stringify!($nightly_feature), "`]", | |
| "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")", | |
| )])* | |
| #[deprecated = "The use of this constant is deprecated, please use `RustTarget::nightly` instead."] | |
| pub const Nightly: Self = Self::nightly(); | |
| /// The nightly version of Rust, which introduces the following features:" | |
| $(#[doc = concat!( | |
| "- [`", stringify!($nightly_feature), "`]", | |
| "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")", | |
| )])* | |
| pub const fn nightly() -> Self { | |
| Self(Version::Nightly) | |
| } | |
| $( | |
| #[doc = concat!("Version 1.", stringify!($minor), " of Rust, which introduced the following features:")] | |
| $(#[doc = concat!( | |
| "- [`", stringify!($feature), "`]", | |
| "(", $("https://github.com/rust-lang/rust/pull/", stringify!($pull),)* ")", | |
| )])* | |
| #[deprecated = "The use of this constant is deprecated, please use `RustTarget::stable` instead."] | |
| pub const $variant: Self = Self(Version::Stable($minor, 0)); | |
| )* | |
| const fn stable_releases() -> [(Self, u64); [$($minor,)*].len()] { | |
| [$((Self::$variant, $minor),)*] | |
| } | |
| } | |
| #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] | |
| pub(crate) struct RustFeatures { | |
| $($(pub(crate) $feature: bool,)*)* | |
| $(pub(crate) $nightly_feature: bool,)* | |
| } | |
| impl RustFeatures { | |
| /// Compute the features that must be enabled in a specific Rust target with a specific edition. | |
| pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self { | |
| let mut features = Self { | |
| $($($feature: false,)*)* | |
| $($nightly_feature: false,)* | |
| }; | |
| if target.is_compatible(&RustTarget::nightly()) { | |
| $( | |
| let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::<RustEdition>().ok().expect("invalid edition"),)*]; | |
| if editions.is_empty() || editions.contains(&edition) { | |
| features.$nightly_feature = true; | |
| } | |
| )* | |
| } | |
| $( | |
| if target.is_compatible(&RustTarget::$variant) { | |
| $( | |
| let editions: &[RustEdition] = &[$(stringify!($edition).parse::<RustEdition>().ok().expect("invalid edition"),)*]; | |
| if editions.is_empty() || editions.contains(&edition) { | |
| features.$feature = true; | |
| } | |
| )* | |
| } | |
| )* | |
| features | |
| } | |
| } | |
| }; | |
| } | |
| // NOTE: When adding or removing features here, make sure to add the stabilization PR | |
| // number for the feature if it has been stabilized or the tracking issue number if the feature is | |
| // not stable. | |
| define_rust_targets! { | |
| Nightly => { | |
| vectorcall_abi: #124485, | |
| ptr_metadata: #81513, | |
| layout_for_ptr: #69835, | |
| }, | |
| Stable_1_83(83) => { | |
| const_mut_refs: #129195, | |
| }, | |
| Stable_1_82(82) => { | |
| unsafe_extern_blocks: #127921, | |
| }, | |
| Stable_1_77(77) => { | |
| offset_of: #106655, | |
| literal_cstr(2021)|(2024): #117472, | |
| }, | |
| Stable_1_73(73) => { thiscall_abi: #42202 }, | |
| Stable_1_71(71) => { c_unwind_abi: #106075 }, | |
| Stable_1_68(68) => { abi_efiapi: #105795 }, | |
| Stable_1_64(64) => { core_ffi_c: #94503 }, | |
| Stable_1_59(59) => { const_cstr: #54745 }, | |
| Stable_1_51(51) => {}, | |
| } | |
| /// Latest stable release of Rust that is supported by bindgen | |
| pub const LATEST_STABLE_RUST: RustTarget = { | |
| // FIXME: replace all this code by | |
| // ``` | |
| // RustTarget::stable_releases() | |
| // .into_iter() | |
| // .max_by_key(|(_, m)| m) | |
| // .map(|(t, _)| t) | |
| // .unwrap() | |
| // ``` | |
| // once those operations can be used in constants. | |
| let targets = RustTarget::stable_releases(); | |
| let mut i = 0; | |
| let mut latest_target = None; | |
| let mut latest_minor = 0; | |
| while i < targets.len() { | |
| let (target, minor) = targets[i]; | |
| if latest_minor < minor { | |
| latest_minor = minor; | |
| latest_target = Some(target); | |
| } | |
| i += 1; | |
| } | |
| match latest_target { | |
| Some(target) => target, | |
| None => unreachable!(), | |
| } | |
| }; | |
| /// Earliest stable release of Rust that is supported by bindgen | |
| pub const EARLIEST_STABLE_RUST: RustTarget = { | |
| // FIXME: replace all this code by | |
| // ``` | |
| // RustTarget::stable_releases() | |
| // .into_iter() | |
| // .min_by_key(|(_, m)| m) | |
| // .map(|(t, _)| t) | |
| // .unwrap_or(LATEST_STABLE_RUST) | |
| // ``` | |
| // once those operations can be used in constants. | |
| let targets = RustTarget::stable_releases(); | |
| let mut i = 0; | |
| let mut earliest_target = None; | |
| let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else { | |
| unreachable!() | |
| }; | |
| while i < targets.len() { | |
| let (target, minor) = targets[i]; | |
| if earliest_minor > minor { | |
| earliest_minor = minor; | |
| earliest_target = Some(target); | |
| } | |
| i += 1; | |
| } | |
| match earliest_target { | |
| Some(target) => target, | |
| None => unreachable!(), | |
| } | |
| }; | |
| fn invalid_input(input: &str, msg: impl fmt::Display) -> io::Error { | |
| io::Error::new( | |
| io::ErrorKind::InvalidInput, | |
| format!("\"{input}\" is not a valid Rust target, {msg}"), | |
| ) | |
| } | |
| impl FromStr for RustTarget { | |
| type Err = io::Error; | |
| fn from_str(input: &str) -> Result<Self, Self::Err> { | |
| if input == "nightly" { | |
| return Ok(Self::Nightly); | |
| } | |
| let (version, pre_release) = | |
| if let Some((version, pre_release)) = input.split_once('-') { | |
| (version, pre_release) | |
| } else { | |
| (input, "") | |
| }; | |
| const MSG: &str = r#"accepted values are of the form "1.71", "1.71.1" , "1.71.1-beta", "1.71.1-beta.1", "1.71.1-nightly" or "nightly"."#; | |
| if !(pre_release.is_empty() /* stable */ | |
| || pre_release == "beta" | |
| || pre_release.starts_with("beta.") | |
| || pre_release == "nightly") | |
| { | |
| return Err(invalid_input(input, MSG)); | |
| } | |
| let Some((major_str, tail)) = version.split_once('.') else { | |
| return Err(invalid_input(input, MSG)); | |
| }; | |
| if major_str != "1" { | |
| return Err(invalid_input( | |
| input, | |
| "The largest major version of Rust released is \"1\"", | |
| )); | |
| } | |
| let (mut minor, mut patch) = if let Some((minor_str, patch_str)) = | |
| tail.split_once('.') | |
| { | |
| let Ok(minor) = minor_str.parse::<u64>() else { | |
| return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer")); | |
| }; | |
| let Ok(patch) = patch_str.parse::<u64>() else { | |
| return Err(invalid_input(input, "the patch version number must be an unsigned 64-bit integer")); | |
| }; | |
| (minor, patch) | |
| } else { | |
| let Ok(minor) = tail.parse::<u64>() else { | |
| return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer")); | |
| }; | |
| (minor, 0) | |
| }; | |
| if pre_release == "nightly" { | |
| // treat nightly as incomplete and therefore as the previous maximaly patched stable | |
| minor -= 1; | |
| patch = u64::MAX; | |
| } | |
| Self::stable(minor, patch).map_err(|err| invalid_input(input, err)) | |
| } | |
| } | |
| impl RustFeatures { | |
| /// Compute the features that must be enabled in a specific Rust target with the latest edition | |
| /// available in that target. | |
| pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self { | |
| Self::new(target, target.latest_edition()) | |
| } | |
| } | |
| impl Default for RustFeatures { | |
| fn default() -> Self { | |
| Self::new_with_latest_edition(RustTarget::default()) | |
| } | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use super::*; | |
| #[test] | |
| fn release_versions_for_editions() { | |
| assert_eq!( | |
| "1.51".parse::<RustTarget>().unwrap().latest_edition(), | |
| RustEdition::Edition2018 | |
| ); | |
| assert_eq!( | |
| "1.59".parse::<RustTarget>().unwrap().latest_edition(), | |
| RustEdition::Edition2021 | |
| ); | |
| assert_eq!( | |
| "1.85".parse::<RustTarget>().unwrap().latest_edition(), | |
| RustEdition::Edition2024 | |
| ); | |
| assert_eq!( | |
| "nightly".parse::<RustTarget>().unwrap().latest_edition(), | |
| RustEdition::Edition2024 | |
| ); | |
| } | |
| #[test] | |
| fn target_features() { | |
| let features = | |
| RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71); | |
| assert!( | |
| features.c_unwind_abi && | |
| features.abi_efiapi && | |
| !features.thiscall_abi | |
| ); | |
| let features = RustFeatures::new( | |
| RustTarget::Stable_1_77, | |
| RustEdition::Edition2018, | |
| ); | |
| assert!(!features.literal_cstr); | |
| let features = | |
| RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77); | |
| assert!(features.literal_cstr); | |
| let f_nightly = | |
| RustFeatures::new_with_latest_edition(RustTarget::Nightly); | |
| assert!( | |
| f_nightly.vectorcall_abi && | |
| f_nightly.ptr_metadata && | |
| f_nightly.layout_for_ptr | |
| ); | |
| } | |
| fn test_target(input: &str, expected: RustTarget) { | |
| // Two targets are equivalent if they enable the same set of features | |
| let expected = RustFeatures::new_with_latest_edition(expected); | |
| let found = RustFeatures::new_with_latest_edition( | |
| input.parse::<RustTarget>().unwrap_or_else(|_| { | |
| panic!("{input} should parse as a valid target") | |
| }), | |
| ); | |
| assert_eq!( | |
| expected, | |
| found, | |
| "target {input} enables features:\n{found:#?}\nand should enable features:\n{expected:#?}" | |
| ); | |
| } | |
| fn test_invalid_target(input: &str) { | |
| assert!( | |
| input.parse::<RustTarget>().is_err(), | |
| "{input} should be an invalid target" | |
| ); | |
| } | |
| #[test] | |
| fn valid_targets() { | |
| test_target("1.71", RustTarget::Stable_1_71); | |
| test_target("1.71.0", RustTarget::Stable_1_71); | |
| test_target("1.71.1", RustTarget::Stable_1_71); | |
| test_target("1.72", RustTarget::Stable_1_71); | |
| test_target("1.73", RustTarget::Stable_1_73); | |
| test_target("1.82.0-beta", RustTarget::Stable_1_82); | |
| test_target("1.82.0-beta.1", RustTarget::Stable_1_82); | |
| test_target("1.83.1-nightly", RustTarget::Stable_1_82); | |
| test_target("1.18446744073709551615", LATEST_STABLE_RUST); | |
| test_target("nightly", RustTarget::Nightly); | |
| } | |
| #[test] | |
| fn invalid_targets() { | |
| test_invalid_target("2.0"); | |
| test_invalid_target("1.cat"); | |
| test_invalid_target("1.0.cat"); | |
| test_invalid_target("1.18446744073709551616"); | |
| test_invalid_target("1.0.18446744073709551616"); | |
| test_invalid_target("1.-1.0"); | |
| test_invalid_target("1.0.-1"); | |
| test_invalid_target("beta"); | |
| test_invalid_target("1.0.0"); | |
| test_invalid_target("1.32.0"); | |
| } | |
| } |