use super::target_env_tracked;
use bindgen::ClangVersion;
use clang_sys::support::Clang as ClangSys;
use std::{ffi::OsStr, path::PathBuf};
use walkdir::{DirEntry, WalkDir};
pub(crate) fn detect_include_paths_for(
preferred_clang: Option<&std::path::Path>,
) -> (bool, Vec<PathBuf>) {
if target_env_tracked("PGRX_BINDGEN_NO_DETECT_INCLUDES").is_some() {
return (false, vec![]);
}
let clang_major = match bindgen::clang_version() {
ClangVersion { parsed: Some((major, _)), full } => {
eprintln!("Bindgen found {full}");
major
}
ClangVersion { full, .. } => {
eprintln!("Bindgen failed to parse clang version: {full}");
return (true, vec![]);
}
};
if let Some(ClangSys { path, version: Some(v), c_search_paths, .. }) =
ClangSys::find(preferred_clang, &[])
&& Some(&*path) == preferred_clang
&& v.Major as u32 == clang_major
{
return (false, c_search_paths.unwrap_or_default());
}
let libclang_path =
clang_sys::get_library().expect("libclang should have been loaded?").path().to_owned();
eprintln!("found libclang at {}", libclang_path.display());
let clang_major_fmt = clang_major.to_string();
let mut paths = vec![];
for ancestor in libclang_path.ancestors() {
paths = WalkDir::new(ancestor)
.min_depth(1)
.max_depth(6)
.sort_by_file_name()
.into_iter()
.filter_entry(|entry| {
!is_hidden(entry) && {
entry_contains(entry, "clang")
|| entry_contains(entry, "include")
|| entry_contains(entry, &clang_major_fmt)
|| os_str_contains(entry.file_name(), "lib")
}
})
.filter_map(|e| e.ok()) .filter(|entry| {
entry_contains(entry, &clang_major_fmt)
&& entry_contains(entry, "clang")
&& entry_contains(entry, "include")
})
.filter(|entry| {
os_str_contains(entry.file_name(), "emmintrin.h")
|| os_str_contains(entry.file_name(), "arm_neon.h")
})
.filter_map(|entry| {
let mut pbuf = entry.into_path();
if pbuf.pop() && pbuf.is_dir() && os_str_contains(pbuf.file_name()?, "include") {
Some(pbuf)
} else {
None
}
})
.collect::<Vec<_>>();
if !paths.is_empty() {
paths.sort();
paths.dedup();
break;
}
}
let autodetect = paths.is_empty();
eprintln!("Found include dirs {paths:?}");
(autodetect, paths)
}
fn is_hidden(entry: &DirEntry) -> bool {
entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)
}
fn entry_contains(entry: &DirEntry, needle: &str) -> bool {
entry.path().components().any(|part| os_str_contains(part.as_os_str(), needle))
}
fn os_str_contains(os_s: &OsStr, needle: &str) -> bool {
os_s.to_str().filter(|part| part.contains(needle)).is_some()
}