1#![deny(unused_extern_crates)]
4#![deny(clippy::missing_docs_in_private_items)]
5#![allow(deprecated)]
6
7use std::str::FromStr;
8use std::{fmt, io};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12#[repr(transparent)]
13pub struct RustTarget(Version);
14
15impl RustTarget {
16 pub fn stable(minor: u64, patch: u64) -> Result<Self, InvalidRustTarget> {
18 let target = Self(Version::Stable(minor, patch));
19
20 if target < EARLIEST_STABLE_RUST {
21 return Err(InvalidRustTarget::TooEarly);
22 }
23
24 Ok(target)
25 }
26
27 const fn minor(&self) -> Option<u64> {
28 match self.0 {
29 Version::Nightly => None,
30 Version::Stable(minor, _) => Some(minor),
31 }
32 }
33
34 const fn is_compatible(&self, other: &Self) -> bool {
35 match (self.0, other.0) {
36 (Version::Stable(minor, _), Version::Stable(other_minor, _)) => {
37 minor >= other_minor
40 }
41 (Version::Nightly, _) => true,
43 (Version::Stable { .. }, Version::Nightly) => false,
45 }
46 }
47}
48
49impl Default for RustTarget {
50 fn default() -> Self {
51 #[cfg(not(feature = "__cli"))]
54 {
55 use std::env;
56 use std::iter;
57 use std::process::Command;
58 use std::sync::OnceLock;
59
60 static CURRENT_RUST: OnceLock<Option<RustTarget>> = OnceLock::new();
61
62 if let Some(current_rust) = *CURRENT_RUST.get_or_init(|| {
63 let is_build_script =
64 env::var_os("CARGO_CFG_TARGET_ARCH").is_some();
65 if !is_build_script {
66 return None;
67 }
68
69 let rustc = env::var_os("RUSTC")?;
70 let rustc_wrapper = env::var_os("RUSTC_WRAPPER")
71 .filter(|wrapper| !wrapper.is_empty());
72 let wrapped_rustc =
73 rustc_wrapper.iter().chain(iter::once(&rustc));
74
75 let mut is_clippy_driver = false;
76 loop {
77 let mut wrapped_rustc = wrapped_rustc.clone();
78 let mut command =
79 Command::new(wrapped_rustc.next().unwrap());
80 command.args(wrapped_rustc);
81 if is_clippy_driver {
82 command.arg("--rustc");
83 }
84 command.arg("--version");
85
86 let output = command.output().ok()?;
87 let string = String::from_utf8(output.stdout).ok()?;
88
89 let last_line = string.lines().last().unwrap_or(&string);
91 let (program, rest) = last_line.trim().split_once(' ')?;
92 if program != "rustc" {
93 if program.starts_with("clippy") && !is_clippy_driver {
94 is_clippy_driver = true;
95 continue;
96 }
97 return None;
98 }
99
100 let number = rest.split([' ', '-', '+']).next()?;
101 break RustTarget::from_str(number).ok();
102 }
103 }) {
104 return current_rust;
105 }
106 }
107
108 LATEST_STABLE_RUST
112 }
113}
114
115impl fmt::Display for RustTarget {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self.0 {
118 Version::Stable(minor, patch) => write!(f, "1.{minor}.{patch}"),
119 Version::Nightly => "nightly".fmt(f),
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
125enum Version {
126 Stable(u64, u64),
127 Nightly,
128}
129
130#[derive(Debug, PartialEq, Eq, Hash)]
131pub enum InvalidRustTarget {
132 TooEarly,
133}
134
135impl fmt::Display for InvalidRustTarget {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 Self::TooEarly => write!(f, "the earliest Rust version supported by bindgen is {EARLIEST_STABLE_RUST}"),
139 }
140 }
141}
142
143macro_rules! define_rust_editions {
145 ($($variant:ident($value:literal) => $minor:literal,)*) => {
146 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
147 #[doc = "Represents Rust Edition for the generated bindings"]
148 pub enum RustEdition {
149 $(
150 #[doc = concat!("The ", stringify!($value), " edition of Rust.")]
151 $variant,
152 )*
153 }
154
155 impl FromStr for RustEdition {
156 type Err = InvalidRustEdition;
157
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 match s {
160 $(stringify!($value) => Ok(Self::$variant),)*
161 _ => Err(InvalidRustEdition(s.to_owned())),
162 }
163 }
164 }
165
166 impl fmt::Display for RustEdition {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 $(Self::$variant => stringify!($value).fmt(f),)*
170 }
171 }
172 }
173
174 impl RustEdition {
175 pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*];
176
177 pub(crate) fn is_available(self, target: RustTarget) -> bool {
178 let Some(minor) = target.minor() else {
179 return true;
180 };
181
182 match self {
183 $(Self::$variant => $minor <= minor,)*
184 }
185 }
186 }
187 }
188}
189
190#[derive(Debug)]
191pub struct InvalidRustEdition(String);
192
193impl fmt::Display for InvalidRustEdition {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "\"{}\" is not a valid Rust edition", self.0)
196 }
197}
198
199impl std::error::Error for InvalidRustEdition {}
200
201define_rust_editions! {
202 Edition2018(2018) => 31,
203 Edition2021(2021) => 56,
204 Edition2024(2024) => 85,
205}
206
207impl RustTarget {
208 pub(crate) fn latest_edition(self) -> RustEdition {
210 RustEdition::ALL
211 .iter()
212 .rev()
213 .find(|edition| edition.is_available(self))
214 .copied()
215 .expect("bindgen should always support at least one edition")
216 }
217}
218
219impl Default for RustEdition {
220 fn default() -> Self {
221 RustTarget::default().latest_edition()
222 }
223}
224
225macro_rules! define_rust_targets {
227 (
228 Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))|* $(: #$issue:literal)?),* $(,)?} $(,)?
229 $(
230 $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))|* $(: #$pull:literal)?),* $(,)?},
231 )*
232 $(,)?
233 ) => {
234
235 impl RustTarget {
236 $(#[doc = concat!(
238 "- [`", stringify!($nightly_feature), "`]",
239 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
240 )])*
241 #[deprecated = "The use of this constant is deprecated, please use `RustTarget::nightly` instead."]
242 pub const Nightly: Self = Self::nightly();
243
244 $(#[doc = concat!(
246 "- [`", stringify!($nightly_feature), "`]",
247 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
248 )])*
249 pub const fn nightly() -> Self {
250 Self(Version::Nightly)
251 }
252
253 $(
254 #[doc = concat!("Version 1.", stringify!($minor), " of Rust, which introduced the following features:")]
255 $(#[doc = concat!(
256 "- [`", stringify!($feature), "`]",
257 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($pull),)* ")",
258 )])*
259 #[deprecated = "The use of this constant is deprecated, please use `RustTarget::stable` instead."]
260 pub const $variant: Self = Self(Version::Stable($minor, 0));
261 )*
262
263 const fn stable_releases() -> [(Self, u64); [$($minor,)*].len()] {
264 [$((Self::$variant, $minor),)*]
265 }
266 }
267
268 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
269 pub(crate) struct RustFeatures {
270 $($(pub(crate) $feature: bool,)*)*
271 $(pub(crate) $nightly_feature: bool,)*
272 }
273
274 impl RustFeatures {
275 pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self {
277 let mut features = Self {
278 $($($feature: false,)*)*
279 $($nightly_feature: false,)*
280 };
281
282 if target.is_compatible(&RustTarget::nightly()) {
283 $(
284 let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
285
286 if editions.is_empty() || editions.contains(&edition) {
287 features.$nightly_feature = true;
288 }
289 )*
290 }
291
292 $(
293 if target.is_compatible(&RustTarget::$variant) {
294 $(
295 let editions: &[RustEdition] = &[$(stringify!($edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
296
297 if editions.is_empty() || editions.contains(&edition) {
298 features.$feature = true;
299 }
300 )*
301 }
302 )*
303
304 features
305 }
306 }
307 };
308}
309
310define_rust_targets! {
314 Nightly => {
315 vectorcall_abi: #124485,
316 ptr_metadata: #81513,
317 layout_for_ptr: #69835,
318 },
319 Stable_1_82(82) => {
320 unsafe_extern_blocks: #127921,
321 },
322 Stable_1_77(77) => {
323 offset_of: #106655,
324 literal_cstr(2021)|(2024): #117472,
325 },
326 Stable_1_73(73) => { thiscall_abi: #42202 },
327 Stable_1_71(71) => { c_unwind_abi: #106075 },
328 Stable_1_68(68) => { abi_efiapi: #105795 },
329 Stable_1_64(64) => { core_ffi_c: #94503 },
330 Stable_1_59(59) => { const_cstr: #54745 },
331 Stable_1_51(51) => {},
332}
333
334pub const LATEST_STABLE_RUST: RustTarget = {
336 let targets = RustTarget::stable_releases();
346
347 let mut i = 0;
348 let mut latest_target = None;
349 let mut latest_minor = 0;
350
351 while i < targets.len() {
352 let (target, minor) = targets[i];
353
354 if latest_minor < minor {
355 latest_minor = minor;
356 latest_target = Some(target);
357 }
358
359 i += 1;
360 }
361
362 match latest_target {
363 Some(target) => target,
364 None => unreachable!(),
365 }
366};
367
368pub const EARLIEST_STABLE_RUST: RustTarget = {
370 let targets = RustTarget::stable_releases();
380
381 let mut i = 0;
382 let mut earliest_target = None;
383 let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else {
384 unreachable!()
385 };
386
387 while i < targets.len() {
388 let (target, minor) = targets[i];
389
390 if earliest_minor > minor {
391 earliest_minor = minor;
392 earliest_target = Some(target);
393 }
394
395 i += 1;
396 }
397
398 match earliest_target {
399 Some(target) => target,
400 None => unreachable!(),
401 }
402};
403
404fn invalid_input(input: &str, msg: impl fmt::Display) -> io::Error {
405 io::Error::new(
406 io::ErrorKind::InvalidInput,
407 format!("\"{input}\" is not a valid Rust target, {msg}"),
408 )
409}
410
411impl FromStr for RustTarget {
412 type Err = io::Error;
413
414 fn from_str(input: &str) -> Result<Self, Self::Err> {
415 if input == "nightly" {
416 return Ok(Self::Nightly);
417 }
418
419 let (version, pre_release) =
420 if let Some((version, pre_release)) = input.split_once('-') {
421 (version, pre_release)
422 } else {
423 (input, "")
424 };
425
426 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"."#;
427
428 if !(pre_release.is_empty() || pre_release == "beta"
430 || pre_release.starts_with("beta.")
431 || pre_release == "nightly")
432 {
433 return Err(invalid_input(input, MSG));
434 }
435
436 let Some((major_str, tail)) = version.split_once('.') else {
437 return Err(invalid_input(input, MSG));
438 };
439
440 if major_str != "1" {
441 return Err(invalid_input(
442 input,
443 "The largest major version of Rust released is \"1\"",
444 ));
445 }
446
447 let (mut minor, mut patch) = if let Some((minor_str, patch_str)) =
448 tail.split_once('.')
449 {
450 let Ok(minor) = minor_str.parse::<u64>() else {
451 return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
452 };
453 let Ok(patch) = patch_str.parse::<u64>() else {
454 return Err(invalid_input(input, "the patch version number must be an unsigned 64-bit integer"));
455 };
456 (minor, patch)
457 } else {
458 let Ok(minor) = tail.parse::<u64>() else {
459 return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
460 };
461 (minor, 0)
462 };
463
464 if pre_release == "nightly" {
465 minor -= 1;
467 patch = u64::MAX;
468 }
469
470 Self::stable(minor, patch).map_err(|err| invalid_input(input, err))
471 }
472}
473
474impl RustFeatures {
475 pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self {
478 Self::new(target, target.latest_edition())
479 }
480}
481
482impl Default for RustFeatures {
483 fn default() -> Self {
484 Self::new_with_latest_edition(RustTarget::default())
485 }
486}
487
488#[cfg(test)]
489mod test {
490 use super::*;
491
492 #[test]
493 fn release_versions_for_editions() {
494 assert_eq!(
495 "1.51".parse::<RustTarget>().unwrap().latest_edition(),
496 RustEdition::Edition2018
497 );
498 assert_eq!(
499 "1.59".parse::<RustTarget>().unwrap().latest_edition(),
500 RustEdition::Edition2021
501 );
502 assert_eq!(
503 "1.85".parse::<RustTarget>().unwrap().latest_edition(),
504 RustEdition::Edition2024
505 );
506 assert_eq!(
507 "nightly".parse::<RustTarget>().unwrap().latest_edition(),
508 RustEdition::Edition2024
509 );
510 }
511
512 #[test]
513 fn target_features() {
514 let features =
515 RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71);
516 assert!(
517 features.c_unwind_abi &&
518 features.abi_efiapi &&
519 !features.thiscall_abi
520 );
521
522 let features = RustFeatures::new(
523 RustTarget::Stable_1_77,
524 RustEdition::Edition2018,
525 );
526 assert!(!features.literal_cstr);
527
528 let features =
529 RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77);
530 assert!(features.literal_cstr);
531
532 let f_nightly =
533 RustFeatures::new_with_latest_edition(RustTarget::Nightly);
534 assert!(
535 f_nightly.vectorcall_abi &&
536 f_nightly.ptr_metadata &&
537 f_nightly.layout_for_ptr
538 );
539 }
540
541 fn test_target(input: &str, expected: RustTarget) {
542 let expected = RustFeatures::new_with_latest_edition(expected);
544 let found = RustFeatures::new_with_latest_edition(
545 input.parse::<RustTarget>().unwrap_or_else(|_| {
546 panic!("{input} should parse as a valid target")
547 }),
548 );
549 assert_eq!(
550 expected,
551 found,
552 "target {input} enables features:\n{found:#?}\nand should enable features:\n{expected:#?}"
553 );
554 }
555
556 fn test_invalid_target(input: &str) {
557 assert!(
558 input.parse::<RustTarget>().is_err(),
559 "{input} should be an invalid target"
560 );
561 }
562
563 #[test]
564 fn valid_targets() {
565 test_target("1.71", RustTarget::Stable_1_71);
566 test_target("1.71.0", RustTarget::Stable_1_71);
567 test_target("1.71.1", RustTarget::Stable_1_71);
568 test_target("1.72", RustTarget::Stable_1_71);
569 test_target("1.73", RustTarget::Stable_1_73);
570 test_target("1.82.0-beta", RustTarget::Stable_1_82);
571 test_target("1.82.0-beta.1", RustTarget::Stable_1_82);
572 test_target("1.83.1-nightly", RustTarget::Stable_1_82);
573 test_target("1.18446744073709551615", LATEST_STABLE_RUST);
574 test_target("nightly", RustTarget::Nightly);
575 }
576
577 #[test]
578 fn invalid_targets() {
579 test_invalid_target("2.0");
580 test_invalid_target("1.cat");
581 test_invalid_target("1.0.cat");
582 test_invalid_target("1.18446744073709551616");
583 test_invalid_target("1.0.18446744073709551616");
584 test_invalid_target("1.-1.0");
585 test_invalid_target("1.0.-1");
586 test_invalid_target("beta");
587 test_invalid_target("1.0.0");
588 test_invalid_target("1.32.0");
589 }
590}