use std::path::{Path, PathBuf};
use crate::build_env::Error;
#[derive(Debug, Clone, serde::Serialize)]
pub struct Sdk {
pub native_root: PathBuf,
pub sysroot: PathBuf,
pub llvm_root: PathBuf,
pub llvm_bin: PathBuf,
pub cmake: Option<PathBuf>,
pub cmake_toolchain_file: Option<PathBuf>,
}
const ENV_CANDIDATES: &[&str] = &["OHOS_SDK_NATIVE", "OHOS_NDK_HOME", "OHOS_SDK_HOME"];
impl Sdk {
pub fn discover(explicit: Option<&Path>) -> Result<Self, Error> {
let mut tried = Vec::new();
if let Some(p) = explicit {
return Self::from_candidate(p).ok_or_else(|| Error::SdkNotFound {
tried: vec![p.display().to_string()],
});
}
for var in ENV_CANDIDATES {
let Some(value) = std::env::var_os(var) else {
continue;
};
let path = PathBuf::from(value);
if let Some(sdk) = Self::from_candidate(&path) {
return Ok(sdk);
}
tried.push(format!("${var} = {}", path.display()));
}
if tried.is_empty() {
tried.push(format!("none of ${} are set", ENV_CANDIDATES.join(", $")));
}
Err(Error::SdkNotFound { tried })
}
fn from_candidate(path: &Path) -> Option<Self> {
if let Some(sdk) = Self::load(path) {
return Some(sdk);
}
if let Some(sdk) = Self::load(&path.join("native")) {
return Some(sdk);
}
Self::highest_api_level(path).and_then(|p| Self::load(&p))
}
fn highest_api_level(root: &Path) -> Option<PathBuf> {
let mut best: Option<(u32, PathBuf)> = None;
let mut default = None;
for entry in std::fs::read_dir(root).ok()? {
let entry = entry.ok()?;
let name = entry.file_name();
let name = name.to_string_lossy();
let candidate = entry.path().join("native");
if !candidate.is_dir() {
continue;
}
if name == "default" {
default = Some(candidate);
} else if let Ok(level) = name.parse::<u32>() {
if best.as_ref().is_none_or(|(b, _)| level > *b) {
best = Some((level, candidate));
}
}
}
best.map(|(_, p)| p).or(default)
}
fn load(native_root: &Path) -> Option<Self> {
let native_root = native_root.canonicalize().ok()?;
let llvm_root = native_root.join("llvm");
let llvm_bin = llvm_root.join("bin");
let sysroot = native_root.join("sysroot");
if !llvm_bin.is_dir() || !sysroot.is_dir() {
return None;
}
let cmake = exe(&native_root
.join("build-tools")
.join("cmake")
.join("bin")
.join("cmake"));
let cmake_toolchain_file = native_root
.join("build")
.join("cmake")
.join("ohos.toolchain.cmake");
Some(Self {
sysroot,
llvm_bin,
llvm_root,
cmake,
cmake_toolchain_file: cmake_toolchain_file
.is_file()
.then_some(cmake_toolchain_file),
native_root,
})
}
}
fn exe(path: &Path) -> Option<PathBuf> {
let with_ext = if cfg!(windows) {
path.with_extension("exe")
} else {
path.to_path_buf()
};
with_ext.is_file().then_some(with_ext)
}