//! 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 { let target = Self(Version::Stable(minor, patch)); if target < EARLIEST_STABLE_RUST { return Err(InvalidRustTarget::TooEarly); } Ok(target) } const fn minor(&self) -> Option { 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> = 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, } #[derive(Debug, PartialEq, Eq, Hash)] pub enum InvalidRustTarget { 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 { 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,)* } } } } } #[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::().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::().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_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 { if input == "nightly" { return Ok(Self::Nightly); } let Some((major_str, tail)) = input.split_once('.') else { return Err(invalid_input(input, "accepted values are of the form \"1.71\", \"1.71.1\" or \"nightly\"." ) ); }; if major_str != "1" { return Err(invalid_input( input, "The largest major version of Rust released is \"1\"", )); } let (minor, patch) = if let Some((minor_str, patch_str)) = tail.split_once('.') { let Ok(minor) = minor_str.parse::() else { return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer")); }; let Ok(patch) = patch_str.parse::() 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::() else { return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer")); }; (minor, 0) }; 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::().unwrap().latest_edition(), RustEdition::Edition2018 ); assert_eq!( "1.59".parse::().unwrap().latest_edition(), RustEdition::Edition2021 ); assert_eq!( "1.85".parse::().unwrap().latest_edition(), RustEdition::Edition2024 ); assert_eq!( "nightly".parse::().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::().unwrap(), ); 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::().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.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"); } }