16cdb10c1Sopenharmony_ci// SPDX-License-Identifier: Apache-2.0 26cdb10c1Sopenharmony_ci 36cdb10c1Sopenharmony_ciextern crate glob; 46cdb10c1Sopenharmony_ci 56cdb10c1Sopenharmony_ciuse std::cell::RefCell; 66cdb10c1Sopenharmony_ciuse std::collections::HashMap; 76cdb10c1Sopenharmony_ciuse std::env; 86cdb10c1Sopenharmony_ciuse std::path::{Path, PathBuf}; 96cdb10c1Sopenharmony_ciuse std::process::Command; 106cdb10c1Sopenharmony_ci 116cdb10c1Sopenharmony_ciuse glob::{MatchOptions, Pattern}; 126cdb10c1Sopenharmony_ci 136cdb10c1Sopenharmony_ci//================================================ 146cdb10c1Sopenharmony_ci// Commands 156cdb10c1Sopenharmony_ci//================================================ 166cdb10c1Sopenharmony_ci 176cdb10c1Sopenharmony_cithread_local! { 186cdb10c1Sopenharmony_ci /// The errors encountered by the build script while executing commands. 196cdb10c1Sopenharmony_ci static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); 206cdb10c1Sopenharmony_ci} 216cdb10c1Sopenharmony_ci 226cdb10c1Sopenharmony_ci/// Adds an error encountered by the build script while executing a command. 236cdb10c1Sopenharmony_cifn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) { 246cdb10c1Sopenharmony_ci COMMAND_ERRORS.with(|e| { 256cdb10c1Sopenharmony_ci e.borrow_mut() 266cdb10c1Sopenharmony_ci .entry(name.into()) 276cdb10c1Sopenharmony_ci .or_insert_with(Vec::new) 286cdb10c1Sopenharmony_ci .push(format!( 296cdb10c1Sopenharmony_ci "couldn't execute `{} {}` (path={}) ({})", 306cdb10c1Sopenharmony_ci name, 316cdb10c1Sopenharmony_ci arguments.join(" "), 326cdb10c1Sopenharmony_ci path, 336cdb10c1Sopenharmony_ci message, 346cdb10c1Sopenharmony_ci )) 356cdb10c1Sopenharmony_ci }); 366cdb10c1Sopenharmony_ci} 376cdb10c1Sopenharmony_ci 386cdb10c1Sopenharmony_ci/// A struct that prints the errors encountered by the build script while 396cdb10c1Sopenharmony_ci/// executing commands when dropped (unless explictly discarded). 406cdb10c1Sopenharmony_ci/// 416cdb10c1Sopenharmony_ci/// This is handy because we only want to print these errors when the build 426cdb10c1Sopenharmony_ci/// script fails to link to an instance of `libclang`. For example, if 436cdb10c1Sopenharmony_ci/// `llvm-config` couldn't be executed but an instance of `libclang` was found 446cdb10c1Sopenharmony_ci/// anyway we don't want to pollute the build output with irrelevant errors. 456cdb10c1Sopenharmony_ci#[derive(Default)] 466cdb10c1Sopenharmony_cipub struct CommandErrorPrinter { 476cdb10c1Sopenharmony_ci discard: bool, 486cdb10c1Sopenharmony_ci} 496cdb10c1Sopenharmony_ci 506cdb10c1Sopenharmony_ciimpl CommandErrorPrinter { 516cdb10c1Sopenharmony_ci pub fn discard(mut self) { 526cdb10c1Sopenharmony_ci self.discard = true; 536cdb10c1Sopenharmony_ci } 546cdb10c1Sopenharmony_ci} 556cdb10c1Sopenharmony_ci 566cdb10c1Sopenharmony_ciimpl Drop for CommandErrorPrinter { 576cdb10c1Sopenharmony_ci fn drop(&mut self) { 586cdb10c1Sopenharmony_ci if self.discard { 596cdb10c1Sopenharmony_ci return; 606cdb10c1Sopenharmony_ci } 616cdb10c1Sopenharmony_ci 626cdb10c1Sopenharmony_ci let errors = COMMAND_ERRORS.with(|e| e.borrow().clone()); 636cdb10c1Sopenharmony_ci 646cdb10c1Sopenharmony_ci if let Some(errors) = errors.get("llvm-config") { 656cdb10c1Sopenharmony_ci println!( 666cdb10c1Sopenharmony_ci "cargo:warning=could not execute `llvm-config` one or more \ 676cdb10c1Sopenharmony_ci times, if the LLVM_CONFIG_PATH environment variable is set to \ 686cdb10c1Sopenharmony_ci a full path to valid `llvm-config` executable it will be used \ 696cdb10c1Sopenharmony_ci to try to find an instance of `libclang` on your system: {}", 706cdb10c1Sopenharmony_ci errors 716cdb10c1Sopenharmony_ci .iter() 726cdb10c1Sopenharmony_ci .map(|e| format!("\"{}\"", e)) 736cdb10c1Sopenharmony_ci .collect::<Vec<_>>() 746cdb10c1Sopenharmony_ci .join("\n "), 756cdb10c1Sopenharmony_ci ) 766cdb10c1Sopenharmony_ci } 776cdb10c1Sopenharmony_ci 786cdb10c1Sopenharmony_ci if let Some(errors) = errors.get("xcode-select") { 796cdb10c1Sopenharmony_ci println!( 806cdb10c1Sopenharmony_ci "cargo:warning=could not execute `xcode-select` one or more \ 816cdb10c1Sopenharmony_ci times, if a valid instance of this executable is on your PATH \ 826cdb10c1Sopenharmony_ci it will be used to try to find an instance of `libclang` on \ 836cdb10c1Sopenharmony_ci your system: {}", 846cdb10c1Sopenharmony_ci errors 856cdb10c1Sopenharmony_ci .iter() 866cdb10c1Sopenharmony_ci .map(|e| format!("\"{}\"", e)) 876cdb10c1Sopenharmony_ci .collect::<Vec<_>>() 886cdb10c1Sopenharmony_ci .join("\n "), 896cdb10c1Sopenharmony_ci ) 906cdb10c1Sopenharmony_ci } 916cdb10c1Sopenharmony_ci } 926cdb10c1Sopenharmony_ci} 936cdb10c1Sopenharmony_ci 946cdb10c1Sopenharmony_ci/// Executes a command and returns the `stdout` output if the command was 956cdb10c1Sopenharmony_ci/// successfully executed (errors are added to `COMMAND_ERRORS`). 966cdb10c1Sopenharmony_cifn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> { 976cdb10c1Sopenharmony_ci let output = match Command::new(path).args(arguments).output() { 986cdb10c1Sopenharmony_ci Ok(output) => output, 996cdb10c1Sopenharmony_ci Err(error) => { 1006cdb10c1Sopenharmony_ci let message = format!("error: {}", error); 1016cdb10c1Sopenharmony_ci add_command_error(name, path, arguments, message); 1026cdb10c1Sopenharmony_ci return None; 1036cdb10c1Sopenharmony_ci } 1046cdb10c1Sopenharmony_ci }; 1056cdb10c1Sopenharmony_ci 1066cdb10c1Sopenharmony_ci if output.status.success() { 1076cdb10c1Sopenharmony_ci Some(String::from_utf8_lossy(&output.stdout).into_owned()) 1086cdb10c1Sopenharmony_ci } else { 1096cdb10c1Sopenharmony_ci let message = format!("exit code: {}", output.status); 1106cdb10c1Sopenharmony_ci add_command_error(name, path, arguments, message); 1116cdb10c1Sopenharmony_ci None 1126cdb10c1Sopenharmony_ci } 1136cdb10c1Sopenharmony_ci} 1146cdb10c1Sopenharmony_ci 1156cdb10c1Sopenharmony_ci/// Executes the `llvm-config` command and returns the `stdout` output if the 1166cdb10c1Sopenharmony_ci/// command was successfully executed (errors are added to `COMMAND_ERRORS`). 1176cdb10c1Sopenharmony_cipub fn run_llvm_config(arguments: &[&str]) -> Option<String> { 1186cdb10c1Sopenharmony_ci let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); 1196cdb10c1Sopenharmony_ci run_command("llvm-config", &path, arguments) 1206cdb10c1Sopenharmony_ci} 1216cdb10c1Sopenharmony_ci 1226cdb10c1Sopenharmony_ci/// Executes the `xcode-select` command and returns the `stdout` output if the 1236cdb10c1Sopenharmony_ci/// command was successfully executed (errors are added to `COMMAND_ERRORS`). 1246cdb10c1Sopenharmony_cipub fn run_xcode_select(arguments: &[&str]) -> Option<String> { 1256cdb10c1Sopenharmony_ci run_command("xcode-select", "xcode-select", arguments) 1266cdb10c1Sopenharmony_ci} 1276cdb10c1Sopenharmony_ci 1286cdb10c1Sopenharmony_ci//================================================ 1296cdb10c1Sopenharmony_ci// Search Directories 1306cdb10c1Sopenharmony_ci//================================================ 1316cdb10c1Sopenharmony_ci 1326cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Haiku. 1336cdb10c1Sopenharmony_ciconst DIRECTORIES_HAIKU: &[&str] = &[ 1346cdb10c1Sopenharmony_ci "/boot/system/lib", 1356cdb10c1Sopenharmony_ci "/boot/system/develop/lib", 1366cdb10c1Sopenharmony_ci "/boot/system/non-packaged/lib", 1376cdb10c1Sopenharmony_ci "/boot/system/non-packaged/develop/lib", 1386cdb10c1Sopenharmony_ci "/boot/home/config/non-packaged/lib", 1396cdb10c1Sopenharmony_ci "/boot/home/config/non-packaged/develop/lib", 1406cdb10c1Sopenharmony_ci]; 1416cdb10c1Sopenharmony_ci 1426cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Linux (and FreeBSD). 1436cdb10c1Sopenharmony_ciconst DIRECTORIES_LINUX: &[&str] = &[ 1446cdb10c1Sopenharmony_ci "/usr/lib*", 1456cdb10c1Sopenharmony_ci "/usr/lib*/*", 1466cdb10c1Sopenharmony_ci "/usr/lib*/*/*", 1476cdb10c1Sopenharmony_ci "/usr/local/lib*", 1486cdb10c1Sopenharmony_ci "/usr/local/lib*/*", 1496cdb10c1Sopenharmony_ci "/usr/local/lib*/*/*", 1506cdb10c1Sopenharmony_ci "/usr/local/llvm*/lib*", 1516cdb10c1Sopenharmony_ci]; 1526cdb10c1Sopenharmony_ci 1536cdb10c1Sopenharmony_ci/// `libclang` directory patterns for macOS. 1546cdb10c1Sopenharmony_ciconst DIRECTORIES_MACOS: &[&str] = &[ 1556cdb10c1Sopenharmony_ci "/usr/local/opt/llvm*/lib", 1566cdb10c1Sopenharmony_ci "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", 1576cdb10c1Sopenharmony_ci "/Library/Developer/CommandLineTools/usr/lib", 1586cdb10c1Sopenharmony_ci "/usr/local/opt/llvm*/lib/llvm*/lib", 1596cdb10c1Sopenharmony_ci]; 1606cdb10c1Sopenharmony_ci 1616cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Windows. 1626cdb10c1Sopenharmony_ciconst DIRECTORIES_WINDOWS: &[&str] = &[ 1636cdb10c1Sopenharmony_ci "C:\\LLVM\\lib", 1646cdb10c1Sopenharmony_ci "C:\\Program Files*\\LLVM\\lib", 1656cdb10c1Sopenharmony_ci "C:\\MSYS*\\MinGW*\\lib", 1666cdb10c1Sopenharmony_ci // LLVM + Clang can be installed as a component of Visual Studio. 1676cdb10c1Sopenharmony_ci // https://github.com/KyleMayes/clang-sys/issues/121 1686cdb10c1Sopenharmony_ci "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", 1696cdb10c1Sopenharmony_ci // LLVM + Clang can be installed using Scoop (https://scoop.sh). 1706cdb10c1Sopenharmony_ci // Other Windows package managers install LLVM + Clang to previously listed 1716cdb10c1Sopenharmony_ci // system-wide directories. 1726cdb10c1Sopenharmony_ci "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin", 1736cdb10c1Sopenharmony_ci]; 1746cdb10c1Sopenharmony_ci 1756cdb10c1Sopenharmony_ci/// `libclang` directory patterns for illumos 1766cdb10c1Sopenharmony_ciconst DIRECTORIES_ILLUMOS: &[&str] = &[ 1776cdb10c1Sopenharmony_ci "/opt/ooce/clang-*/lib", 1786cdb10c1Sopenharmony_ci "/opt/ooce/llvm-*/lib", 1796cdb10c1Sopenharmony_ci]; 1806cdb10c1Sopenharmony_ci 1816cdb10c1Sopenharmony_ci//================================================ 1826cdb10c1Sopenharmony_ci// Searching 1836cdb10c1Sopenharmony_ci//================================================ 1846cdb10c1Sopenharmony_ci 1856cdb10c1Sopenharmony_ci/// Finds the files in a directory that match one or more filename glob patterns 1866cdb10c1Sopenharmony_ci/// and returns the paths to and filenames of those files. 1876cdb10c1Sopenharmony_cifn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { 1886cdb10c1Sopenharmony_ci // Escape the specified directory in case it contains characters that have 1896cdb10c1Sopenharmony_ci // special meaning in glob patterns (e.g., `[` or `]`). 1906cdb10c1Sopenharmony_ci let directory = Pattern::escape(directory.to_str().unwrap()); 1916cdb10c1Sopenharmony_ci let directory = Path::new(&directory); 1926cdb10c1Sopenharmony_ci 1936cdb10c1Sopenharmony_ci // Join the escaped directory to the filename glob patterns to obtain 1946cdb10c1Sopenharmony_ci // complete glob patterns for the files being searched for. 1956cdb10c1Sopenharmony_ci let paths = filenames 1966cdb10c1Sopenharmony_ci .iter() 1976cdb10c1Sopenharmony_ci .map(|f| directory.join(f).to_str().unwrap().to_owned()); 1986cdb10c1Sopenharmony_ci 1996cdb10c1Sopenharmony_ci // Prevent wildcards from matching path separators to ensure that the search 2006cdb10c1Sopenharmony_ci // is limited to the specified directory. 2016cdb10c1Sopenharmony_ci let mut options = MatchOptions::new(); 2026cdb10c1Sopenharmony_ci options.require_literal_separator = true; 2036cdb10c1Sopenharmony_ci 2046cdb10c1Sopenharmony_ci paths 2056cdb10c1Sopenharmony_ci .map(|p| glob::glob_with(&p, options)) 2066cdb10c1Sopenharmony_ci .filter_map(Result::ok) 2076cdb10c1Sopenharmony_ci .flatten() 2086cdb10c1Sopenharmony_ci .filter_map(|p| { 2096cdb10c1Sopenharmony_ci let path = p.ok()?; 2106cdb10c1Sopenharmony_ci let filename = path.file_name()?.to_str().unwrap(); 2116cdb10c1Sopenharmony_ci 2126cdb10c1Sopenharmony_ci // The `libclang_shared` library has been renamed to `libclang-cpp` 2136cdb10c1Sopenharmony_ci // in Clang 10. This can cause instances of this library (e.g., 2146cdb10c1Sopenharmony_ci // `libclang-cpp.so.10`) to be matched by patterns looking for 2156cdb10c1Sopenharmony_ci // instances of `libclang`. 2166cdb10c1Sopenharmony_ci if filename.contains("-cpp.") { 2176cdb10c1Sopenharmony_ci return None; 2186cdb10c1Sopenharmony_ci } 2196cdb10c1Sopenharmony_ci 2206cdb10c1Sopenharmony_ci Some((directory.to_owned(), filename.into())) 2216cdb10c1Sopenharmony_ci }) 2226cdb10c1Sopenharmony_ci .collect::<Vec<_>>() 2236cdb10c1Sopenharmony_ci} 2246cdb10c1Sopenharmony_ci 2256cdb10c1Sopenharmony_ci/// Finds the files in a directory (and any relevant sibling directories) that 2266cdb10c1Sopenharmony_ci/// match one or more filename glob patterns and returns the paths to and 2276cdb10c1Sopenharmony_ci/// filenames of those files. 2286cdb10c1Sopenharmony_cifn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { 2296cdb10c1Sopenharmony_ci let mut results = search_directory(directory, filenames); 2306cdb10c1Sopenharmony_ci 2316cdb10c1Sopenharmony_ci // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory 2326cdb10c1Sopenharmony_ci // while `libclang.lib` is usually found in the LLVM `lib` directory. To 2336cdb10c1Sopenharmony_ci // keep things consistent with other platforms, only LLVM `lib` directories 2346cdb10c1Sopenharmony_ci // are included in the backup search directory globs so we need to search 2356cdb10c1Sopenharmony_ci // the LLVM `bin` directory here. 2366cdb10c1Sopenharmony_ci if cfg!(target_os = "windows") && directory.ends_with("lib") { 2376cdb10c1Sopenharmony_ci let sibling = directory.parent().unwrap().join("bin"); 2386cdb10c1Sopenharmony_ci results.extend(search_directory(&sibling, filenames).into_iter()); 2396cdb10c1Sopenharmony_ci } 2406cdb10c1Sopenharmony_ci 2416cdb10c1Sopenharmony_ci results 2426cdb10c1Sopenharmony_ci} 2436cdb10c1Sopenharmony_ci 2446cdb10c1Sopenharmony_ci/// Finds the `libclang` static or dynamic libraries matching one or more 2456cdb10c1Sopenharmony_ci/// filename glob patterns and returns the paths to and filenames of those files. 2466cdb10c1Sopenharmony_cipub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> { 2476cdb10c1Sopenharmony_ci // Search only the path indicated by the relevant environment variable 2486cdb10c1Sopenharmony_ci // (e.g., `LIBCLANG_PATH`) if it is set. 2496cdb10c1Sopenharmony_ci if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { 2506cdb10c1Sopenharmony_ci // Check if the path is a matching file. 2516cdb10c1Sopenharmony_ci if let Some(parent) = path.parent() { 2526cdb10c1Sopenharmony_ci let filename = path.file_name().unwrap().to_str().unwrap(); 2536cdb10c1Sopenharmony_ci let libraries = search_directories(parent, filenames); 2546cdb10c1Sopenharmony_ci if libraries.iter().any(|(_, f)| f == filename) { 2556cdb10c1Sopenharmony_ci return vec![(parent.into(), filename.into())]; 2566cdb10c1Sopenharmony_ci } 2576cdb10c1Sopenharmony_ci } 2586cdb10c1Sopenharmony_ci 2596cdb10c1Sopenharmony_ci // Check if the path is directory containing a matching file. 2606cdb10c1Sopenharmony_ci return search_directories(&path, filenames); 2616cdb10c1Sopenharmony_ci } 2626cdb10c1Sopenharmony_ci 2636cdb10c1Sopenharmony_ci let mut found = vec![]; 2646cdb10c1Sopenharmony_ci 2656cdb10c1Sopenharmony_ci // Search the `bin` and `lib` directories in the directory returned by 2666cdb10c1Sopenharmony_ci // `llvm-config --prefix`. 2676cdb10c1Sopenharmony_ci if let Some(output) = run_llvm_config(&["--prefix"]) { 2686cdb10c1Sopenharmony_ci let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); 2696cdb10c1Sopenharmony_ci found.extend(search_directories(&directory.join("bin"), filenames)); 2706cdb10c1Sopenharmony_ci found.extend(search_directories(&directory.join("lib"), filenames)); 2716cdb10c1Sopenharmony_ci found.extend(search_directories(&directory.join("lib64"), filenames)); 2726cdb10c1Sopenharmony_ci } 2736cdb10c1Sopenharmony_ci 2746cdb10c1Sopenharmony_ci // Search the toolchain directory in the directory returned by 2756cdb10c1Sopenharmony_ci // `xcode-select --print-path`. 2766cdb10c1Sopenharmony_ci if cfg!(target_os = "macos") { 2776cdb10c1Sopenharmony_ci if let Some(output) = run_xcode_select(&["--print-path"]) { 2786cdb10c1Sopenharmony_ci let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); 2796cdb10c1Sopenharmony_ci let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); 2806cdb10c1Sopenharmony_ci found.extend(search_directories(&directory, filenames)); 2816cdb10c1Sopenharmony_ci } 2826cdb10c1Sopenharmony_ci } 2836cdb10c1Sopenharmony_ci 2846cdb10c1Sopenharmony_ci // Search the directories in the `LD_LIBRARY_PATH` environment variable. 2856cdb10c1Sopenharmony_ci if let Ok(path) = env::var("LD_LIBRARY_PATH") { 2866cdb10c1Sopenharmony_ci for directory in env::split_paths(&path) { 2876cdb10c1Sopenharmony_ci found.extend(search_directories(&directory, filenames)); 2886cdb10c1Sopenharmony_ci } 2896cdb10c1Sopenharmony_ci } 2906cdb10c1Sopenharmony_ci 2916cdb10c1Sopenharmony_ci // Determine the `libclang` directory patterns. 2926cdb10c1Sopenharmony_ci let directories = if cfg!(target_os = "haiku") { 2936cdb10c1Sopenharmony_ci DIRECTORIES_HAIKU 2946cdb10c1Sopenharmony_ci } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { 2956cdb10c1Sopenharmony_ci DIRECTORIES_LINUX 2966cdb10c1Sopenharmony_ci } else if cfg!(target_os = "macos") { 2976cdb10c1Sopenharmony_ci DIRECTORIES_MACOS 2986cdb10c1Sopenharmony_ci } else if cfg!(target_os = "windows") { 2996cdb10c1Sopenharmony_ci DIRECTORIES_WINDOWS 3006cdb10c1Sopenharmony_ci } else if cfg!(target_os = "illumos") { 3016cdb10c1Sopenharmony_ci DIRECTORIES_ILLUMOS 3026cdb10c1Sopenharmony_ci } else { 3036cdb10c1Sopenharmony_ci &[] 3046cdb10c1Sopenharmony_ci }; 3056cdb10c1Sopenharmony_ci 3066cdb10c1Sopenharmony_ci // Search the directories provided by the `libclang` directory patterns. 3076cdb10c1Sopenharmony_ci let mut options = MatchOptions::new(); 3086cdb10c1Sopenharmony_ci options.case_sensitive = false; 3096cdb10c1Sopenharmony_ci options.require_literal_separator = true; 3106cdb10c1Sopenharmony_ci for directory in directories.iter().rev() { 3116cdb10c1Sopenharmony_ci if let Ok(directories) = glob::glob_with(directory, options) { 3126cdb10c1Sopenharmony_ci for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { 3136cdb10c1Sopenharmony_ci found.extend(search_directories(&directory, filenames)); 3146cdb10c1Sopenharmony_ci } 3156cdb10c1Sopenharmony_ci } 3166cdb10c1Sopenharmony_ci } 3176cdb10c1Sopenharmony_ci 3186cdb10c1Sopenharmony_ci found 3196cdb10c1Sopenharmony_ci} 320