use std::path::PathBuf;
use color_eyre::eyre;
use crate::{
brew::Brew,
toolchain::linux::{has_supported_package_manager, install_named_packages},
toolchain::winget::{WingetInstallError, ensure_package_installed},
toolchain::{Installation, Toolchain, ToolchainError},
utils::which,
};
#[derive(Debug, Clone, Default)]
pub struct Cmake {}
impl Cmake {
pub async fn path(&self) -> eyre::Result<PathBuf> {
which("cmake").await.map_err(|e| eyre::eyre!(e))
}
}
impl Toolchain for Cmake {
type Installation = CmakeInstallation;
async fn check(&self) -> Result<(), ToolchainError<Self::Installation>> {
if which("cmake").await.is_ok() {
Ok(())
} else if cfg!(target_os = "windows") {
if which("winget").await.is_ok() {
Err(ToolchainError::fixable(CmakeInstallation))
} else {
Err(ToolchainError::unfixable(
"CMake not found and winget is unavailable",
"Install Microsoft App Installer to provide winget, or install CMake manually and ensure `cmake` is available in PATH.",
))
}
} else if cfg!(target_os = "macos") {
if which("brew").await.is_ok() {
Err(ToolchainError::fixable(CmakeInstallation))
} else {
Err(ToolchainError::unfixable(
"CMake not found and Homebrew is unavailable",
"Install Homebrew to enable automatic fixes, or install CMake manually and ensure `cmake` is available in PATH.",
))
}
} else if cfg!(target_os = "linux") {
if has_supported_package_manager().await {
Err(ToolchainError::fixable(CmakeInstallation))
} else {
Err(ToolchainError::unfixable(
"CMake is missing and no supported package manager was found",
"Install CMake manually and ensure `cmake` is available in PATH.",
))
}
} else {
Err(ToolchainError::unfixable(
"CMake not found",
"Install CMake manually for your platform and ensure `cmake` is available in PATH.",
))
}
}
}
#[derive(Debug, Clone)]
pub struct CmakeInstallation;
#[derive(Debug, thiserror::Error)]
pub enum FailToInstallCmake {
#[error("Homebrew not found. Please install Homebrew to proceed.")]
BrewNotFound,
#[error("Failed to install CMake: {0}")]
Other(eyre::Report),
#[error(
"winget is required for automatic CMake installation on Windows. Install App Installer and retry."
)]
WingetNotFound,
#[error("Failed to install CMake via winget: {0}")]
WingetInstallFailed(String),
#[error(
"No supported Linux package manager found (apt-get, dnf, pacman, zypper, apk). Install CMake manually."
)]
UnsupportedPackageManager,
#[error(
"Automatic installation of CMake is not supported on this platform. Please install CMake manually."
)]
UnsupportedPlatform,
}
impl Installation for CmakeInstallation {
type Error = FailToInstallCmake;
async fn install(&self) -> Result<(), Self::Error> {
if cfg!(target_os = "macos") {
let brew = Brew::default();
brew.check()
.await
.map_err(|_| FailToInstallCmake::BrewNotFound)?;
brew.install("cmake")
.await
.map_err(FailToInstallCmake::Other)?;
Ok(())
} else if cfg!(target_os = "windows") {
ensure_package_installed("Kitware.CMake")
.await
.map_err(map_winget_error_for_cmake)
} else if cfg!(target_os = "linux") {
install_named_packages(&["cmake"])
.await
.map_err(map_linux_error_for_cmake)
} else {
Err(FailToInstallCmake::UnsupportedPlatform)
}
}
}
fn map_linux_error_for_cmake(error: eyre::Report) -> FailToInstallCmake {
let message = error.to_string();
if message.contains("No supported Linux package manager found") {
FailToInstallCmake::UnsupportedPackageManager
} else {
FailToInstallCmake::Other(error)
}
}
fn map_winget_error_for_cmake(error: WingetInstallError) -> FailToInstallCmake {
match error {
WingetInstallError::WingetNotFound => FailToInstallCmake::WingetNotFound,
WingetInstallError::CommandFailed(err) => {
FailToInstallCmake::WingetInstallFailed(err.to_string())
}
WingetInstallError::NotInstalled { package_id } => {
FailToInstallCmake::WingetInstallFailed(format!(
"Package `{package_id}` is still missing after winget install; verify winget sources and retry."
))
}
}
}
#[cfg(test)]
mod tests {
use super::{FailToInstallCmake, map_winget_error_for_cmake};
use crate::toolchain::winget::WingetInstallError;
#[test]
fn maps_winget_not_found_to_specific_error() {
let mapped = map_winget_error_for_cmake(WingetInstallError::WingetNotFound);
assert!(matches!(mapped, FailToInstallCmake::WingetNotFound));
}
#[test]
fn maps_not_installed_error_with_package_context() {
let mapped = map_winget_error_for_cmake(WingetInstallError::NotInstalled {
package_id: "Kitware.CMake",
});
let message = mapped.to_string();
assert!(message.contains("Kitware.CMake"));
assert!(message.contains("still missing"));
}
}