use anyhow::{Context, Result, anyhow, ensure};
use regex::Regex;
use std::{borrow::Cow, process::Command, sync::LazyLock};
static TOOLCHAIN_CHANNELS: &[&str] = &[
"nightly",
"beta",
"stable",
r"\d{1}\.\d{1,3}(?:\.\d{1,2})?",
];
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Toolchain(String);
impl Toolchain {
const PINNED_NIGHTLY_TOOLCHAIN: &'static str = "nightly-2025-10-20";
pub fn recommended_nightly() -> Self {
Self(Self::PINNED_NIGHTLY_TOOLCHAIN.into())
}
pub fn try_from_rustup() -> Result<Self> {
let output = Command::new("rustup")
.args(["show", "active-toolchain"])
.output()
.context("`rustup` command failed")?;
ensure!(
output.status.success(),
"`rustup` exit code is not successful"
);
let toolchain_desc =
std::str::from_utf8(&output.stdout).expect("unexpected `rustup` output");
static TOOLCHAIN_CHANNEL_RE: LazyLock<Regex> = LazyLock::new(|| {
let channels = TOOLCHAIN_CHANNELS.join("|");
let pattern = format!(r"(?:{channels})(?:-\d{{4}}-\d{{2}}-\d{{2}})?");
Regex::new(&pattern).unwrap()
});
let toolchain = TOOLCHAIN_CHANNEL_RE
.captures(toolchain_desc)
.ok_or_else(|| anyhow!("cargo toolchain is invalid {toolchain_desc}"))?
.get(0)
.unwrap() .as_str()
.to_owned();
Ok(Self(toolchain))
}
pub fn raw_toolchain_str(&'_ self) -> Cow<'_, str> {
self.0.as_str().into()
}
pub fn check_recommended_toolchain(&self) -> Result<()> {
let toolchain = Self::PINNED_NIGHTLY_TOOLCHAIN;
ensure!(
self.raw_toolchain_str() == toolchain,
anyhow!(
"recommended toolchain `{x}` not found, install it using the command:\n\
rustup toolchain install {x} --target wasm32v1-none\n\n\
after installation, do not forget to set `channel = \"{x}\"` in `rust-toolchain.toml` file",
x = toolchain
)
);
Ok(())
}
}