16cdb10c1Sopenharmony_ci// SPDX-License-Identifier: Apache-2.0 26cdb10c1Sopenharmony_ci 36cdb10c1Sopenharmony_ci//! Provides helper functionality. 46cdb10c1Sopenharmony_ci 56cdb10c1Sopenharmony_ciuse std::path::{Path, PathBuf}; 66cdb10c1Sopenharmony_ciuse std::process::Command; 76cdb10c1Sopenharmony_ciuse std::{env, io}; 86cdb10c1Sopenharmony_ci 96cdb10c1Sopenharmony_ciuse glob::{self, Pattern}; 106cdb10c1Sopenharmony_ci 116cdb10c1Sopenharmony_ciuse libc::c_int; 126cdb10c1Sopenharmony_ci 136cdb10c1Sopenharmony_ciuse super::CXVersion; 146cdb10c1Sopenharmony_ci 156cdb10c1Sopenharmony_ci//================================================ 166cdb10c1Sopenharmony_ci// Structs 176cdb10c1Sopenharmony_ci//================================================ 186cdb10c1Sopenharmony_ci 196cdb10c1Sopenharmony_ci/// A `clang` executable. 206cdb10c1Sopenharmony_ci#[derive(Clone, Debug)] 216cdb10c1Sopenharmony_cipub struct Clang { 226cdb10c1Sopenharmony_ci /// The path to this `clang` executable. 236cdb10c1Sopenharmony_ci pub path: PathBuf, 246cdb10c1Sopenharmony_ci /// The version of this `clang` executable if it could be parsed. 256cdb10c1Sopenharmony_ci pub version: Option<CXVersion>, 266cdb10c1Sopenharmony_ci /// The directories searched by this `clang` executable for C headers if 276cdb10c1Sopenharmony_ci /// they could be parsed. 286cdb10c1Sopenharmony_ci pub c_search_paths: Option<Vec<PathBuf>>, 296cdb10c1Sopenharmony_ci /// The directories searched by this `clang` executable for C++ headers if 306cdb10c1Sopenharmony_ci /// they could be parsed. 316cdb10c1Sopenharmony_ci pub cpp_search_paths: Option<Vec<PathBuf>>, 326cdb10c1Sopenharmony_ci} 336cdb10c1Sopenharmony_ci 346cdb10c1Sopenharmony_ciimpl Clang { 356cdb10c1Sopenharmony_ci fn new(path: impl AsRef<Path>, args: &[String]) -> Self { 366cdb10c1Sopenharmony_ci Self { 376cdb10c1Sopenharmony_ci path: path.as_ref().into(), 386cdb10c1Sopenharmony_ci version: parse_version(path.as_ref()), 396cdb10c1Sopenharmony_ci c_search_paths: parse_search_paths(path.as_ref(), "c", args), 406cdb10c1Sopenharmony_ci cpp_search_paths: parse_search_paths(path.as_ref(), "c++", args), 416cdb10c1Sopenharmony_ci } 426cdb10c1Sopenharmony_ci } 436cdb10c1Sopenharmony_ci 446cdb10c1Sopenharmony_ci /// Returns a `clang` executable if one can be found. 456cdb10c1Sopenharmony_ci /// 466cdb10c1Sopenharmony_ci /// If the `CLANG_PATH` environment variable is set, that is the instance of 476cdb10c1Sopenharmony_ci /// `clang` used. Otherwise, a series of directories are searched. First, if 486cdb10c1Sopenharmony_ci /// a path is supplied, that is the first directory searched. Then, the 496cdb10c1Sopenharmony_ci /// directory returned by `llvm-config --bindir` is searched. On macOS 506cdb10c1Sopenharmony_ci /// systems, `xcodebuild -find clang` will next be queried. Last, the 516cdb10c1Sopenharmony_ci /// directories in the system's `PATH` are searched. 526cdb10c1Sopenharmony_ci /// 536cdb10c1Sopenharmony_ci /// ## Cross-compilation 546cdb10c1Sopenharmony_ci /// 556cdb10c1Sopenharmony_ci /// If target arguments are provided (e.g., `-target` followed by a target 566cdb10c1Sopenharmony_ci /// like `x86_64-unknown-linux-gnu`) then this method will prefer a 576cdb10c1Sopenharmony_ci /// target-prefixed instance of `clang` (e.g., 586cdb10c1Sopenharmony_ci /// `x86_64-unknown-linux-gnu-clang` for the above example). 596cdb10c1Sopenharmony_ci pub fn find(path: Option<&Path>, args: &[String]) -> Option<Clang> { 606cdb10c1Sopenharmony_ci if let Ok(path) = env::var("CLANG_PATH") { 616cdb10c1Sopenharmony_ci let p = Path::new(&path); 626cdb10c1Sopenharmony_ci if p.is_file() && is_executable(&p).unwrap_or(false) { 636cdb10c1Sopenharmony_ci return Some(Clang::new(p, args)); 646cdb10c1Sopenharmony_ci } 656cdb10c1Sopenharmony_ci } 666cdb10c1Sopenharmony_ci 676cdb10c1Sopenharmony_ci // Determine the cross-compilation target, if any. 686cdb10c1Sopenharmony_ci 696cdb10c1Sopenharmony_ci let mut target = None; 706cdb10c1Sopenharmony_ci for i in 0..args.len() { 716cdb10c1Sopenharmony_ci if args[i] == "-target" && i + 1 < args.len() { 726cdb10c1Sopenharmony_ci target = Some(&args[i + 1]); 736cdb10c1Sopenharmony_ci } 746cdb10c1Sopenharmony_ci } 756cdb10c1Sopenharmony_ci 766cdb10c1Sopenharmony_ci // Collect the paths to search for a `clang` executable in. 776cdb10c1Sopenharmony_ci 786cdb10c1Sopenharmony_ci let mut paths = vec![]; 796cdb10c1Sopenharmony_ci 806cdb10c1Sopenharmony_ci if let Some(path) = path { 816cdb10c1Sopenharmony_ci paths.push(path.into()); 826cdb10c1Sopenharmony_ci } 836cdb10c1Sopenharmony_ci 846cdb10c1Sopenharmony_ci if let Ok(path) = run_llvm_config(&["--bindir"]) { 856cdb10c1Sopenharmony_ci if let Some(line) = path.lines().next() { 866cdb10c1Sopenharmony_ci paths.push(line.into()); 876cdb10c1Sopenharmony_ci } 886cdb10c1Sopenharmony_ci } 896cdb10c1Sopenharmony_ci 906cdb10c1Sopenharmony_ci if cfg!(target_os = "macos") { 916cdb10c1Sopenharmony_ci if let Ok((path, _)) = run("xcodebuild", &["-find", "clang"]) { 926cdb10c1Sopenharmony_ci if let Some(line) = path.lines().next() { 936cdb10c1Sopenharmony_ci paths.push(line.into()); 946cdb10c1Sopenharmony_ci } 956cdb10c1Sopenharmony_ci } 966cdb10c1Sopenharmony_ci } 976cdb10c1Sopenharmony_ci 986cdb10c1Sopenharmony_ci if let Ok(path) = env::var("PATH") { 996cdb10c1Sopenharmony_ci paths.extend(env::split_paths(&path)); 1006cdb10c1Sopenharmony_ci } 1016cdb10c1Sopenharmony_ci 1026cdb10c1Sopenharmony_ci // First, look for a target-prefixed `clang` executable. 1036cdb10c1Sopenharmony_ci 1046cdb10c1Sopenharmony_ci if let Some(target) = target { 1056cdb10c1Sopenharmony_ci let default = format!("{}-clang{}", target, env::consts::EXE_SUFFIX); 1066cdb10c1Sopenharmony_ci let versioned = format!("{}-clang-[0-9]*{}", target, env::consts::EXE_SUFFIX); 1076cdb10c1Sopenharmony_ci let patterns = &[&default[..], &versioned[..]]; 1086cdb10c1Sopenharmony_ci for path in &paths { 1096cdb10c1Sopenharmony_ci if let Some(path) = find(path, patterns) { 1106cdb10c1Sopenharmony_ci return Some(Clang::new(path, args)); 1116cdb10c1Sopenharmony_ci } 1126cdb10c1Sopenharmony_ci } 1136cdb10c1Sopenharmony_ci } 1146cdb10c1Sopenharmony_ci 1156cdb10c1Sopenharmony_ci // Otherwise, look for any other `clang` executable. 1166cdb10c1Sopenharmony_ci 1176cdb10c1Sopenharmony_ci let default = format!("clang{}", env::consts::EXE_SUFFIX); 1186cdb10c1Sopenharmony_ci let versioned = format!("clang-[0-9]*{}", env::consts::EXE_SUFFIX); 1196cdb10c1Sopenharmony_ci let patterns = &[&default[..], &versioned[..]]; 1206cdb10c1Sopenharmony_ci for path in paths { 1216cdb10c1Sopenharmony_ci if let Some(path) = find(&path, patterns) { 1226cdb10c1Sopenharmony_ci return Some(Clang::new(path, args)); 1236cdb10c1Sopenharmony_ci } 1246cdb10c1Sopenharmony_ci } 1256cdb10c1Sopenharmony_ci 1266cdb10c1Sopenharmony_ci None 1276cdb10c1Sopenharmony_ci } 1286cdb10c1Sopenharmony_ci} 1296cdb10c1Sopenharmony_ci 1306cdb10c1Sopenharmony_ci//================================================ 1316cdb10c1Sopenharmony_ci// Functions 1326cdb10c1Sopenharmony_ci//================================================ 1336cdb10c1Sopenharmony_ci 1346cdb10c1Sopenharmony_ci/// Returns the first match to the supplied glob patterns in the supplied 1356cdb10c1Sopenharmony_ci/// directory if there are any matches. 1366cdb10c1Sopenharmony_cifn find(directory: &Path, patterns: &[&str]) -> Option<PathBuf> { 1376cdb10c1Sopenharmony_ci // Escape the directory in case it contains characters that have special 1386cdb10c1Sopenharmony_ci // meaning in glob patterns (e.g., `[` or `]`). 1396cdb10c1Sopenharmony_ci let directory = if let Some(directory) = directory.to_str() { 1406cdb10c1Sopenharmony_ci Path::new(&Pattern::escape(directory)).to_owned() 1416cdb10c1Sopenharmony_ci } else { 1426cdb10c1Sopenharmony_ci return None; 1436cdb10c1Sopenharmony_ci }; 1446cdb10c1Sopenharmony_ci 1456cdb10c1Sopenharmony_ci for pattern in patterns { 1466cdb10c1Sopenharmony_ci let pattern = directory.join(pattern).to_string_lossy().into_owned(); 1476cdb10c1Sopenharmony_ci if let Some(path) = glob::glob(&pattern).ok()?.filter_map(|p| p.ok()).next() { 1486cdb10c1Sopenharmony_ci if path.is_file() && is_executable(&path).unwrap_or(false) { 1496cdb10c1Sopenharmony_ci return Some(path); 1506cdb10c1Sopenharmony_ci } 1516cdb10c1Sopenharmony_ci } 1526cdb10c1Sopenharmony_ci } 1536cdb10c1Sopenharmony_ci 1546cdb10c1Sopenharmony_ci None 1556cdb10c1Sopenharmony_ci} 1566cdb10c1Sopenharmony_ci 1576cdb10c1Sopenharmony_ci#[cfg(unix)] 1586cdb10c1Sopenharmony_cifn is_executable(path: &Path) -> io::Result<bool> { 1596cdb10c1Sopenharmony_ci use std::ffi::CString; 1606cdb10c1Sopenharmony_ci use std::os::unix::ffi::OsStrExt; 1616cdb10c1Sopenharmony_ci 1626cdb10c1Sopenharmony_ci let path = CString::new(path.as_os_str().as_bytes())?; 1636cdb10c1Sopenharmony_ci unsafe { Ok(libc::access(path.as_ptr(), libc::X_OK) == 0) } 1646cdb10c1Sopenharmony_ci} 1656cdb10c1Sopenharmony_ci 1666cdb10c1Sopenharmony_ci#[cfg(not(unix))] 1676cdb10c1Sopenharmony_cifn is_executable(_: &Path) -> io::Result<bool> { 1686cdb10c1Sopenharmony_ci Ok(true) 1696cdb10c1Sopenharmony_ci} 1706cdb10c1Sopenharmony_ci 1716cdb10c1Sopenharmony_ci/// Attempts to run an executable, returning the `stdout` and `stderr` output if 1726cdb10c1Sopenharmony_ci/// successful. 1736cdb10c1Sopenharmony_cifn run(executable: &str, arguments: &[&str]) -> Result<(String, String), String> { 1746cdb10c1Sopenharmony_ci Command::new(executable) 1756cdb10c1Sopenharmony_ci .args(arguments) 1766cdb10c1Sopenharmony_ci .output() 1776cdb10c1Sopenharmony_ci .map(|o| { 1786cdb10c1Sopenharmony_ci let stdout = String::from_utf8_lossy(&o.stdout).into_owned(); 1796cdb10c1Sopenharmony_ci let stderr = String::from_utf8_lossy(&o.stderr).into_owned(); 1806cdb10c1Sopenharmony_ci (stdout, stderr) 1816cdb10c1Sopenharmony_ci }) 1826cdb10c1Sopenharmony_ci .map_err(|e| format!("could not run executable `{}`: {}", executable, e)) 1836cdb10c1Sopenharmony_ci} 1846cdb10c1Sopenharmony_ci 1856cdb10c1Sopenharmony_ci/// Runs `clang`, returning the `stdout` and `stderr` output. 1866cdb10c1Sopenharmony_cifn run_clang(path: &Path, arguments: &[&str]) -> (String, String) { 1876cdb10c1Sopenharmony_ci run(&path.to_string_lossy().into_owned(), arguments).unwrap() 1886cdb10c1Sopenharmony_ci} 1896cdb10c1Sopenharmony_ci 1906cdb10c1Sopenharmony_ci/// Runs `llvm-config`, returning the `stdout` output if successful. 1916cdb10c1Sopenharmony_cifn run_llvm_config(arguments: &[&str]) -> Result<String, String> { 1926cdb10c1Sopenharmony_ci let config = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".to_string()); 1936cdb10c1Sopenharmony_ci run(&config, arguments).map(|(o, _)| o) 1946cdb10c1Sopenharmony_ci} 1956cdb10c1Sopenharmony_ci 1966cdb10c1Sopenharmony_ci/// Parses a version number if possible, ignoring trailing non-digit characters. 1976cdb10c1Sopenharmony_cifn parse_version_number(number: &str) -> Option<c_int> { 1986cdb10c1Sopenharmony_ci number 1996cdb10c1Sopenharmony_ci .chars() 2006cdb10c1Sopenharmony_ci .take_while(|c| c.is_digit(10)) 2016cdb10c1Sopenharmony_ci .collect::<String>() 2026cdb10c1Sopenharmony_ci .parse() 2036cdb10c1Sopenharmony_ci .ok() 2046cdb10c1Sopenharmony_ci} 2056cdb10c1Sopenharmony_ci 2066cdb10c1Sopenharmony_ci/// Parses the version from the output of a `clang` executable if possible. 2076cdb10c1Sopenharmony_cifn parse_version(path: &Path) -> Option<CXVersion> { 2086cdb10c1Sopenharmony_ci let output = run_clang(path, &["--version"]).0; 2096cdb10c1Sopenharmony_ci let start = output.find("version ")? + 8; 2106cdb10c1Sopenharmony_ci let mut numbers = output[start..].split_whitespace().next()?.split('.'); 2116cdb10c1Sopenharmony_ci let major = numbers.next().and_then(parse_version_number)?; 2126cdb10c1Sopenharmony_ci let minor = numbers.next().and_then(parse_version_number)?; 2136cdb10c1Sopenharmony_ci let subminor = numbers.next().and_then(parse_version_number).unwrap_or(0); 2146cdb10c1Sopenharmony_ci Some(CXVersion { 2156cdb10c1Sopenharmony_ci Major: major, 2166cdb10c1Sopenharmony_ci Minor: minor, 2176cdb10c1Sopenharmony_ci Subminor: subminor, 2186cdb10c1Sopenharmony_ci }) 2196cdb10c1Sopenharmony_ci} 2206cdb10c1Sopenharmony_ci 2216cdb10c1Sopenharmony_ci/// Parses the search paths from the output of a `clang` executable if possible. 2226cdb10c1Sopenharmony_cifn parse_search_paths(path: &Path, language: &str, args: &[String]) -> Option<Vec<PathBuf>> { 2236cdb10c1Sopenharmony_ci let mut clang_args = vec!["-E", "-x", language, "-", "-v"]; 2246cdb10c1Sopenharmony_ci clang_args.extend(args.iter().map(|s| &**s)); 2256cdb10c1Sopenharmony_ci let output = run_clang(path, &clang_args).1; 2266cdb10c1Sopenharmony_ci let start = output.find("#include <...> search starts here:")? + 34; 2276cdb10c1Sopenharmony_ci let end = output.find("End of search list.")?; 2286cdb10c1Sopenharmony_ci let paths = output[start..end].replace("(framework directory)", ""); 2296cdb10c1Sopenharmony_ci Some( 2306cdb10c1Sopenharmony_ci paths 2316cdb10c1Sopenharmony_ci .lines() 2326cdb10c1Sopenharmony_ci .filter(|l| !l.is_empty()) 2336cdb10c1Sopenharmony_ci .map(|l| Path::new(l.trim()).into()) 2346cdb10c1Sopenharmony_ci .collect(), 2356cdb10c1Sopenharmony_ci ) 2366cdb10c1Sopenharmony_ci} 237