use serde::{Deserialize, Serialize};
use std::fmt;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Architecture {
#[default]
X64,
X86,
Arm64,
Arm,
}
impl fmt::Display for Architecture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Architecture::X64 => write!(f, "x64"),
Architecture::X86 => write!(f, "x86"),
Architecture::Arm64 => write!(f, "arm64"),
Architecture::Arm => write!(f, "arm"),
}
}
}
impl std::str::FromStr for Architecture {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"x64" | "amd64" | "x86_64" => Ok(Architecture::X64),
"x86" | "i686" | "i386" => Ok(Architecture::X86),
"arm64" | "aarch64" => Ok(Architecture::Arm64),
"arm" => Ok(Architecture::Arm),
_ => Err(format!("Unknown architecture: {}", s)),
}
}
}
impl Architecture {
pub fn host() -> Self {
#[cfg(target_arch = "x86_64")]
return Architecture::X64;
#[cfg(target_arch = "x86")]
return Architecture::X86;
#[cfg(target_arch = "aarch64")]
return Architecture::Arm64;
#[cfg(target_arch = "arm")]
return Architecture::Arm;
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "arm"
)))]
return Architecture::X64; }
pub fn msvc_host_dir(&self) -> &'static str {
match self {
Architecture::X64 => "Hostx64",
Architecture::X86 => "Hostx86",
Architecture::Arm64 => "Hostarm64",
Architecture::Arm => "Hostarm",
}
}
pub fn msvc_target_dir(&self) -> &'static str {
match self {
Architecture::X64 => "x64",
Architecture::X86 => "x86",
Architecture::Arm64 => "arm64",
Architecture::Arm => "arm",
}
}
}
pub trait VersionType: Clone + Default {
fn component_name() -> &'static str;
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Msvc;
impl VersionType for Msvc {
fn component_name() -> &'static str {
"MSVC"
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sdk;
impl VersionType for Sdk {
fn component_name() -> &'static str {
"Windows SDK"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Version<T: VersionType> {
pub version: String,
pub display_name: String,
pub is_latest: bool,
pub install_path: Option<PathBuf>,
pub download_url: Option<String>,
pub size: Option<u64>,
pub sha256: Option<String>,
#[serde(skip)]
_marker: PhantomData<T>,
}
impl<T: VersionType> Version<T> {
pub fn new(version: impl Into<String>, display_name: impl Into<String>) -> Self {
Self {
version: version.into(),
display_name: display_name.into(),
is_latest: false,
install_path: None,
download_url: None,
size: None,
sha256: None,
_marker: PhantomData,
}
}
pub fn is_installed(&self) -> bool {
self.install_path
.as_ref()
.map(|p| p.exists())
.unwrap_or(false)
}
pub fn component_name(&self) -> &'static str {
T::component_name()
}
}
impl<T: VersionType> fmt::Display for Version<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.version)?;
if self.is_latest {
write!(f, " (latest)")?;
}
if self.is_installed() {
write!(f, " [installed]")?;
}
Ok(())
}
}
pub type MsvcVersion = Version<Msvc>;
pub type SdkVersion = Version<Sdk>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstalledVersion {
pub msvc: Option<MsvcVersion>,
pub sdk: Option<SdkVersion>,
pub installed_at: chrono::DateTime<chrono::Utc>,
pub arch: Architecture,
}
pub fn is_msvc_installed(install_dir: &Path, version: &str) -> bool {
let msvc_dir = install_dir.join("VC").join("Tools").join("MSVC");
if !msvc_dir.exists() {
return false;
}
let version_dir = msvc_dir.join(version);
if version_dir.exists() {
return true;
}
if let Ok(entries) = std::fs::read_dir(&msvc_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with(version) {
return true;
}
}
}
}
}
false
}
pub fn is_sdk_installed(install_dir: &Path, version: &str) -> bool {
let sdk_dir = install_dir.join("Windows Kits").join("10").join("Include");
if !sdk_dir.exists() {
return false;
}
let version_dir = sdk_dir.join(version);
if version_dir.exists() {
return true;
}
if let Ok(entries) = std::fs::read_dir(&sdk_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(name) = entry.file_name().to_str() {
if name.contains(version) {
return true;
}
}
}
}
}
false
}
pub fn list_installed_msvc(install_dir: &Path) -> Vec<MsvcVersion> {
let msvc_dir = install_dir.join("VC").join("Tools").join("MSVC");
if !msvc_dir.exists() {
return Vec::new();
}
let mut versions = Vec::new();
if let Ok(entries) = std::fs::read_dir(&msvc_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(name) = entry.file_name().to_str() {
let mut version = MsvcVersion::new(name, format!("MSVC {}", name));
version.install_path = Some(entry.path());
versions.push(version);
}
}
}
}
versions.sort_by(|a, b| b.version.cmp(&a.version));
if let Some(first) = versions.first_mut() {
first.is_latest = true;
}
versions
}
pub fn list_installed_sdk(install_dir: &Path) -> Vec<SdkVersion> {
let sdk_dir = install_dir.join("Windows Kits").join("10").join("Include");
if !sdk_dir.exists() {
return Vec::new();
}
let mut versions = Vec::new();
if let Ok(entries) = std::fs::read_dir(&sdk_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with("10.0.") {
let mut version = SdkVersion::new(name, format!("Windows SDK {}", name));
version.install_path = Some(install_dir.join("Windows Kits").join("10"));
versions.push(version);
}
}
}
}
}
versions.sort_by(|a, b| b.version.cmp(&a.version));
if let Some(first) = versions.first_mut() {
first.is_latest = true;
}
versions
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_architecture_display() {
assert_eq!(Architecture::X64.to_string(), "x64");
assert_eq!(Architecture::X86.to_string(), "x86");
assert_eq!(Architecture::Arm64.to_string(), "arm64");
}
#[test]
fn test_architecture_parse() {
assert_eq!("x64".parse::<Architecture>().unwrap(), Architecture::X64);
assert_eq!("amd64".parse::<Architecture>().unwrap(), Architecture::X64);
assert_eq!("x86".parse::<Architecture>().unwrap(), Architecture::X86);
assert_eq!(
"arm64".parse::<Architecture>().unwrap(),
Architecture::Arm64
);
}
#[test]
fn test_msvc_host_dir() {
assert_eq!(Architecture::X64.msvc_host_dir(), "Hostx64");
assert_eq!(Architecture::X86.msvc_host_dir(), "Hostx86");
}
#[test]
fn test_version_generic() {
let msvc = MsvcVersion::new("14.40.33807", "MSVC 14.40");
assert_eq!(msvc.component_name(), "MSVC");
assert!(!msvc.is_installed());
let sdk = SdkVersion::new("10.0.22621.0", "Windows SDK 10.0.22621");
assert_eq!(sdk.component_name(), "Windows SDK");
assert!(!sdk.is_installed());
}
}