571 lines
18 KiB
Rust
571 lines
18 KiB
Rust
//! 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,
|
|
}
|
|
|
|
#[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<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,)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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_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_51(51) => {
|
|
raw_ref_macros: #80886,
|
|
min_const_generics: #74878,
|
|
},
|
|
Stable_1_59(59) => { const_cstr: #54745 },
|
|
Stable_1_47(47) => { larger_arrays: #74060 },
|
|
Stable_1_43(43) => { associated_constants: #68952 },
|
|
Stable_1_40(40) => { non_exhaustive: #44109 },
|
|
Stable_1_36(36) => { maybe_uninit: #60445 },
|
|
Stable_1_33(33) => { repr_packed_n: #57049 },
|
|
}
|
|
|
|
/// 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 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::<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)
|
|
};
|
|
|
|
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.33".parse::<RustTarget>().unwrap().latest_edition(),
|
|
RustEdition::Edition2018
|
|
);
|
|
assert_eq!(
|
|
"1.56".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(),
|
|
);
|
|
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.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");
|
|
}
|
|
}
|