Skip to content

Commit d59bf5e

Browse files
committed
tasks: add a toolchain check
1 parent 348a992 commit d59bf5e

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

tasks/src/lint.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use xshell::Shell;
33

44
use crate::environment::{get_crate_dirs, quiet_println};
55
use crate::quiet_cmd;
6+
use crate::toolchain::{check_toolchain, Toolchain};
67

78
/// Configuration for allowed duplicate dependencies.
89
#[derive(Debug, serde::Deserialize)]
@@ -13,6 +14,7 @@ struct WhitelistConfig {
1314

1415
/// Run the lint task.
1516
pub fn run(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
17+
check_toolchain(sh, Toolchain::Nightly)?;
1618
quiet_println("Running lint task...");
1719

1820
lint_workspace(sh)?;

tasks/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod environment;
22
mod lint;
3+
mod toolchain;
34

45
use clap::{Parser, Subcommand};
56
use std::process;

tasks/src/toolchain.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use std::path::Path;
2+
use xshell::{cmd, Shell};
3+
4+
/// Toolchain requirement for a task.
5+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6+
pub enum Toolchain {
7+
/// Nightly toolchain.
8+
Nightly,
9+
/// Stable toolchain.
10+
Stable,
11+
/// Minimum Supported Rust Version.
12+
Msrv,
13+
}
14+
15+
/// Check if the current toolchain matches the requirement of current crate.
16+
///
17+
/// # Errors
18+
///
19+
/// * Cannot determine current toolchain version.
20+
/// * Current toolchain doesn't match requirement.
21+
/// * For MSRV: cannot read rust-version from Cargo.toml.
22+
pub fn check_toolchain(sh: &Shell, required: Toolchain) -> Result<(), Box<dyn std::error::Error>> {
23+
let current = cmd!(sh, "rustc --version").read()?;
24+
25+
match required {
26+
Toolchain::Nightly => {
27+
if !current.contains("nightly") {
28+
return Err(format!("Need a nightly compiler; have {}", current).into());
29+
}
30+
}
31+
Toolchain::Stable => {
32+
if current.contains("nightly") || current.contains("beta") {
33+
return Err(format!("Need a stable compiler; have {}", current).into());
34+
}
35+
}
36+
Toolchain::Msrv => {
37+
let manifest_path = sh.current_dir().join("Cargo.toml");
38+
39+
if !manifest_path.exists() {
40+
return Err("Not in a crate directory (no Cargo.toml found)".into());
41+
}
42+
43+
let msrv_version = get_msrv_from_manifest(&manifest_path)?;
44+
let current_version =
45+
extract_version(&current).ok_or("Could not parse rustc version")?;
46+
47+
if current_version != msrv_version {
48+
return Err(format!(
49+
"Need Rust {} for MSRV testing in {}; have {}",
50+
msrv_version,
51+
manifest_path.display(),
52+
current_version
53+
)
54+
.into());
55+
}
56+
}
57+
}
58+
59+
Ok(())
60+
}
61+
62+
/// Extract MSRV from Cargo.toml using cargo metadata.
63+
fn get_msrv_from_manifest(manifest_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
64+
let sh = Shell::new()?;
65+
let metadata = cmd!(sh, "cargo metadata --format-version 1 --no-deps").read()?;
66+
let data: serde_json::Value = serde_json::from_str(&metadata)?;
67+
68+
// Convert Path to string for comparison. If path contains invalid UTF-8, fail early.
69+
let manifest_path_str = manifest_path.to_str().ok_or_else(|| {
70+
format!(
71+
"Manifest path contains invalid UTF-8: {}",
72+
manifest_path.display()
73+
)
74+
})?;
75+
76+
let msrv = data["packages"]
77+
.as_array()
78+
.and_then(|packages| {
79+
packages
80+
.iter()
81+
.find(|pkg| pkg["manifest_path"].as_str() == Some(manifest_path_str))
82+
})
83+
.and_then(|pkg| pkg["rust_version"].as_str())
84+
.ok_or_else(|| {
85+
format!(
86+
"No MSRV (rust-version) specified in {}",
87+
manifest_path.display()
88+
)
89+
})?;
90+
91+
Ok(msrv.to_string())
92+
}
93+
94+
/// Extract version number from rustc --version output.
95+
/// Example: "rustc 1.74.0 (79e9716c9 2023-11-13)" -> Some("1.74.0")
96+
fn extract_version(rustc_version: &str) -> Option<&str> {
97+
rustc_version.split_whitespace().find_map(|part| {
98+
// Split off any suffix like "-nightly" or "-beta".
99+
let version_part = part.split('-').next()?;
100+
101+
// Version format: digit.digit.digit
102+
let parts: Vec<&str> = version_part.split('.').collect();
103+
if parts.len() == 3 && parts.iter().all(|p| p.chars().all(|c| c.is_ascii_digit())) {
104+
Some(version_part)
105+
} else {
106+
None
107+
}
108+
})
109+
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
use super::*;
114+
115+
#[test]
116+
fn test_extract_version() {
117+
assert_eq!(
118+
extract_version("rustc 1.74.0 (79e9716c9 2023-11-13)"),
119+
Some("1.74.0")
120+
);
121+
assert_eq!(
122+
extract_version("rustc 1.75.0-nightly (12345abcd 2023-11-20)"),
123+
Some("1.75.0")
124+
);
125+
assert_eq!(extract_version("rustc 1.74.0"), Some("1.74.0"));
126+
assert_eq!(extract_version("rustc unknown version"), None);
127+
assert_eq!(extract_version("no version here"), None);
128+
}
129+
}

0 commit comments

Comments
 (0)