1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2
3 use std::{env, iter, process::Command, str};
4
rustc_version() -> Option<Version>5 pub(crate) fn rustc_version() -> Option<Version> {
6 let rustc = env::var_os("RUSTC")?;
7 let rustc_wrapper = if env::var_os("CARGO_ENCODED_RUSTFLAGS").is_some() {
8 env::var_os("RUSTC_WRAPPER").filter(|v| !v.is_empty())
9 } else {
10 // Cargo sets environment variables for wrappers correctly only since https://github.com/rust-lang/cargo/pull/9601.
11 None
12 };
13 // Do not apply RUSTC_WORKSPACE_WRAPPER: https://github.com/cuviper/autocfg/issues/58#issuecomment-2067625980
14 let mut rustc = rustc_wrapper.into_iter().chain(iter::once(rustc));
15 let mut cmd = Command::new(rustc.next().unwrap());
16 cmd.args(rustc);
17 // Use verbose version output because the packagers add extra strings to the normal version output.
18 // Do not use long flags (--version --verbose) because clippy-deriver doesn't handle them properly.
19 // -vV is also matched with that cargo internally uses: https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/rustc.rs#L65
20 let output = cmd.arg("-vV").output().ok()?;
21 let verbose_version = str::from_utf8(&output.stdout).ok()?;
22 Version::parse(verbose_version)
23 }
24
25 #[cfg_attr(test, derive(Debug, PartialEq))]
26 pub(crate) struct Version {
27 pub(crate) minor: u32,
28 pub(crate) nightly: bool,
29 commit_date: Date,
30 pub(crate) llvm: u32,
31 }
32
33 impl Version {
34 // The known latest stable version. If we unable to determine
35 // the rustc version, we assume this is the current version.
36 // It is no problem if this is older than the actual latest stable.
37 // LLVM version is assumed to be the minimum external LLVM version:
38 // https://github.com/rust-lang/rust/blob/1.81.0/src/bootstrap/src/core/build_steps/llvm.rs#L588
39 pub(crate) const LATEST: Self = Self::stable(81, 17);
40
stable(rustc_minor: u32, llvm_major: u32) -> Self41 pub(crate) const fn stable(rustc_minor: u32, llvm_major: u32) -> Self {
42 Self { minor: rustc_minor, nightly: false, commit_date: Date::UNKNOWN, llvm: llvm_major }
43 }
44
probe(&self, minor: u32, year: u16, month: u8, day: u8) -> bool45 pub(crate) fn probe(&self, minor: u32, year: u16, month: u8, day: u8) -> bool {
46 if self.nightly {
47 self.minor > minor
48 || self.minor == minor && self.commit_date >= Date::new(year, month, day)
49 } else {
50 self.minor >= minor
51 }
52 }
53
54 #[cfg(test)]
commit_date(&self) -> &Date55 pub(crate) fn commit_date(&self) -> &Date {
56 &self.commit_date
57 }
58
parse(verbose_version: &str) -> Option<Self>59 pub(crate) fn parse(verbose_version: &str) -> Option<Self> {
60 let mut release = verbose_version
61 .lines()
62 .find(|line| line.starts_with("release: "))
63 .map(|line| &line["release: ".len()..])?
64 .splitn(2, '-');
65 let version = release.next().unwrap();
66 let channel = release.next().unwrap_or_default();
67 let mut digits = version.splitn(3, '.');
68 let major = digits.next()?;
69 if major != "1" {
70 return None;
71 }
72 let minor = digits.next()?.parse::<u32>().ok()?;
73 let _patch = digits.next().unwrap_or("0").parse::<u32>().ok()?;
74 let nightly = channel == "nightly" || channel == "dev";
75
76 // Note that rustc 1.49-1.50 (and 1.13 or older) don't print LLVM version.
77 let llvm_major = (|| {
78 let version = verbose_version
79 .lines()
80 .find(|line| line.starts_with("LLVM version: "))
81 .map(|line| &line["LLVM version: ".len()..])?;
82 let mut digits = version.splitn(3, '.');
83 let major = digits.next()?.parse::<u32>().ok()?;
84 let _minor = digits.next()?.parse::<u32>().ok()?;
85 let _patch = digits.next().unwrap_or("0").parse::<u32>().ok()?;
86 Some(major)
87 })()
88 .unwrap_or(0);
89
90 // we don't refer commit date on stable/beta.
91 if nightly {
92 let commit_date = (|| {
93 let mut commit_date = verbose_version
94 .lines()
95 .find(|line| line.starts_with("commit-date: "))
96 .map(|line| &line["commit-date: ".len()..])?
97 .splitn(3, '-');
98 let year = commit_date.next()?.parse::<u16>().ok()?;
99 let month = commit_date.next()?.parse::<u8>().ok()?;
100 let day = commit_date.next()?.parse::<u8>().ok()?;
101 if month > 12 || day > 31 {
102 return None;
103 }
104 Some(Date::new(year, month, day))
105 })();
106 Some(Self {
107 minor,
108 nightly,
109 commit_date: commit_date.unwrap_or(Date::UNKNOWN),
110 llvm: llvm_major,
111 })
112 } else {
113 Some(Self::stable(minor, llvm_major))
114 }
115 }
116 }
117
118 #[derive(PartialEq, PartialOrd)]
119 #[cfg_attr(test, derive(Debug))]
120 pub(crate) struct Date {
121 pub(crate) year: u16,
122 pub(crate) month: u8,
123 pub(crate) day: u8,
124 }
125
126 impl Date {
127 const UNKNOWN: Self = Self::new(0, 0, 0);
128
new(year: u16, month: u8, day: u8) -> Self129 const fn new(year: u16, month: u8, day: u8) -> Self {
130 Self { year, month, day }
131 }
132 }
133