use self::super::{ParameterBundle, env_target_and_rc, apply_parameters};
use std::path::{PathBuf, Path, MAIN_SEPARATOR};
use std::sync::atomic::Ordering::SeqCst;
use std::sync::atomic::AtomicBool;
use std::process::Command;
use vswhom::VsFindResult;
use std::borrow::Cow;
use winreg::enums::*;
use std::ffi::OsStr;
use std::{env, fs};
use winreg;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ResourceCompiler;
impl ResourceCompiler {
#[inline(always)]
pub fn new() -> ResourceCompiler {
ResourceCompiler
}
#[inline(always)]
pub fn is_supported(&mut self) -> Option<Cow<'static, str>> {
None
}
pub fn compile_resource<Ms: AsRef<OsStr>, Mi: IntoIterator<Item = Ms>, Is: AsRef<OsStr>, Ii: IntoIterator<Item = Is>>(
&self, out_dir: &str, prefix: &str, resource: &str, parameters: ParameterBundle<Ms, Mi, Is, Ii>)
-> Result<String, Cow<'static, str>> {
let out_file = format!("{}{}{}.lib", out_dir, MAIN_SEPARATOR, prefix);
let (_, rc) = env_target_and_rc()?;
let rc = rc.map(PathBuf::from).or_else(|| find_windows_sdk_tool_impl("rc.exe"));
if !apply_parameters(Command::new(rc.as_deref().unwrap_or(Path::new("rc.exe"))).args(&["/fo", &out_file, "/I", out_dir]),
"/D",
"/I",
parameters)
.arg(resource)
.status()
.map_err(|_| Cow::from("Are you sure you have RC.EXE in your $PATH or ${RC_$TARGET} or $RC is set?"))?
.success() {
return Err("RC.EXE failed to compile specified resource file".into());
}
Ok(out_file)
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum Arch {
X86,
X64,
AArch64,
}
pub fn find_windows_sdk_tool_impl(tool: &str) -> Option<PathBuf> {
let arch = match env::var_os("HOST").expect("No HOST env var").as_encoded_bytes() {
[b'x', b'8', b'6', b'_', b'6', b'4', ..] => Arch::X64, [b'a', b'a', b'r', b'c', b'h', b'6', b'4', ..] => Arch::AArch64, _ => Arch::X86,
};
find_windows_10_kits_tool("KitsRoot10", arch, tool)
.or_else(|| find_windows_kits_tool("KitsRoot10", arch, tool))
.or_else(|| find_windows_kits_tool("KitsRoot81", arch, tool))
.or_else(|| find_windows_kits_tool("KitsRoot", arch, tool))
.or_else(|| find_latest_windows_sdk_tool(arch, tool))
.or_else(|| find_with_vswhom(arch, tool))
}
fn find_with_vswhom(arch: Arch, tool: &str) -> Option<PathBuf> {
let res = VsFindResult::search();
res.as_ref()
.and_then(|res| res.windows_sdk_root.as_ref())
.map(PathBuf::from)
.and_then(|mut root| {
let ver = root.file_name().expect("malformed vswhom-returned SDK root").to_os_string();
root.pop();
root.pop();
root.push("bin");
root.push(ver);
try_bin_dir(root, "x86", "x64", "arm64", arch)
})
.and_then(|pb| try_tool(pb, tool))
.or_else(move || {
res.and_then(|res| res.windows_sdk_root)
.map(PathBuf::from)
.and_then(|mut root| {
root.pop();
root.pop();
try_bin_dir(root, "bin/x86", "bin/x64", "bin/arm64", arch)
})
.and_then(|pb| try_tool(pb, tool))
})
}
fn find_windows_kits_tool(key: &str, arch: Arch, tool: &str) -> Option<PathBuf> {
winreg::RegKey::predef(HKEY_LOCAL_MACHINE)
.open_subkey_with_flags(r"SOFTWARE\Microsoft\Windows Kits\Installed Roots", KEY_QUERY_VALUE)
.and_then(|reg_key| reg_key.get_value::<String, _>(key))
.ok()
.and_then(|root_dir| try_bin_dir(root_dir, "bin/x86", "bin/x64", "bin/arm64", arch))
.and_then(|pb| try_tool(pb, tool))
}
fn find_latest_windows_sdk_tool(arch: Arch, tool: &str) -> Option<PathBuf> {
winreg::RegKey::predef(HKEY_LOCAL_MACHINE)
.open_subkey_with_flags(r"SOFTWARE\Microsoft\Microsoft SDKs\Windows", KEY_QUERY_VALUE)
.and_then(|reg_key| reg_key.get_value::<String, _>("CurrentInstallFolder"))
.ok()
.and_then(|root_dir| try_bin_dir(root_dir, "Bin", "Bin/x64", "Bin/arm64", arch))
.and_then(|pb| try_tool(pb, tool))
}
fn find_windows_10_kits_tool(key: &str, arch: Arch, tool: &str) -> Option<PathBuf> {
let kit_root = (winreg::RegKey::predef(HKEY_LOCAL_MACHINE)
.open_subkey_with_flags(r"SOFTWARE\Microsoft\Windows Kits\Installed Roots", KEY_QUERY_VALUE)
.and_then(|reg_key| reg_key.get_value::<String, _>(key))
.ok())?;
include_windows_10_kits(&kit_root);
let root_dir = kit_root + "/bin";
for entry in fs::read_dir(&root_dir).ok()?.filter(|d| d.is_ok()).map(Result::unwrap) {
let fname = entry.file_name().into_string();
let ftype = entry.file_type();
if fname.is_err() || ftype.is_err() || ftype.unwrap().is_file() {
continue;
}
let fname = entry.file_name().into_string().unwrap();
if let Some(rc) = try_bin_dir(root_dir.clone(),
&format!("{}/x86", fname),
&format!("{}/x64", fname),
&format!("{}/arm64", fname),
arch)
.and_then(|pb| try_tool(pb, tool)) {
return Some(rc);
}
}
None
}
fn include_windows_10_kits(kit_root: &str) {
static IS_INCLUDED: AtomicBool = AtomicBool::new(false);
if !IS_INCLUDED.swap(true, SeqCst) {
let mut include = env::var("INCLUDE").unwrap_or_default();
if !include.ends_with(';') {
include.push(';');
}
if let Ok(include_root) = fs::read_dir(kit_root.to_string() + r"\Include\") {
get_dirs(include_root).filter_map(|dir| fs::read_dir(dir.path()).ok()).for_each(|dir| {
get_dirs(dir).for_each(|sub_dir| if let Some(sub_dir) = sub_dir.path().to_str() {
if !include.contains(sub_dir) {
include.push_str(sub_dir);
include.push(';');
}
})
});
}
if let Some(cl) = cc::windows_registry::find_tool(env::var("TARGET").expect("No TARGET env var").as_str(), "cl.exe") {
if let Some((_, ipaths)) = cl.env().iter().find(|(k, _)| k == "INCLUDE") {
include.push_str(ipaths.to_str().expect("%INCLUDE% from cc nonrepresentable"));
include.push(';');
}
}
env::set_var("INCLUDE", include);
}
}
fn get_dirs(read_dir: fs::ReadDir) -> impl Iterator<Item = fs::DirEntry> {
read_dir.filter_map(|dir| dir.ok()).filter(|dir| dir.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
}
fn try_bin_dir<R: Into<PathBuf>>(root_dir: R, x86_bin: &str, x64_bin: &str, aarch64_bin: &str, arch: Arch) -> Option<PathBuf> {
try_bin_dir_impl(root_dir.into(), x86_bin, x64_bin, aarch64_bin, arch)
}
fn try_bin_dir_impl(mut root_dir: PathBuf, x86_bin: &str, x64_bin: &str, aarch64_bin: &str, arch: Arch) -> Option<PathBuf> {
match arch {
Arch::X86 => root_dir.push(x86_bin),
Arch::X64 => root_dir.push(x64_bin),
Arch::AArch64 => root_dir.push(aarch64_bin),
}
if root_dir.is_dir() {
Some(root_dir)
} else {
None
}
}
fn try_tool(mut pb: PathBuf, tool: &str) -> Option<PathBuf> {
pb.push(tool);
if pb.exists() { Some(pb) } else { None }
}