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", |
| 49 | ]; |
| 50 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 51 | thread_local! { |
| 52 | /// The errors encountered when attempting to execute console commands. |
| 53 | static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default(); |
| 54 | } |
| 55 | |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 56 | /// Executes the supplied console command, returning the `stdout` output if the |
| 57 | /// command was successfully executed. |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 58 | fn run_command(name: &str, command: &str, arguments: &[&str]) -> Option<String> { |
| 59 | macro_rules! error { |
| 60 | ($error:expr) => {{ |
| 61 | COMMAND_ERRORS.with(|e| e.borrow_mut() |
| 62 | .entry(name.into()) |
| 63 | .or_insert_with(Vec::new) |
| 64 | .push(format!( |
| 65 | "couldn't execute `{} {}` ({})", |
| 66 | command, |
| 67 | arguments.join(" "), |
| 68 | $error, |
| 69 | ))); |
| 70 | }}; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 71 | } |
| 72 | |
| 73 | let output = match Command::new(command).args(arguments).output() { |
| 74 | Ok(output) => output, |
| 75 | Err(error) => { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 76 | error!(format!("error: {}", error)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 77 | return None; |
| 78 | } |
| 79 | }; |
| 80 | |
| 81 | if !output.status.success() { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 82 | error!(format!("exit code: {}", output.status)); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 83 | return None; |
| 84 | } |
| 85 | |
| 86 | Some(String::from_utf8_lossy(&output.stdout).into_owned()) |
| 87 | } |
| 88 | |
| 89 | /// Executes `llvm-config`, returning the `stdout` output if the command was |
| 90 | /// successfully executed. |
| 91 | pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { |
| 92 | 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] | 93 | run_command("llvm-config", &path, arguments) |
| 94 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 95 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 96 | /// A struct that prints errors encountered when attempting to execute console |
| 97 | /// commands on drop if not discarded. |
| 98 | #[derive(Default)] |
| 99 | pub struct CommandErrorPrinter { |
| 100 | discard: bool |
| 101 | } |
| 102 | |
| 103 | impl CommandErrorPrinter { |
| 104 | pub fn discard(mut self) { |
| 105 | self.discard = true; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 106 | } |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 107 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 108 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 109 | impl Drop for CommandErrorPrinter { |
| 110 | fn drop(&mut self) { |
| 111 | if self.discard { |
| 112 | return; |
| 113 | } |
| 114 | |
| 115 | let errors = COMMAND_ERRORS.with(|e| e.borrow().clone()); |
| 116 | |
| 117 | if let Some(errors) = errors.get("llvm-config") { |
| 118 | println!( |
| 119 | "cargo:warning=could not execute `llvm-config` one or more \ |
| 120 | times, if the LLVM_CONFIG_PATH environment variable is set to \ |
| 121 | a full path to valid `llvm-config` executable it will be used \ |
| 122 | to try to find an instance of `libclang` on your system: {}", |
| 123 | errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), |
| 124 | ) |
| 125 | } |
| 126 | |
| 127 | if let Some(errors) = errors.get("xcode-select") { |
| 128 | println!( |
| 129 | "cargo:warning=could not execute `xcode-select` one or more \ |
| 130 | times, if a valid instance of this executable is on your PATH \ |
| 131 | it will be used to try to find an instance of `libclang` on \ |
| 132 | your system: {}", |
| 133 | errors.iter().map(|e| format!("\"{}\"", e)).collect::<Vec<_>>().join("\n "), |
| 134 | ) |
| 135 | } |
| 136 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 137 | } |
| 138 | |
| 139 | /// Returns the paths to and the filenames of the files matching the supplied |
| 140 | /// filename patterns in the supplied directory. |
| 141 | fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 142 | // Join the directory to the filename patterns to obtain the path patterns. |
| 143 | let paths = filenames |
| 144 | .iter() |
| 145 | .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned)); |
| 146 | |
| 147 | // Prevent wildcards from matching path separators. |
| 148 | let mut options = MatchOptions::new(); |
| 149 | options.require_literal_separator = true; |
| 150 | |
| 151 | paths |
| 152 | .flat_map(|p| { |
| 153 | if let Ok(paths) = glob::glob_with(&p, options) { |
| 154 | paths.filter_map(Result::ok).collect() |
| 155 | } else { |
| 156 | vec![] |
| 157 | } |
| 158 | }) |
| 159 | .filter_map(|p| { |
| 160 | let filename = p.file_name().and_then(|f| f.to_str())?; |
| 161 | |
| 162 | // The `libclang_shared` library has been renamed to `libclang-cpp` |
| 163 | // in Clang 10. This can cause instances of this library (e.g., |
| 164 | // `libclang-cpp.so.10`) to be matched by patterns looking for |
| 165 | // instances of `libclang`. |
| 166 | if filename.contains("-cpp.") { |
| 167 | return None; |
| 168 | } |
| 169 | |
| 170 | Some((directory.to_owned(), filename.into())) |
| 171 | }) |
| 172 | .collect::<Vec<_>>() |
| 173 | } |
| 174 | |
| 175 | /// Returns the paths to and the filenames of the files matching the supplied |
| 176 | /// filename patterns in the supplied directory, checking any relevant sibling |
| 177 | /// directories. |
| 178 | fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 179 | let mut results = search_directory(directory, filenames); |
| 180 | |
| 181 | // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory |
| 182 | // while `libclang.lib` is usually found in the LLVM `lib` directory. To |
| 183 | // keep things consistent with other platforms, only LLVM `lib` directories |
| 184 | // are included in the backup search directory globs so we need to search |
| 185 | // the LLVM `bin` directory here. |
| 186 | if cfg!(target_os = "windows") && directory.ends_with("lib") { |
| 187 | let sibling = directory.parent().unwrap().join("bin"); |
| 188 | results.extend(search_directory(&sibling, filenames).into_iter()); |
| 189 | } |
| 190 | |
| 191 | results |
| 192 | } |
| 193 | |
| 194 | /// Returns the paths to and the filenames of the `libclang` static or dynamic |
| 195 | /// libraries matching the supplied filename patterns. |
| 196 | pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> { |
| 197 | // Use the path provided by the relevant environment variable. |
| 198 | if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { |
| 199 | // Check if the path is referring to a matching file already. |
| 200 | if let Some(parent) = path.parent() { |
| 201 | let filename = path.file_name().unwrap().to_str().unwrap(); |
| 202 | let libraries = search_directories(parent, files); |
| 203 | if libraries.iter().any(|(_, f)| f == filename) { |
| 204 | return vec![(parent.into(), filename.into())]; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | return search_directories(&path, files); |
| 209 | } |
| 210 | |
| 211 | let mut found = vec![]; |
| 212 | |
| 213 | // Search the `bin` and `lib` directories in directory provided by |
| 214 | // `llvm-config --prefix`. |
| 215 | if let Some(output) = run_llvm_config(&["--prefix"]) { |
| 216 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 217 | found.extend(search_directories(&directory.join("bin"), files)); |
| 218 | found.extend(search_directories(&directory.join("lib"), files)); |
| 219 | found.extend(search_directories(&directory.join("lib64"), files)); |
| 220 | } |
| 221 | |
| 222 | // Search the toolchain directory in the directory provided by |
| 223 | // `xcode-select --print-path`. |
| 224 | if cfg!(target_os = "macos") { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 225 | 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] | 226 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 227 | let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); |
| 228 | found.extend(search_directories(&directory, files)); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | // Search the directories provided by the `LD_LIBRARY_PATH` environment |
| 233 | // variable. |
| 234 | if let Ok(path) = env::var("LD_LIBRARY_PATH") { |
| 235 | for directory in path.split(':').map(Path::new) { |
| 236 | found.extend(search_directories(&directory, files)); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | // Determine the `libclang` directory patterns. |
| 241 | let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) { |
| 242 | DIRECTORIES_LINUX |
| 243 | } else if cfg!(target_os = "macos") { |
| 244 | DIRECTORIES_MACOS |
| 245 | } else if cfg!(target_os = "windows") { |
| 246 | DIRECTORIES_WINDOWS |
| 247 | } else { |
| 248 | &[] |
| 249 | }; |
| 250 | |
| 251 | // Search the directories provided by the `libclang` directory patterns. |
| 252 | let mut options = MatchOptions::new(); |
| 253 | options.case_sensitive = false; |
| 254 | options.require_literal_separator = true; |
| 255 | for directory in directories.iter().rev() { |
| 256 | if let Ok(directories) = glob::glob_with(directory, options) { |
| 257 | for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { |
| 258 | found.extend(search_directories(&directory, files)); |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | found |
| 264 | } |