use std::path::PathBuf;
#[cfg(target_os = "windows")]
use std::process::Command;
use anyhow::{bail, Result};
use super::Toolchain;
pub struct MsvcToolchain {
vs_path: PathBuf,
version: String,
vc_tools_version: String,
}
impl MsvcToolchain {
pub fn detect() -> Result<Self> {
#[cfg(target_os = "windows")]
{
Self::detect_windows()
}
#[cfg(not(target_os = "windows"))]
{
Self::detect_xwin()
}
}
#[cfg(not(target_os = "windows"))]
fn detect_xwin() -> Result<Self> {
let xwin_sdk_path = PathBuf::from("/opt/xwin/sdk");
if !xwin_sdk_path.exists() {
bail!(
"xwin Windows SDK not found at /opt/xwin/sdk\n\
For Docker builds with MSVC toolchain, use: --docker --toolchain msvc\n\
For cross-compilation, use MinGW instead: --toolchain mingw"
);
}
let clang_cl_path = PathBuf::from("/usr/local/bin/clang-cl");
if !clang_cl_path.exists() {
bail!(
"clang-cl wrapper not found at /usr/local/bin/clang-cl\n\
Your Docker image may be outdated. Please rebuild it:\n\
docker rmi ccgo-builder-windows-msvc"
);
}
let clang_output = std::process::Command::new("clang")
.arg("--version")
.output();
if clang_output.is_err() {
bail!("clang not found. xwin requires LLVM/Clang to be installed.");
}
let version = if let Ok(output) = clang_output {
String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.unwrap_or("unknown")
.to_string()
} else {
"unknown".to_string()
};
Ok(Self {
vs_path: xwin_sdk_path,
version,
vc_tools_version: "xwin".to_string(),
})
}
#[cfg(target_os = "windows")]
fn detect_windows() -> Result<Self> {
let vswhere_paths = [
r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe",
r"C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe",
];
let vswhere_path = vswhere_paths
.iter()
.find(|p| std::path::Path::new(p).exists())
.ok_or_else(|| {
anyhow::anyhow!(
"Visual Studio not found. Please install Visual Studio 2019 or later."
)
})?;
let output = Command::new(vswhere_path)
.args([
"-latest",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property",
"installationPath",
])
.output()
.map_err(|e| anyhow::anyhow!("Failed to run vswhere: {}", e))?;
let vs_path = String::from_utf8_lossy(&output.stdout)
.trim()
.to_string();
if vs_path.is_empty() {
bail!("No Visual Studio installation with C++ tools found");
}
let vs_path = PathBuf::from(&vs_path);
let version_output = Command::new(vswhere_path)
.args(["-latest", "-property", "installationVersion"])
.output()
.map_err(|e| anyhow::anyhow!("Failed to get VS version: {}", e))?;
let version = String::from_utf8_lossy(&version_output.stdout)
.trim()
.to_string();
let vc_version_file = vs_path
.join("VC")
.join("Auxiliary")
.join("Build")
.join("Microsoft.VCToolsVersion.default.txt");
let vc_tools_version = if vc_version_file.exists() {
std::fs::read_to_string(&vc_version_file)
.unwrap_or_default()
.trim()
.to_string()
} else {
"unknown".to_string()
};
Ok(Self {
vs_path,
version,
vc_tools_version,
})
}
pub fn vs_path(&self) -> &PathBuf {
&self.vs_path
}
pub fn version(&self) -> &str {
&self.version
}
pub fn vc_tools_version(&self) -> &str {
&self.vc_tools_version
}
pub fn vcvarsall_path(&self) -> PathBuf {
self.vs_path
.join("VC")
.join("Auxiliary")
.join("Build")
.join("vcvarsall.bat")
}
pub fn cmake_generator(&self) -> &str {
if self.vc_tools_version == "xwin" {
return "Ninja";
}
let major = self
.version
.split('.')
.next()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(16);
match major {
17 => "Visual Studio 17 2022",
16 => "Visual Studio 16 2019",
15 => "Visual Studio 15 2017",
_ => "Visual Studio 16 2019", }
}
pub fn is_xwin(&self) -> bool {
self.vc_tools_version == "xwin"
}
}
impl Toolchain for MsvcToolchain {
fn name(&self) -> &str {
"msvc"
}
fn is_available(&self) -> bool {
self.vs_path.exists() && self.vcvarsall_path().exists()
}
fn path(&self) -> Option<PathBuf> {
Some(self.vs_path.clone())
}
fn cmake_variables(&self) -> Vec<(String, String)> {
let mut vars = vec![
(
"CMAKE_GENERATOR".to_string(),
self.cmake_generator().to_string(),
),
];
if !self.is_xwin() {
vars.push(("CMAKE_GENERATOR_PLATFORM".to_string(), "x64".to_string()));
} else {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_default();
let toolchain_path = PathBuf::from(home)
.join(".ccgo/cmake/windows-msvc.toolchain.cmake");
vars.push((
"CMAKE_TOOLCHAIN_FILE".to_string(),
toolchain_path.display().to_string(),
));
}
vars
}
fn validate(&self) -> Result<()> {
if !self.vs_path.exists() {
if self.is_xwin() {
bail!(
"xwin Windows SDK not found at: {}\n\
Your Docker image may be missing xwin installation.",
self.vs_path.display()
);
} else {
bail!(
"Visual Studio installation not found at: {}",
self.vs_path.display()
);
}
}
if !self.is_xwin() {
let vcvarsall = self.vcvarsall_path();
if !vcvarsall.exists() {
bail!("vcvarsall.bat not found at: {}", vcvarsall.display());
}
} else {
let clang_cl = PathBuf::from("/usr/local/bin/clang-cl");
if !clang_cl.exists() {
bail!(
"clang-cl wrapper not found at: {}\n\
Your Docker image may be outdated.",
clang_cl.display()
);
}
}
Ok(())
}
}
pub fn is_msvc_available() -> bool {
MsvcToolchain::detect().is_ok()
}