David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: Apache-2.0 |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 2 | |
| 3 | extern crate glob; |
| 4 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 5 | use std::cell::RefCell; |
| 6 | use std::collections::HashMap; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 7 | use std::env; |
| 8 | use std::path::{Path, PathBuf}; |
| 9 | use std::process::Command; |
| 10 | |
Joel Galenson | eabe835 | 2021-09-22 10:52:39 -0700 | [diff] [blame] | 11 | use glob::{MatchOptions, Pattern}; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 12 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 13 | //================================================ |
| 14 | // Commands |
| 15 | //================================================ |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 16 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 17 | thread_local! { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 18 | /// The errors encountered by the build script while executing commands. |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 19 | static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); |
| 20 | } |
| 21 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 22 | /// Adds an error encountered by the build script while executing a command. |
| 23 | fn 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 Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 36 | } |
| 37 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 38 | /// 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 Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 45 | #[derive(Default)] |
| 46 | pub struct CommandErrorPrinter { |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame] | 47 | discard: bool, |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 48 | } |
| 49 | |
| 50 | impl CommandErrorPrinter { |
| 51 | pub fn discard(mut self) { |
| 52 | self.discard = true; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 53 | } |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 54 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 55 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 56 | impl 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 Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame] | 70 | errors |
| 71 | .iter() |
| 72 | .map(|e| format!("\"{}\"", e)) |
| 73 | .collect::<Vec<_>>() |
| 74 | .join("\n "), |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 75 | ) |
| 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 Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame] | 84 | errors |
| 85 | .iter() |
| 86 | .map(|e| format!("\"{}\"", e)) |
| 87 | .collect::<Vec<_>>() |
| 88 | .join("\n "), |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 89 | ) |
| 90 | } |
| 91 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 92 | } |
| 93 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 94 | /// Executes a command and returns the `stdout` output if the command was |
| 95 | /// successfully executed (errors are added to `COMMAND_ERRORS`). |
| 96 | fn 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`). |
| 117 | pub 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`). |
| 124 | pub 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. |
| 133 | const 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). |
| 143 | const 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. |
| 154 | const 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. |
| 162 | const 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 Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 181 | fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 182 | // Escape the specified directory in case it contains characters that have |
| 183 | // special meaning in glob patterns (e.g., `[` or `]`). |
Joel Galenson | eabe835 | 2021-09-22 10:52:39 -0700 | [diff] [blame] | 184 | let directory = Pattern::escape(directory.to_str().unwrap()); |
| 185 | let directory = Path::new(&directory); |
| 186 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 187 | // Join the escaped directory to the filename glob patterns to obtain |
| 188 | // complete glob patterns for the files being searched for. |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 189 | let paths = filenames |
| 190 | .iter() |
Joel Galenson | eabe835 | 2021-09-22 10:52:39 -0700 | [diff] [blame] | 191 | .map(|f| directory.join(f).to_str().unwrap().to_owned()); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 192 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 193 | // Prevent wildcards from matching path separators to ensure that the search |
| 194 | // is limited to the specified directory. |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 195 | let mut options = MatchOptions::new(); |
| 196 | options.require_literal_separator = true; |
| 197 | |
| 198 | paths |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 199 | .map(|p| glob::glob_with(&p, options)) |
| 200 | .filter_map(Result::ok) |
| 201 | .flatten() |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 202 | .filter_map(|p| { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 203 | let path = p.ok()?; |
| 204 | let filename = path.file_name()?.to_str().unwrap(); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 205 | |
| 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 LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 219 | /// 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 Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 222 | fn 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 LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 238 | /// 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. |
| 240 | pub 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 Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 243 | if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 244 | // Check if the path is a matching file. |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 245 | if let Some(parent) = path.parent() { |
| 246 | let filename = path.file_name().unwrap().to_str().unwrap(); |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 247 | let libraries = search_directories(parent, filenames); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 248 | if libraries.iter().any(|(_, f)| f == filename) { |
| 249 | return vec![(parent.into(), filename.into())]; |
| 250 | } |
| 251 | } |
| 252 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 253 | // Check if the path is directory containing a matching file. |
| 254 | return search_directories(&path, filenames); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 255 | } |
| 256 | |
| 257 | let mut found = vec![]; |
| 258 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 259 | // Search the `bin` and `lib` directories in the directory returned by |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 260 | // `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 LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 263 | 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 Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 266 | } |
| 267 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 268 | // Search the toolchain directory in the directory returned by |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 269 | // `xcode-select --print-path`. |
| 270 | if cfg!(target_os = "macos") { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 271 | if let Some(output) = run_xcode_select(&["--print-path"]) { |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 272 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 273 | let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 274 | found.extend(search_directories(&directory, filenames)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 275 | } |
| 276 | } |
| 277 | |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 278 | // Search the directories in the `LD_LIBRARY_PATH` environment variable. |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 279 | if let Ok(path) = env::var("LD_LIBRARY_PATH") { |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame] | 280 | for directory in env::split_paths(&path) { |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 281 | found.extend(search_directories(&directory, filenames)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 282 | } |
| 283 | } |
| 284 | |
| 285 | // Determine the `libclang` directory patterns. |
David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 286 | let directories = if cfg!(target_os = "haiku") { |
| 287 | DIRECTORIES_HAIKU |
| 288 | } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 289 | 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 LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 305 | found.extend(search_directories(&directory, filenames)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | found |
| 311 | } |