1 | // SPDX-License-Identifier: Apache-2.0
|
2 |
|
3 | //! Provides helper functionality.
|
4 |
|
5 | use std::path::{Path, PathBuf};
|
6 | use std::process::Command;
|
7 | use std::{env, io};
|
8 |
|
9 | use glob::{self, Pattern};
|
10 |
|
11 | use libc::c_int;
|
12 |
|
13 | use super::CXVersion;
|
14 |
|
15 | //================================================
|
16 | // Structs
|
17 | //================================================
|
18 |
|
19 | /// A `clang` executable.
|
20 | #[derive (Clone, Debug)]
|
21 | pub struct Clang {
|
22 | /// The path to this `clang` executable.
|
23 | pub path: PathBuf,
|
24 | /// The version of this `clang` executable if it could be parsed.
|
25 | pub version: Option<CXVersion>,
|
26 | /// The directories searched by this `clang` executable for C headers if
|
27 | /// they could be parsed.
|
28 | pub c_search_paths: Option<Vec<PathBuf>>,
|
29 | /// The directories searched by this `clang` executable for C++ headers if
|
30 | /// they could be parsed.
|
31 | pub cpp_search_paths: Option<Vec<PathBuf>>,
|
32 | }
|
33 |
|
34 | impl Clang {
|
35 | fn new(path: impl AsRef<Path>, args: &[String]) -> Self {
|
36 | Self {
|
37 | path: path.as_ref().into(),
|
38 | version: parse_version(path.as_ref()),
|
39 | c_search_paths: parse_search_paths(path.as_ref(), "c" , args),
|
40 | cpp_search_paths: parse_search_paths(path.as_ref(), "c++" , args),
|
41 | }
|
42 | }
|
43 |
|
44 | /// Returns a `clang` executable if one can be found.
|
45 | ///
|
46 | /// If the `CLANG_PATH` environment variable is set, that is the instance of
|
47 | /// `clang` used. Otherwise, a series of directories are searched. First, if
|
48 | /// a path is supplied, that is the first directory searched. Then, the
|
49 | /// directory returned by `llvm-config --bindir` is searched. On macOS
|
50 | /// systems, `xcodebuild -find clang` will next be queried. Last, the
|
51 | /// directories in the system's `PATH` are searched.
|
52 | ///
|
53 | /// ## Cross-compilation
|
54 | ///
|
55 | /// If target arguments are provided (e.g., `--target` followed by a target
|
56 | /// like `x86_64-unknown-linux-gnu`) then this method will prefer a
|
57 | /// target-prefixed instance of `clang` (e.g.,
|
58 | /// `x86_64-unknown-linux-gnu-clang` for the above example).
|
59 | pub fn find(path: Option<&Path>, args: &[String]) -> Option<Clang> {
|
60 | if let Ok(path) = env::var("CLANG_PATH" ) {
|
61 | let p = Path::new(&path);
|
62 | if p.is_file() && is_executable(p).unwrap_or(false) {
|
63 | return Some(Clang::new(p, args));
|
64 | } else {
|
65 | eprintln!("`CLANG_PATH` env var set but is not a full path to an executable" );
|
66 | }
|
67 | }
|
68 |
|
69 | // Determine the cross-compilation target, if any.
|
70 |
|
71 | let mut target = None;
|
72 | for i in 0..args.len() {
|
73 | if (args[i] == "-target" || args[i] == "-target" ) && i + 1 < args.len() {
|
74 | target = Some(&args[i + 1]);
|
75 | }
|
76 | }
|
77 |
|
78 | // Collect the paths to search for a `clang` executable in.
|
79 |
|
80 | let mut paths = vec![];
|
81 |
|
82 | if let Some(path) = path {
|
83 | paths.push(path.into());
|
84 | }
|
85 |
|
86 | if let Ok(path) = run_llvm_config(&["--bindir" ]) {
|
87 | if let Some(line) = path.lines().next() {
|
88 | paths.push(line.into());
|
89 | }
|
90 | }
|
91 |
|
92 | if cfg!(target_os = "macos" ) {
|
93 | if let Ok((path, _)) = run("xcodebuild" , &["-find" , "clang" ]) {
|
94 | if let Some(line) = path.lines().next() {
|
95 | paths.push(line.into());
|
96 | }
|
97 | }
|
98 | }
|
99 |
|
100 | if let Ok(path) = env::var("PATH" ) {
|
101 | paths.extend(env::split_paths(&path));
|
102 | }
|
103 |
|
104 | // First, look for a target-prefixed `clang` executable.
|
105 |
|
106 | if let Some(target) = target {
|
107 | let default = format!(" {}-clang {}" , target, env::consts::EXE_SUFFIX);
|
108 | let versioned = format!(" {}-clang-[0-9]* {}" , target, env::consts::EXE_SUFFIX);
|
109 | let patterns = &[&default[..], &versioned[..]];
|
110 | for path in &paths {
|
111 | if let Some(path) = find(path, patterns) {
|
112 | return Some(Clang::new(path, args));
|
113 | }
|
114 | }
|
115 | }
|
116 |
|
117 | // Otherwise, look for any other `clang` executable.
|
118 |
|
119 | let default = format!("clang {}" , env::consts::EXE_SUFFIX);
|
120 | let versioned = format!("clang-[0-9]* {}" , env::consts::EXE_SUFFIX);
|
121 | let patterns = &[&default[..], &versioned[..]];
|
122 | for path in paths {
|
123 | if let Some(path) = find(&path, patterns) {
|
124 | return Some(Clang::new(path, args));
|
125 | }
|
126 | }
|
127 |
|
128 | None
|
129 | }
|
130 | }
|
131 |
|
132 | //================================================
|
133 | // Functions
|
134 | //================================================
|
135 |
|
136 | /// Returns the first match to the supplied glob patterns in the supplied
|
137 | /// directory if there are any matches.
|
138 | fn find(directory: &Path, patterns: &[&str]) -> Option<PathBuf> {
|
139 | // Escape the directory in case it contains characters that have special
|
140 | // meaning in glob patterns (e.g., `[` or `]`).
|
141 | let directory: PathBuf = if let Some(directory: &str) = directory.to_str() {
|
142 | Path::new(&Pattern::escape(directory)).to_owned()
|
143 | } else {
|
144 | return None;
|
145 | };
|
146 |
|
147 | for pattern: &&str in patterns {
|
148 | let pattern: String = directory.join(path:pattern).to_string_lossy().into_owned();
|
149 | if let Some(path: PathBuf) = glob::glob(&pattern).ok()?.filter_map(|p: Result| p.ok()).next() {
|
150 | if path.is_file() && is_executable(&path).unwrap_or(default:false) {
|
151 | return Some(path);
|
152 | }
|
153 | }
|
154 | }
|
155 |
|
156 | None
|
157 | }
|
158 |
|
159 | #[cfg (unix)]
|
160 | fn is_executable(path: &Path) -> io::Result<bool> {
|
161 | use std::ffi::CString;
|
162 | use std::os::unix::ffi::OsStrExt;
|
163 |
|
164 | let path: CString = CString::new(path.as_os_str().as_bytes())?;
|
165 | unsafe { Ok(libc::access(path.as_ptr(), amode:libc::X_OK) == 0) }
|
166 | }
|
167 |
|
168 | #[cfg (not(unix))]
|
169 | fn is_executable(_: &Path) -> io::Result<bool> {
|
170 | Ok(true)
|
171 | }
|
172 |
|
173 | /// Attempts to run an executable, returning the `stdout` and `stderr` output if
|
174 | /// successful.
|
175 | fn run(executable: &str, arguments: &[&str]) -> Result<(String, String), String> {
|
176 | Command::new(executable)
|
177 | .args(arguments)
|
178 | .output()
|
179 | .map(|o| {
|
180 | let stdout = String::from_utf8_lossy(&o.stdout).into_owned();
|
181 | let stderr = String::from_utf8_lossy(&o.stderr).into_owned();
|
182 | (stdout, stderr)
|
183 | })
|
184 | .map_err(|e: Error| format!("could not run executable ` {}`: {}" , executable, e))
|
185 | }
|
186 |
|
187 | /// Runs `clang`, returning the `stdout` and `stderr` output.
|
188 | fn run_clang(path: &Path, arguments: &[&str]) -> (String, String) {
|
189 | run(&path.to_string_lossy(), arguments).unwrap()
|
190 | }
|
191 |
|
192 | /// Runs `llvm-config`, returning the `stdout` output if successful.
|
193 | fn run_llvm_config(arguments: &[&str]) -> Result<String, String> {
|
194 | let config: String = env::var("LLVM_CONFIG_PATH" ).unwrap_or_else(|_| "llvm-config" .to_string());
|
195 | run(&config, arguments).map(|(o: String, _)| o)
|
196 | }
|
197 |
|
198 | /// Parses a version number if possible, ignoring trailing non-digit characters.
|
199 | fn parse_version_number(number: &str) -> Option<c_int> {
|
200 | numberResult
|
201 | .chars()
|
202 | .take_while(|c: &char| c.is_ascii_digit())
|
203 | .collect::<String>()
|
204 | .parse()
|
205 | .ok()
|
206 | }
|
207 |
|
208 | /// Parses the version from the output of a `clang` executable if possible.
|
209 | fn parse_version(path: &Path) -> Option<CXVersion> {
|
210 | let output: String = run_clang(path, &["--version" ]).0;
|
211 | let start: usize = output.find("version " )? + 8;
|
212 | let mut numbers: Split<'_, char> = output[start..].split_whitespace().next()?.split('.' );
|
213 | let major: i32 = numbers.next().and_then(parse_version_number)?;
|
214 | let minor: i32 = numbers.next().and_then(parse_version_number)?;
|
215 | let subminor: i32 = numbers.next().and_then(parse_version_number).unwrap_or(default:0);
|
216 | Some(CXVersion {
|
217 | Major: major,
|
218 | Minor: minor,
|
219 | Subminor: subminor,
|
220 | })
|
221 | }
|
222 |
|
223 | /// Parses the search paths from the output of a `clang` executable if possible.
|
224 | fn parse_search_paths(path: &Path, language: &str, args: &[String]) -> Option<Vec<PathBuf>> {
|
225 | let mut clang_args: Vec<&'static str> = vec!["-E" , "-x" , language, "-" , "-v" ];
|
226 | clang_args.extend(iter:args.iter().map(|s: &String| &**s));
|
227 | let output: String = run_clang(path, &clang_args).1;
|
228 | let start: usize = output.find("#include <...> search starts here:" )? + 34;
|
229 | let end: usize = output.find("End of search list." )?;
|
230 | let paths: String = output[start..end].replace(from:"(framework directory)" , to:"" );
|
231 | Some(
|
232 | pathsimpl Iterator
|
233 | .lines()
|
234 | .filter(|l: &&str| !l.is_empty())
|
235 | .map(|l: &str| Path::new(l.trim()).into())
|
236 | .collect(),
|
237 | )
|
238 | }
|
239 | |