use std::path::PathBuf;
#[cfg(target_os = "windows")]
use std::process::Command;
#[cfg(target_os = "windows")]
use std::{env, fs};
#[cfg(target_os = "windows")]
pub(crate) fn which_exe(name: &str) -> Option<PathBuf> {
let path = env::var_os("PATH")?;
for dir in env::split_paths(&path) {
let candidate = dir.join(name);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
pub(crate) fn find_on_path(name: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
let exts: &[&str] = if cfg!(windows) { &["", ".exe"] } else { &[""] };
for dir in std::env::split_paths(&path) {
for ext in exts {
let mut candidate = dir.join(name);
if !ext.is_empty() {
let mut s = candidate.into_os_string();
s.push(ext);
candidate = PathBuf::from(s);
}
if candidate.is_file() {
return Some(candidate);
}
}
}
None
}
#[cfg(target_os = "windows")]
pub(crate) fn locate_cmake() -> Option<PathBuf> {
if let Some(p) = which_exe("cmake.exe") {
return Some(p);
}
for vs_install in vs_install_paths() {
let bundled =
vs_install.join(r"Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe");
if bundled.is_file() {
return Some(bundled);
}
}
for c in [
r"C:\Program Files\CMake\bin\cmake.exe",
r"C:\Program Files (x86)\CMake\bin\cmake.exe",
] {
let p = PathBuf::from(c);
if p.is_file() {
return Some(p);
}
}
None
}
#[cfg(target_os = "windows")]
pub(crate) fn locate_ninja() -> Option<PathBuf> {
if let Some(p) = which_exe("ninja.exe") {
return Some(p);
}
for vs_install in vs_install_paths() {
let bundled =
vs_install.join(r"Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe");
if bundled.is_file() {
return Some(bundled);
}
}
None
}
#[cfg(target_os = "windows")]
pub(crate) fn locate_msvc_cl() -> Option<PathBuf> {
if let Some(p) = which_exe("cl.exe") {
return Some(p);
}
let mut candidates: Vec<(String, PathBuf)> = Vec::new();
for vs_install in vs_install_paths() {
let msvc_root = vs_install.join(r"VC\Tools\MSVC");
let Ok(entries) = fs::read_dir(&msvc_root) else {
continue;
};
for entry in entries.flatten() {
let cl = entry.path().join(r"bin\Hostx64\x64\cl.exe");
if cl.is_file() {
let ver = entry.file_name().to_string_lossy().into_owned();
candidates.push((ver, cl));
}
}
}
candidates.sort_by(|a, b| b.0.cmp(&a.0));
candidates.into_iter().next().map(|(_, p)| p)
}
#[cfg(target_os = "windows")]
pub(crate) fn vs_install_paths() -> Vec<PathBuf> {
let vswhere =
PathBuf::from(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe");
if !vswhere.exists() {
return Vec::new();
}
let out = Command::new(&vswhere)
.args(["-all", "-property", "installationPath", "-format", "value"])
.output();
match out {
Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout)
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.map(PathBuf::from)
.collect(),
_ => Vec::new(),
}
}
#[cfg(target_os = "windows")]
pub(crate) fn locate_vcvars64() -> Option<PathBuf> {
let vswhere =
PathBuf::from(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe");
if !vswhere.exists() {
return None;
}
let out = Command::new(&vswhere)
.args([
"-latest",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property",
"installationPath",
"-format",
"value",
])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let install = String::from_utf8(out.stdout).ok()?;
let install = install.trim();
if install.is_empty() {
return None;
}
let vcvars = PathBuf::from(install).join(r"VC\Auxiliary\Build\vcvars64.bat");
vcvars.exists().then_some(vcvars)
}
#[cfg(target_os = "windows")]
pub(crate) fn locate_vcvarsall() -> Option<PathBuf> {
for vs_install in vs_install_paths() {
let vcvars = vs_install.join(r"VC\Auxiliary\Build\vcvarsall.bat");
if vcvars.is_file() {
return Some(vcvars);
}
}
None
}