#![deny(missing_docs)]
#[cfg(not(target_os = "windows"))]
compile_error!("This crate only supports Windows.");
use std::ptr::null_mut;
use std::fs;
use std::path::{Path, PathBuf};
use winapi::*;
mod winapi;
#[derive(Debug)]
pub struct Info {
pub sdk: Option<SdkInfo>,
pub toolchain: Option<ToolchainInfo>,
}
#[derive(Debug)]
pub struct SdkInfo {
pub major_version: u8,
pub root: PathBuf,
pub um_lib_path: PathBuf,
pub ucrt_lib_path: PathBuf,
}
#[derive(Debug)]
pub struct ToolchainInfo {
pub exe_path: PathBuf,
pub lib_path: PathBuf,
}
pub fn find_vc_and_windows_sdk() -> Option<Info> {
let wsdk_root = find_windows_kit();
let sdk = wsdk_root.map(|sdk| SdkInfo {
major_version: sdk.0,
root: sdk.1.clone(),
um_lib_path: sdk.1.join(r"um\x64"),
ucrt_lib_path: sdk.1.join(r"ucrt\x64"),
});
let toolchain = find_vc_toolchain();
Some(Info { sdk, toolchain })
}
fn find_vc_toolchain() -> Option<ToolchainInfo> {
let _com = ComScope::start()?;
let mut config = null_mut();
let status = unsafe {
CoCreateInstance(
&CLSID_SetupConfiguration,
null_mut(),
CLSCTX_INPROC_SERVER,
&IID_ISetupConfiguration,
&mut config,
)
};
if status < 0 {
return None;
}
let config = unsafe { ComPtr::from_raw(config as *mut ISetupConfiguration) };
let mut instances = null_mut();
let status = unsafe { config.EnumInstances(&mut instances) };
if status < 0 {
return None;
}
let instances = unsafe { ComPtr::from_raw(instances) };
loop {
let mut found: ULONG = 0;
let mut instance = null_mut();
let hr = unsafe { instances.Next(1, &mut instance, &mut found) };
if hr != S_OK {
break;
}
let instance = unsafe { ComPtr::from_raw(instance) };
let mut install_path = null_mut();
let hr = unsafe { instance.GetInstallationPath(&mut install_path) };
if hr != S_OK {
break;
}
let install_path = unsafe { BStr::from_raw(install_path) };
let install_path = install_path.to_osstring();
let install_path = Path::new(&install_path);
let tools_version = install_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
let tools_version = fs::read_to_string(tools_version).ok()?;
let tools_version = tools_version.trim();
let mut lib_path = install_path.join(r"VC\Tools\MSVC");
lib_path.push(&tools_version);
lib_path.push(r"lib\x64");
let lib_file = lib_path.join("vcruntime.lib");
if lib_file.exists() {
let mut exe_path = install_path.join(r"VC\Tools\MSVC");
exe_path.push(&tools_version);
exe_path.push(r"bin\Hostx64\x64");
let info = ToolchainInfo { exe_path, lib_path };
return Some(info);
}
}
let info = find_old_vc_toolchain();
if info.is_some() {
return info;
}
None
}
fn find_old_vc_toolchain() -> Option<ToolchainInfo> {
let mut vs7_key = RegKey::zeroed();
let err = unsafe {
RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7\0".as_ptr() as LPCSTR,
0,
KEY_QUERY_VALUE | KEY_WOW64_32KEY,
&mut *vs7_key,
)
};
if err != ERROR_SUCCESS {
return None;
}
const OLD_VERSIONS: [&str; 4] = ["14.0", "12.0", "11.0", "10.0"];
for version in OLD_VERSIONS {
if let Some(root) = reg_query_string_value(&vs7_key, version) {
let lib_path = root.join(r"VC\Lib\amd64");
let lib_file = lib_path.join("vcruntime.lib");
if lib_file.exists() {
let exe_path = root.join(r"VC\bin");
let info = ToolchainInfo { exe_path, lib_path };
return Some(info);
}
}
}
None
}
type Version = (u32, u32, u32, u32);
const SDK_VERSIONS: [(u8, &str, fn(&Path) -> Option<Version>); 2] = [
(10, "KitsRoot10", parse_w10_version),
( 8, "KitsRoot81", parse_w8_version),
];
fn find_windows_kit() -> Option<(u8, PathBuf)> {
let mut main_key = RegKey::zeroed();
let err = unsafe {
RegOpenKeyExA(
HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots\0".as_ptr() as LPCSTR,
0,
KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS,
&mut *main_key,
)
};
if err != ERROR_SUCCESS {
return None;
}
for (major_version, sub_key, version_parser) in SDK_VERSIONS {
let root = reg_query_string_value(&main_key, sub_key);
if let Some(mut root) = root {
root.push("Lib");
let best = find_latest_version(root, version_parser);
if let Some(best) = best {
return Some((major_version, best))
}
}
}
None
}
fn find_latest_version<P>(root: PathBuf, version_parser: P) -> Option<PathBuf>
where
P: Fn(&Path) -> Option<Version>,
{
let mut latest = None;
let mut folder = None;
for entry in fs::read_dir(&root).ok()? {
let entry = entry.ok()?;
let path = entry.path();
if path.is_dir() {
let version = version_parser(&path);
if version > latest {
latest = version;
folder = Some(path);
}
}
}
folder
}
fn parse_w10_version(path: &Path) -> Option<Version> {
let name = path.file_name()?.to_str()?;
let mut parts = name.split('.');
let v0 = parts.next()?.parse().ok()?;
let v1 = parts.next()?.parse().ok()?;
let v2 = parts.next()?.parse().ok()?;
let v3 = parts.next()?.parse().ok()?;
if parts.next().is_some() {
return None;
}
Some((v0, v1, v2, v3))
}
fn parse_w8_version(path: &Path) -> Option<Version> {
let name = path.file_name()?.to_str()?;
let mut parts = name.strip_prefix("winv")?.split('.');
let v0 = parts.next()?.parse().ok()?;
let v1 = parts.next()?.parse().ok()?;
if parts.next().is_some() {
return None;
}
Some((v0, v1, 0, 0))
}