blob: bc720ca6b0c8dc75fff1dd74cb582dbe7a828e5a [file] [log] [blame]
David LeGare82e2b172022-03-01 18:53:05 +00001// SPDX-License-Identifier: Apache-2.0
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -07002
3extern crate glob;
4
Haibo Huang8b9513e2020-07-13 22:05:39 -07005use std::cell::RefCell;
6use std::collections::HashMap;
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -07007use std::env;
8use std::path::{Path, PathBuf};
9use std::process::Command;
10
Joel Galensoneabe8352021-09-22 10:52:39 -070011use glob::{MatchOptions, Pattern};
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070012
David LeGare82e2b172022-03-01 18:53:05 +000013//================================================
14// Commands
15//================================================
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070016
Haibo Huang8b9513e2020-07-13 22:05:39 -070017thread_local! {
David LeGare82e2b172022-03-01 18:53:05 +000018 /// The errors encountered by the build script while executing commands.
Haibo Huang8b9513e2020-07-13 22:05:39 -070019 static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
20}
21
David LeGare82e2b172022-03-01 18:53:05 +000022/// Adds an error encountered by the build script while executing a command.
23fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) {
24 COMMAND_ERRORS.with(|e| {
25 e.borrow_mut()
26 .entry(name.into())
27 .or_insert_with(Vec::new)
28 .push(format!(
29 "couldn't execute `{} {}` (path={}) ({})",
30 name,
31 arguments.join(" "),
32 path,
33 message,
34 ))
35 });
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070036}
37
David LeGare82e2b172022-03-01 18:53:05 +000038/// A struct that prints the errors encountered by the build script while
39/// executing commands when dropped (unless explictly discarded).
40///
41/// This is handy because we only want to print these errors when the build
42/// script fails to link to an instance of `libclang`. For example, if
43/// `llvm-config` couldn't be executed but an instance of `libclang` was found
44/// anyway we don't want to pollute the build output with irrelevant errors.
Haibo Huang8b9513e2020-07-13 22:05:39 -070045#[derive(Default)]
46pub struct CommandErrorPrinter {
Joel Galenson83337ed2021-05-19 14:55:23 -070047 discard: bool,
Haibo Huang8b9513e2020-07-13 22:05:39 -070048}
49
50impl CommandErrorPrinter {
51 pub fn discard(mut self) {
52 self.discard = true;
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070053 }
Haibo Huang8b9513e2020-07-13 22:05:39 -070054}
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070055
Haibo Huang8b9513e2020-07-13 22:05:39 -070056impl Drop for CommandErrorPrinter {
57 fn drop(&mut self) {
58 if self.discard {
59 return;
60 }
61
62 let errors = COMMAND_ERRORS.with(|e| e.borrow().clone());
63
64 if let Some(errors) = errors.get("llvm-config") {
65 println!(
66 "cargo:warning=could not execute `llvm-config` one or more \
67 times, if the LLVM_CONFIG_PATH environment variable is set to \
68 a full path to valid `llvm-config` executable it will be used \
69 to try to find an instance of `libclang` on your system: {}",
Joel Galenson83337ed2021-05-19 14:55:23 -070070 errors
71 .iter()
72 .map(|e| format!("\"{}\"", e))
73 .collect::<Vec<_>>()
74 .join("\n "),
Haibo Huang8b9513e2020-07-13 22:05:39 -070075 )
76 }
77
78 if let Some(errors) = errors.get("xcode-select") {
79 println!(
80 "cargo:warning=could not execute `xcode-select` one or more \
81 times, if a valid instance of this executable is on your PATH \
82 it will be used to try to find an instance of `libclang` on \
83 your system: {}",
Joel Galenson83337ed2021-05-19 14:55:23 -070084 errors
85 .iter()
86 .map(|e| format!("\"{}\"", e))
87 .collect::<Vec<_>>()
88 .join("\n "),
Haibo Huang8b9513e2020-07-13 22:05:39 -070089 )
90 }
91 }
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070092}
93
David LeGare82e2b172022-03-01 18:53:05 +000094/// Executes a command and returns the `stdout` output if the command was
95/// successfully executed (errors are added to `COMMAND_ERRORS`).
96fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> {
97 let output = match Command::new(path).args(arguments).output() {
98 Ok(output) => output,
99 Err(error) => {
100 let message = format!("error: {}", error);
101 add_command_error(name, path, arguments, message);
102 return None;
103 }
104 };
105
106 if output.status.success() {
107 Some(String::from_utf8_lossy(&output.stdout).into_owned())
108 } else {
109 let message = format!("exit code: {}", output.status);
110 add_command_error(name, path, arguments, message);
111 None
112 }
113}
114
115/// Executes the `llvm-config` command and returns the `stdout` output if the
116/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
117pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
118 let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
119 run_command("llvm-config", &path, arguments)
120}
121
122/// Executes the `xcode-select` command and returns the `stdout` output if the
123/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
124pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
125 run_command("xcode-select", "xcode-select", arguments)
126}
127
128//================================================
129// Search Directories
130//================================================
131
132/// `libclang` directory patterns for Haiku.
133const DIRECTORIES_HAIKU: &[&str] = &[
134 "/boot/system/lib",
135 "/boot/system/develop/lib",
136 "/boot/system/non-packaged/lib",
137 "/boot/system/non-packaged/develop/lib",
138 "/boot/home/config/non-packaged/lib",
139 "/boot/home/config/non-packaged/develop/lib",
140];
141
142/// `libclang` directory patterns for Linux (and FreeBSD).
143const DIRECTORIES_LINUX: &[&str] = &[
144 "/usr/lib*",
145 "/usr/lib*/*",
146 "/usr/lib*/*/*",
147 "/usr/local/lib*",
148 "/usr/local/lib*/*",
149 "/usr/local/lib*/*/*",
150 "/usr/local/llvm*/lib*",
151];
152
153/// `libclang` directory patterns for macOS.
154const DIRECTORIES_MACOS: &[&str] = &[
155 "/usr/local/opt/llvm*/lib",
156 "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
157 "/Library/Developer/CommandLineTools/usr/lib",
158 "/usr/local/opt/llvm*/lib/llvm*/lib",
159];
160
161/// `libclang` directory patterns for Windows.
162const DIRECTORIES_WINDOWS: &[&str] = &[
163 "C:\\LLVM\\lib",
164 "C:\\Program Files*\\LLVM\\lib",
165 "C:\\MSYS*\\MinGW*\\lib",
166 // LLVM + Clang can be installed as a component of Visual Studio.
167 // https://github.com/KyleMayes/clang-sys/issues/121
168 "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin",
169 // LLVM + Clang can be installed using Scoop (https://scoop.sh).
170 // Other Windows package managers install LLVM + Clang to previously listed
171 // system-wide directories.
172 "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
173];
174
175//================================================
176// Searching
177//================================================
178
179/// Finds the files in a directory that match one or more filename glob patterns
180/// and returns the paths to and filenames of those files.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700181fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
David LeGare82e2b172022-03-01 18:53:05 +0000182 // Escape the specified directory in case it contains characters that have
183 // special meaning in glob patterns (e.g., `[` or `]`).
Joel Galensoneabe8352021-09-22 10:52:39 -0700184 let directory = Pattern::escape(directory.to_str().unwrap());
185 let directory = Path::new(&directory);
186
David LeGare82e2b172022-03-01 18:53:05 +0000187 // Join the escaped directory to the filename glob patterns to obtain
188 // complete glob patterns for the files being searched for.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700189 let paths = filenames
190 .iter()
Joel Galensoneabe8352021-09-22 10:52:39 -0700191 .map(|f| directory.join(f).to_str().unwrap().to_owned());
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700192
David LeGare82e2b172022-03-01 18:53:05 +0000193 // Prevent wildcards from matching path separators to ensure that the search
194 // is limited to the specified directory.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700195 let mut options = MatchOptions::new();
196 options.require_literal_separator = true;
197
198 paths
David LeGare82e2b172022-03-01 18:53:05 +0000199 .map(|p| glob::glob_with(&p, options))
200 .filter_map(Result::ok)
201 .flatten()
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700202 .filter_map(|p| {
David LeGare82e2b172022-03-01 18:53:05 +0000203 let path = p.ok()?;
204 let filename = path.file_name()?.to_str().unwrap();
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700205
206 // The `libclang_shared` library has been renamed to `libclang-cpp`
207 // in Clang 10. This can cause instances of this library (e.g.,
208 // `libclang-cpp.so.10`) to be matched by patterns looking for
209 // instances of `libclang`.
210 if filename.contains("-cpp.") {
211 return None;
212 }
213
214 Some((directory.to_owned(), filename.into()))
215 })
216 .collect::<Vec<_>>()
217}
218
David LeGare82e2b172022-03-01 18:53:05 +0000219/// Finds the files in a directory (and any relevant sibling directories) that
220/// match one or more filename glob patterns and returns the paths to and
221/// filenames of those files.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700222fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
223 let mut results = search_directory(directory, filenames);
224
225 // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
226 // while `libclang.lib` is usually found in the LLVM `lib` directory. To
227 // keep things consistent with other platforms, only LLVM `lib` directories
228 // are included in the backup search directory globs so we need to search
229 // the LLVM `bin` directory here.
230 if cfg!(target_os = "windows") && directory.ends_with("lib") {
231 let sibling = directory.parent().unwrap().join("bin");
232 results.extend(search_directory(&sibling, filenames).into_iter());
233 }
234
235 results
236}
237
David LeGare82e2b172022-03-01 18:53:05 +0000238/// Finds the `libclang` static or dynamic libraries matching one or more
239/// filename glob patterns and returns the paths to and filenames of those files.
240pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
241 // Search only the path indicated by the relevant environment variable
242 // (e.g., `LIBCLANG_PATH`) if it is set.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700243 if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
David LeGare82e2b172022-03-01 18:53:05 +0000244 // Check if the path is a matching file.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700245 if let Some(parent) = path.parent() {
246 let filename = path.file_name().unwrap().to_str().unwrap();
David LeGare82e2b172022-03-01 18:53:05 +0000247 let libraries = search_directories(parent, filenames);
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700248 if libraries.iter().any(|(_, f)| f == filename) {
249 return vec![(parent.into(), filename.into())];
250 }
251 }
252
David LeGare82e2b172022-03-01 18:53:05 +0000253 // Check if the path is directory containing a matching file.
254 return search_directories(&path, filenames);
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700255 }
256
257 let mut found = vec![];
258
David LeGare82e2b172022-03-01 18:53:05 +0000259 // Search the `bin` and `lib` directories in the directory returned by
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700260 // `llvm-config --prefix`.
261 if let Some(output) = run_llvm_config(&["--prefix"]) {
262 let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
David LeGare82e2b172022-03-01 18:53:05 +0000263 found.extend(search_directories(&directory.join("bin"), filenames));
264 found.extend(search_directories(&directory.join("lib"), filenames));
265 found.extend(search_directories(&directory.join("lib64"), filenames));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700266 }
267
David LeGare82e2b172022-03-01 18:53:05 +0000268 // Search the toolchain directory in the directory returned by
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700269 // `xcode-select --print-path`.
270 if cfg!(target_os = "macos") {
David LeGare82e2b172022-03-01 18:53:05 +0000271 if let Some(output) = run_xcode_select(&["--print-path"]) {
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700272 let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
273 let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
David LeGare82e2b172022-03-01 18:53:05 +0000274 found.extend(search_directories(&directory, filenames));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700275 }
276 }
277
David LeGare82e2b172022-03-01 18:53:05 +0000278 // Search the directories in the `LD_LIBRARY_PATH` environment variable.
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700279 if let Ok(path) = env::var("LD_LIBRARY_PATH") {
Joel Galenson83337ed2021-05-19 14:55:23 -0700280 for directory in env::split_paths(&path) {
David LeGare82e2b172022-03-01 18:53:05 +0000281 found.extend(search_directories(&directory, filenames));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700282 }
283 }
284
285 // Determine the `libclang` directory patterns.
David LeGare82e2b172022-03-01 18:53:05 +0000286 let directories = if cfg!(target_os = "haiku") {
287 DIRECTORIES_HAIKU
288 } else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700289 DIRECTORIES_LINUX
290 } else if cfg!(target_os = "macos") {
291 DIRECTORIES_MACOS
292 } else if cfg!(target_os = "windows") {
293 DIRECTORIES_WINDOWS
294 } else {
295 &[]
296 };
297
298 // Search the directories provided by the `libclang` directory patterns.
299 let mut options = MatchOptions::new();
300 options.case_sensitive = false;
301 options.require_literal_separator = true;
302 for directory in directories.iter().rev() {
303 if let Ok(directories) = glob::glob_with(directory, options) {
304 for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
David LeGare82e2b172022-03-01 18:53:05 +0000305 found.extend(search_directories(&directory, filenames));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700306 }
307 }
308 }
309
310 found
311}