Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 1 | // Copyright 2018 Kyle Mayes |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | extern crate glob; |
| 16 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 17 | use std::cell::RefCell; |
| 18 | use std::collections::HashMap; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 19 | use std::env; |
| 20 | use std::path::{Path, PathBuf}; |
| 21 | use std::process::Command; |
| 22 | |
| 23 | use glob::MatchOptions; |
| 24 | |
| 25 | /// `libclang` directory patterns for FreeBSD and Linux. |
| 26 | const DIRECTORIES_LINUX: &[&str] = &[ |
| 27 | "/usr/lib*", |
| 28 | "/usr/lib*/*", |
| 29 | "/usr/lib*/*/*", |
| 30 | "/usr/local/lib*", |
| 31 | "/usr/local/lib*/*", |
| 32 | "/usr/local/lib*/*/*", |
| 33 | "/usr/local/llvm*/lib*", |
| 34 | ]; |
| 35 | |
| 36 | /// `libclang` directory patterns for macOS. |
| 37 | const DIRECTORIES_MACOS: &[&str] = &[ |
| 38 | "/usr/local/opt/llvm*/lib", |
| 39 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", |
| 40 | "/Library/Developer/CommandLineTools/usr/lib", |
| 41 | "/usr/local/opt/llvm*/lib/llvm*/lib", |
| 42 | ]; |
| 43 | |
| 44 | /// `libclang` directory patterns for Windows. |
| 45 | const DIRECTORIES_WINDOWS: &[&str] = &[ |
| 46 | "C:\\LLVM\\lib", |
| 47 | "C:\\Program Files*\\LLVM\\lib", |
| 48 | "C:\\MSYS*\\MinGW*\\lib", |
Haibo Huang | 2e1e83e | 2021-02-09 23:57:34 -0800 | [diff] [blame] | 49 | // LLVM + Clang can be installed as a component of Visual Studio. |
| 50 | // https://github.com/KyleMayes/clang-sys/issues/121 |
| 51 | "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 52 | ]; |
| 53 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 54 | thread_local! { |
| 55 | /// The errors encountered when attempting to execute console commands. |
| 56 | static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); |
| 57 | } |
| 58 | |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 59 | /// Executes the supplied console command, returning the `stdout` output if the |
| 60 | /// command was successfully executed. |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 61 | fn run_command(name: &str, command: &str, arguments: &[&str]) -> Option<String> { |
| 62 | macro_rules! error { |
| 63 | ($error:expr) => {{ |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame^] | 64 | COMMAND_ERRORS.with(|e| { |
| 65 | e.borrow_mut() |
| 66 | .entry(name.into()) |
| 67 | .or_insert_with(Vec::new) |
| 68 | .push(format!( |
| 69 | "couldn't execute `{} {}` ({})", |
| 70 | command, |
| 71 | arguments.join(" "), |
| 72 | $error, |
| 73 | )) |
| 74 | }); |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 75 | }}; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 76 | } |
| 77 | |
| 78 | let output = match Command::new(command).args(arguments).output() { |
| 79 | Ok(output) => output, |
| 80 | Err(error) => { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 81 | error!(format!("error: {}", error)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 82 | return None; |
| 83 | } |
| 84 | }; |
| 85 | |
| 86 | if !output.status.success() { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 87 | error!(format!("exit code: {}", output.status)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 88 | return None; |
| 89 | } |
| 90 | |
| 91 | Some(String::from_utf8_lossy(&output.stdout).into_owned()) |
| 92 | } |
| 93 | |
| 94 | /// Executes `llvm-config`, returning the `stdout` output if the command was |
| 95 | /// successfully executed. |
| 96 | pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { |
| 97 | let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 98 | run_command("llvm-config", &path, arguments) |
| 99 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 100 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 101 | /// A struct that prints errors encountered when attempting to execute console |
| 102 | /// commands on drop if not discarded. |
| 103 | #[derive(Default)] |
| 104 | pub struct CommandErrorPrinter { |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame^] | 105 | discard: bool, |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | impl CommandErrorPrinter { |
| 109 | pub fn discard(mut self) { |
| 110 | self.discard = true; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 111 | } |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 112 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 113 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 114 | impl Drop for CommandErrorPrinter { |
| 115 | fn drop(&mut self) { |
| 116 | if self.discard { |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | let errors = COMMAND_ERRORS.with(|e| e.borrow().clone()); |
| 121 | |
| 122 | if let Some(errors) = errors.get("llvm-config") { |
| 123 | println!( |
| 124 | "cargo:warning=could not execute `llvm-config` one or more \ |
| 125 | times, if the LLVM_CONFIG_PATH environment variable is set to \ |
| 126 | a full path to valid `llvm-config` executable it will be used \ |
| 127 | to try to find an instance of `libclang` on your system: {}", |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame^] | 128 | errors |
| 129 | .iter() |
| 130 | .map(|e| format!("\"{}\"", e)) |
| 131 | .collect::<Vec<_>>() |
| 132 | .join("\n "), |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 133 | ) |
| 134 | } |
| 135 | |
| 136 | if let Some(errors) = errors.get("xcode-select") { |
| 137 | println!( |
| 138 | "cargo:warning=could not execute `xcode-select` one or more \ |
| 139 | times, if a valid instance of this executable is on your PATH \ |
| 140 | it will be used to try to find an instance of `libclang` on \ |
| 141 | your system: {}", |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame^] | 142 | errors |
| 143 | .iter() |
| 144 | .map(|e| format!("\"{}\"", e)) |
| 145 | .collect::<Vec<_>>() |
| 146 | .join("\n "), |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 147 | ) |
| 148 | } |
| 149 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | /// Returns the paths to and the filenames of the files matching the supplied |
| 153 | /// filename patterns in the supplied directory. |
| 154 | fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 155 | // Join the directory to the filename patterns to obtain the path patterns. |
| 156 | let paths = filenames |
| 157 | .iter() |
| 158 | .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned)); |
| 159 | |
| 160 | // Prevent wildcards from matching path separators. |
| 161 | let mut options = MatchOptions::new(); |
| 162 | options.require_literal_separator = true; |
| 163 | |
| 164 | paths |
| 165 | .flat_map(|p| { |
| 166 | if let Ok(paths) = glob::glob_with(&p, options) { |
| 167 | paths.filter_map(Result::ok).collect() |
| 168 | } else { |
| 169 | vec![] |
| 170 | } |
| 171 | }) |
| 172 | .filter_map(|p| { |
| 173 | let filename = p.file_name().and_then(|f| f.to_str())?; |
| 174 | |
| 175 | // The `libclang_shared` library has been renamed to `libclang-cpp` |
| 176 | // in Clang 10. This can cause instances of this library (e.g., |
| 177 | // `libclang-cpp.so.10`) to be matched by patterns looking for |
| 178 | // instances of `libclang`. |
| 179 | if filename.contains("-cpp.") { |
| 180 | return None; |
| 181 | } |
| 182 | |
| 183 | Some((directory.to_owned(), filename.into())) |
| 184 | }) |
| 185 | .collect::<Vec<_>>() |
| 186 | } |
| 187 | |
| 188 | /// Returns the paths to and the filenames of the files matching the supplied |
| 189 | /// filename patterns in the supplied directory, checking any relevant sibling |
| 190 | /// directories. |
| 191 | fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 192 | let mut results = search_directory(directory, filenames); |
| 193 | |
| 194 | // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory |
| 195 | // while `libclang.lib` is usually found in the LLVM `lib` directory. To |
| 196 | // keep things consistent with other platforms, only LLVM `lib` directories |
| 197 | // are included in the backup search directory globs so we need to search |
| 198 | // the LLVM `bin` directory here. |
| 199 | if cfg!(target_os = "windows") && directory.ends_with("lib") { |
| 200 | let sibling = directory.parent().unwrap().join("bin"); |
| 201 | results.extend(search_directory(&sibling, filenames).into_iter()); |
| 202 | } |
| 203 | |
| 204 | results |
| 205 | } |
| 206 | |
| 207 | /// Returns the paths to and the filenames of the `libclang` static or dynamic |
| 208 | /// libraries matching the supplied filename patterns. |
| 209 | pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> { |
| 210 | // Use the path provided by the relevant environment variable. |
| 211 | if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { |
| 212 | // Check if the path is referring to a matching file already. |
| 213 | if let Some(parent) = path.parent() { |
| 214 | let filename = path.file_name().unwrap().to_str().unwrap(); |
| 215 | let libraries = search_directories(parent, files); |
| 216 | if libraries.iter().any(|(_, f)| f == filename) { |
| 217 | return vec![(parent.into(), filename.into())]; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | return search_directories(&path, files); |
| 222 | } |
| 223 | |
| 224 | let mut found = vec![]; |
| 225 | |
| 226 | // Search the `bin` and `lib` directories in directory provided by |
| 227 | // `llvm-config --prefix`. |
| 228 | if let Some(output) = run_llvm_config(&["--prefix"]) { |
| 229 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 230 | found.extend(search_directories(&directory.join("bin"), files)); |
| 231 | found.extend(search_directories(&directory.join("lib"), files)); |
| 232 | found.extend(search_directories(&directory.join("lib64"), files)); |
| 233 | } |
| 234 | |
| 235 | // Search the toolchain directory in the directory provided by |
| 236 | // `xcode-select --print-path`. |
| 237 | if cfg!(target_os = "macos") { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 238 | if let Some(output) = run_command("xcode-select", "xcode-select", &["--print-path"]) { |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 239 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 240 | let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); |
| 241 | found.extend(search_directories(&directory, files)); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | // Search the directories provided by the `LD_LIBRARY_PATH` environment |
| 246 | // variable. |
| 247 | if let Ok(path) = env::var("LD_LIBRARY_PATH") { |
Joel Galenson | 83337ed | 2021-05-19 14:55:23 -0700 | [diff] [blame^] | 248 | for directory in env::split_paths(&path) { |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 249 | found.extend(search_directories(&directory, files)); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | // Determine the `libclang` directory patterns. |
| 254 | let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) { |
| 255 | DIRECTORIES_LINUX |
| 256 | } else if cfg!(target_os = "macos") { |
| 257 | DIRECTORIES_MACOS |
| 258 | } else if cfg!(target_os = "windows") { |
| 259 | DIRECTORIES_WINDOWS |
| 260 | } else { |
| 261 | &[] |
| 262 | }; |
| 263 | |
| 264 | // Search the directories provided by the `libclang` directory patterns. |
| 265 | let mut options = MatchOptions::new(); |
| 266 | options.case_sensitive = false; |
| 267 | options.require_literal_separator = true; |
| 268 | for directory in directories.iter().rev() { |
| 269 | if let Ok(directories) = glob::glob_with(directory, options) { |
| 270 | for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { |
| 271 | found.extend(search_directories(&directory, files)); |
| 272 | } |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | found |
| 277 | } |