use std::path::{Path, PathBuf};
pub fn windows_kits_roots() -> Vec<PathBuf> {
let mut roots = Vec::new();
if let Ok(v) = std::env::var("DAY_WINDOWS_KITS_ROOT") {
roots.push(PathBuf::from(v));
}
if let Ok(v) = std::env::var("WindowsSdkDir") {
roots.push(PathBuf::from(v));
}
for pf in ["ProgramFiles(x86)", "ProgramFiles"] {
if let Ok(v) = std::env::var(pf) {
roots.push(PathBuf::from(v).join("Windows Kits").join("10"));
}
}
roots.dedup();
roots
}
pub fn cppwinrt_include() -> Option<PathBuf> {
if let Ok(v) = std::env::var("DAY_CPPWINRT") {
let p = PathBuf::from(v);
if p.join("winrt").join("base.h").exists() {
return Some(p);
}
return None;
}
let mut found: Vec<PathBuf> = Vec::new();
for root in windows_kits_roots() {
let Ok(rd) = std::fs::read_dir(root.join("Include")) else {
continue;
};
for entry in rd.flatten() {
let cppwinrt = entry.path().join("cppwinrt");
if cppwinrt.join("winrt").join("base.h").exists() {
found.push(cppwinrt);
}
}
}
found.sort(); found.pop()
}
pub fn cppwinrt_include_for_build_script() -> Option<PathBuf> {
for var in ["DAY_CPPWINRT", "DAY_WINDOWS_KITS_ROOT", "WindowsSdkDir"] {
println!("cargo:rerun-if-env-changed={var}");
}
cppwinrt_include()
}
pub fn windows_kit_tool(tool: &str) -> Option<PathBuf> {
if let Ok(root) = std::env::var("DAY_WINDOWS_KIT") {
let p = PathBuf::from(root).join(tool);
if p.exists() {
return Some(p);
}
}
if let Some(p) = on_path(tool) {
return Some(p);
}
let arch = if cfg!(target_arch = "aarch64") {
"arm64"
} else {
"x64"
};
for root in windows_kits_roots() {
let Ok(rd) = std::fs::read_dir(root.join("bin")) else {
continue;
};
let mut versions: Vec<PathBuf> = rd
.flatten()
.map(|e| e.path())
.filter(|p| {
p.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with("10."))
})
.collect();
versions.sort();
while let Some(v) = versions.pop() {
let candidate = v.join(arch).join(tool);
if candidate.exists() {
return Some(candidate);
}
}
}
None
}
pub fn makensis() -> Option<PathBuf> {
if let Ok(v) = std::env::var("DAY_MAKENSIS") {
let p = PathBuf::from(v);
if p.is_file() {
return Some(p);
}
return None; }
if let Some(p) = on_path("makensis").or_else(|| on_path("makensis.exe")) {
return Some(p);
}
for pf in ["ProgramFiles(x86)", "ProgramFiles"] {
if let Ok(v) = std::env::var(pf) {
let p = PathBuf::from(v).join("NSIS").join("makensis.exe");
if p.exists() {
return Some(p);
}
}
}
None
}
pub fn android_sdk_dir() -> PathBuf {
if let Ok(v) = std::env::var("ANDROID_HOME").or_else(|_| std::env::var("ANDROID_SDK_ROOT")) {
return PathBuf::from(v);
}
if cfg!(target_os = "windows")
&& let Ok(v) = std::env::var("LOCALAPPDATA")
{
return PathBuf::from(v).join("Android").join("Sdk");
}
let home = PathBuf::from(std::env::var("HOME").unwrap_or_default());
if cfg!(target_os = "macos") {
home.join("Library/Android/sdk")
} else {
home.join("Android/Sdk")
}
}
pub fn jdk21_home() -> Option<PathBuf> {
if let Ok(v) = std::env::var("JAVA_HOME") {
return Some(PathBuf::from(v));
}
if cfg!(target_os = "macos") {
if let Ok(out) = std::process::Command::new("/usr/libexec/java_home")
.args(["-v", "21"])
.output()
&& out.status.success()
{
let p = PathBuf::from(String::from_utf8_lossy(&out.stdout).trim());
if p.join("bin/java").exists() {
return Some(p);
}
}
for prefix in ["/opt/homebrew", "/usr/local"] {
let p = PathBuf::from(prefix).join("opt/openjdk@21");
if p.join("bin/java").exists() {
return Some(p);
}
}
}
None
}
pub fn rustup_cargo() -> Result<(PathBuf, PathBuf), String> {
let rustup_home = std::env::var("RUSTUP_HOME")
.map(PathBuf::from)
.or_else(|_| {
std::env::var("HOME")
.map(|h| PathBuf::from(h).join(".rustup"))
.map_err(|e| e.to_string())
})?;
let toolchains = rustup_home.join("toolchains");
let mut entries: Vec<PathBuf> = std::fs::read_dir(&toolchains)
.map_err(|_| "no rustup toolchains (cross-std needs rustup, not Homebrew rust)")?
.flatten()
.map(|e| e.path())
.collect();
entries.sort();
let chosen = entries
.iter()
.find(|p| {
p.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with("stable-"))
})
.or_else(|| entries.first())
.ok_or("empty rustup toolchains dir")?;
let bin = chosen.join("bin");
Ok((bin.join("cargo"), bin))
}
fn on_path(tool: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|d| d.join(tool))
.find(|p| p.is_file())
}
pub fn is_dir(dir: &Path) -> bool {
dir.is_dir()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kits_roots_honor_override_first() {
unsafe { std::env::set_var("DAY_WINDOWS_KITS_ROOT", "/custom/kits/10") };
let roots = windows_kits_roots();
assert_eq!(roots[0], PathBuf::from("/custom/kits/10"));
unsafe { std::env::remove_var("DAY_WINDOWS_KITS_ROOT") };
}
#[test]
fn android_sdk_honors_android_home() {
unsafe { std::env::set_var("ANDROID_HOME", "/custom/android") };
assert_eq!(android_sdk_dir(), PathBuf::from("/custom/android"));
unsafe { std::env::remove_var("ANDROID_HOME") };
}
#[test]
fn explicit_cppwinrt_override_must_validate() {
unsafe { std::env::set_var("DAY_CPPWINRT", "/does/not/exist") };
assert_eq!(cppwinrt_include(), None); unsafe { std::env::remove_var("DAY_CPPWINRT") };
}
}