use std::fmt::Display;
use semver::{Version, VersionReq};
use crate::{
ClangTool, RequestedVersion,
version::{ClangVersion, GetToolError},
};
mod unix;
mod windows;
#[derive(Debug, thiserror::Error)]
pub enum PackageManagerError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("{manager} failed to install {package} package: {stderr}")]
InstallationError {
manager: String,
package: String,
stderr: String,
},
#[cfg(target_os = "linux")]
#[error("Failed to add LLVM PPA repository (for `apt`): {0}")]
LlvmPpaError(String),
#[error("Failed parsing URL: {0}")]
UrlParseError(#[from] url::ParseError),
#[error(transparent)]
DownloadError(#[from] crate::downloader::DownloadError),
}
pub trait PackageManager {
fn is_installed(&self) -> bool;
fn get_package_name(&self, tool: &ClangTool) -> String;
fn list_managers() -> Vec<impl PackageManager + Display>
where
Self: Sized;
fn is_installed_package(&self, package_name: &str, version: Option<&Version>) -> bool;
fn install_package(
&self,
package_name: &str,
version: Option<&Version>,
) -> impl Future<Output = Result<(), PackageManagerError>>;
}
pub fn get_available_package_managers() -> Vec<impl PackageManager + Display> {
let mut managers = Vec::new();
#[cfg(target_os = "windows")]
let possibles = windows::WindowsPackageManager::list_managers();
#[cfg(unix)]
let possibles = unix::UnixPackageManager::list_managers();
for manager in possibles {
if manager.is_installed() {
managers.push(manager);
}
}
managers
}
pub async fn try_install_package(
tool: &ClangTool,
version_req: &VersionReq,
min_version: &Version,
) -> Result<Option<ClangVersion>, GetToolError> {
let os_pkg_managers = get_available_package_managers();
if os_pkg_managers.is_empty() {
log::error!("No supported package managers found on the system.");
return Ok(None);
} else {
for mgr in os_pkg_managers {
log::info!("Trying to install {tool} v{min_version} using {mgr} package manager.");
let pkg_name = mgr.get_package_name(tool);
if mgr.is_installed_package(&pkg_name, Some(min_version)) {
let path =
tool.get_exe_path(&RequestedVersion::Requirement(version_req.clone()))?;
let version = tool.capture_version(&path)?;
if version_req.matches(&version) {
log::info!(
"Found {tool} version matching {version_req} installed via {mgr} package manager."
);
return Ok(Some(ClangVersion { version, path }));
}
} else {
log::info!(
"{mgr} package manager does not have a version of {tool} matching {version_req} installed."
);
match mgr.install_package(&pkg_name, Some(min_version)).await {
Ok(()) => {
log::info!(
"Successfully installed {tool} v{min_version} using {mgr} package manager."
);
let path =
tool.get_exe_path(&RequestedVersion::Requirement(version_req.clone()))?;
let version = tool.capture_version(&path)?;
if version_req.matches(&version) {
log::info!(
"Installed {tool} version {version} matches the requirement {version_req}."
);
return Ok(Some(ClangVersion { version, path }));
} else {
log::error!(
"Installed {tool} version {version} does not match the requirement {version_req}."
);
}
}
Err(e) => {
log::error!(
"Failed to install {tool} v{min_version} using {mgr} package manager: {e}"
);
}
}
}
}
}
Ok(None)
}