use std::env;
use std::error::Error;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
fn main() {
println!("cargo:rerun-if-env-changed=SPEC_AI_TIKA_CACHE_DIR");
println!("cargo:rerun-if-env-changed=CARGO_INSTALL_ROOT");
println!("cargo:rerun-if-env-changed=CARGO_HOME");
println!("cargo:rerun-if-env-changed=HOME");
if env::var("DOCS_RS").is_ok() {
return;
}
match persist_extractous_libs() {
Ok(Some(dir)) => {
let dir_str = dir.display().to_string();
println!("cargo:rustc-env=SPEC_AI_EXTRACTOUS_LIB_DIR={dir_str}");
let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap_or_default();
if target_family != "windows" {
println!("cargo:rustc-link-arg=-Wl,-rpath,{dir_str}");
}
}
Ok(None) => {}
Err(err) => {
println!("cargo:warning=Failed to persist extractous native libs: {err}");
}
}
}
fn persist_extractous_libs() -> Result<Option<PathBuf>, Box<dyn Error>> {
let libs_dir = match locate_extractous_libs()? {
Some(path) => path,
None => return Ok(None),
};
let target = env::var("TARGET")?;
let destination = determine_cache_dir(&target)?;
copy_directory(&libs_dir, &destination)?;
Ok(Some(destination))
}
fn locate_extractous_libs() -> Result<Option<PathBuf>, Box<dyn Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let build_root = out_dir
.parent()
.and_then(|p| p.parent())
.ok_or("unable to determine build directory for spec-ai-cli")?;
let mut newest: Option<(SystemTime, PathBuf)> = None;
for entry in fs::read_dir(build_root)? {
let entry = match entry {
Ok(value) => value,
Err(_) => continue,
};
let name = match entry.file_name().into_string() {
Ok(name) => name,
Err(_) => continue,
};
if !name.starts_with("extractous-") {
continue;
}
let candidate = entry.path().join("out").join("libs");
if !candidate.exists() {
continue;
}
let modified = fs::metadata(&candidate)
.and_then(|meta| meta.modified())
.unwrap_or(UNIX_EPOCH);
match &mut newest {
Some((current, path)) => {
if modified > *current {
*current = modified;
*path = candidate;
}
}
None => newest = Some((modified, candidate)),
}
}
Ok(newest.map(|(_, path)| path))
}
fn determine_cache_dir(target: &str) -> Result<PathBuf, Box<dyn Error>> {
let base = if let Ok(custom) = env::var("SPEC_AI_TIKA_CACHE_DIR") {
PathBuf::from(custom)
} else {
default_cache_base().ok_or("SPEC_AI_TIKA_CACHE_DIR, CARGO_INSTALL_ROOT, CARGO_HOME, or HOME must be set")?
};
let version = env::var("CARGO_PKG_VERSION")?;
Ok(base.join(target).join(version))
}
fn default_cache_base() -> Option<PathBuf> {
if let Ok(root) = env::var("CARGO_INSTALL_ROOT") {
return Some(PathBuf::from(root).join("lib").join("spec-ai").join("extractous"));
}
if let Ok(home) = env::var("CARGO_HOME") {
return Some(PathBuf::from(home).join("lib").join("spec-ai").join("extractous"));
}
env::var("HOME")
.map(|home| PathBuf::from(home).join(".spec-ai").join("extractous"))
.ok()
}
fn copy_directory(src: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
if dest.exists() {
fs::remove_dir_all(dest)?;
}
fs::create_dir_all(dest)?;
copy_dir_recursive(src, dest)?;
Ok(())
}
fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let entry_path = entry.path();
let dest_path = dest.join(entry.file_name());
if file_type.is_dir() {
fs::create_dir_all(&dest_path)?;
copy_dir_recursive(&entry_path, &dest_path)?;
} else if file_type.is_file() {
fs::copy(&entry_path, &dest_path)?;
}
}
Ok(())
}