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 | |
| 17 | use std::env; |
| 18 | use std::path::{Path, PathBuf}; |
| 19 | use std::process::Command; |
| 20 | |
| 21 | use glob::MatchOptions; |
| 22 | |
| 23 | /// `libclang` directory patterns for FreeBSD and Linux. |
| 24 | const DIRECTORIES_LINUX: &[&str] = &[ |
| 25 | "/usr/lib*", |
| 26 | "/usr/lib*/*", |
| 27 | "/usr/lib*/*/*", |
| 28 | "/usr/local/lib*", |
| 29 | "/usr/local/lib*/*", |
| 30 | "/usr/local/lib*/*/*", |
| 31 | "/usr/local/llvm*/lib*", |
| 32 | ]; |
| 33 | |
| 34 | /// `libclang` directory patterns for macOS. |
| 35 | const DIRECTORIES_MACOS: &[&str] = &[ |
| 36 | "/usr/local/opt/llvm*/lib", |
| 37 | "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", |
| 38 | "/Library/Developer/CommandLineTools/usr/lib", |
| 39 | "/usr/local/opt/llvm*/lib/llvm*/lib", |
| 40 | ]; |
| 41 | |
| 42 | /// `libclang` directory patterns for Windows. |
| 43 | const DIRECTORIES_WINDOWS: &[&str] = &[ |
| 44 | "C:\\LLVM\\lib", |
| 45 | "C:\\Program Files*\\LLVM\\lib", |
| 46 | "C:\\MSYS*\\MinGW*\\lib", |
| 47 | ]; |
| 48 | |
| 49 | /// Executes the supplied console command, returning the `stdout` output if the |
| 50 | /// command was successfully executed. |
| 51 | fn run_command(command: &str, arguments: &[&str]) -> Option<String> { |
| 52 | macro_rules! warn { |
| 53 | ($error:expr) => { |
| 54 | println!( |
| 55 | "cargo:warning=couldn't execute `{} {}` ({})", |
| 56 | command, |
| 57 | arguments.join(" "), |
| 58 | $error, |
| 59 | ); |
| 60 | }; |
| 61 | } |
| 62 | |
| 63 | let output = match Command::new(command).args(arguments).output() { |
| 64 | Ok(output) => output, |
| 65 | Err(error) => { |
| 66 | warn!(format!("error: {}", error)); |
| 67 | return None; |
| 68 | } |
| 69 | }; |
| 70 | |
| 71 | if !output.status.success() { |
| 72 | warn!(format!("exit code: {}", output.status)); |
| 73 | return None; |
| 74 | } |
| 75 | |
| 76 | Some(String::from_utf8_lossy(&output.stdout).into_owned()) |
| 77 | } |
| 78 | |
| 79 | /// Executes `llvm-config`, returning the `stdout` output if the command was |
| 80 | /// successfully executed. |
| 81 | pub fn run_llvm_config(arguments: &[&str]) -> Option<String> { |
| 82 | let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()); |
| 83 | |
| 84 | let output = run_command(&path, arguments); |
| 85 | if output.is_none() { |
| 86 | println!( |
| 87 | "cargo:warning=set the LLVM_CONFIG_PATH environment variable to \ |
| 88 | the full path to a valid `llvm-config` executable (including the \ |
| 89 | executable itself)" |
| 90 | ); |
| 91 | } |
| 92 | |
| 93 | output |
| 94 | } |
| 95 | |
| 96 | /// Returns the paths to and the filenames of the files matching the supplied |
| 97 | /// filename patterns in the supplied directory. |
| 98 | fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 99 | // Join the directory to the filename patterns to obtain the path patterns. |
| 100 | let paths = filenames |
| 101 | .iter() |
| 102 | .filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned)); |
| 103 | |
| 104 | // Prevent wildcards from matching path separators. |
| 105 | let mut options = MatchOptions::new(); |
| 106 | options.require_literal_separator = true; |
| 107 | |
| 108 | paths |
| 109 | .flat_map(|p| { |
| 110 | if let Ok(paths) = glob::glob_with(&p, options) { |
| 111 | paths.filter_map(Result::ok).collect() |
| 112 | } else { |
| 113 | vec![] |
| 114 | } |
| 115 | }) |
| 116 | .filter_map(|p| { |
| 117 | let filename = p.file_name().and_then(|f| f.to_str())?; |
| 118 | |
| 119 | // The `libclang_shared` library has been renamed to `libclang-cpp` |
| 120 | // in Clang 10. This can cause instances of this library (e.g., |
| 121 | // `libclang-cpp.so.10`) to be matched by patterns looking for |
| 122 | // instances of `libclang`. |
| 123 | if filename.contains("-cpp.") { |
| 124 | return None; |
| 125 | } |
| 126 | |
| 127 | Some((directory.to_owned(), filename.into())) |
| 128 | }) |
| 129 | .collect::<Vec<_>>() |
| 130 | } |
| 131 | |
| 132 | /// Returns the paths to and the filenames of the files matching the supplied |
| 133 | /// filename patterns in the supplied directory, checking any relevant sibling |
| 134 | /// directories. |
| 135 | fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> { |
| 136 | let mut results = search_directory(directory, filenames); |
| 137 | |
| 138 | // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory |
| 139 | // while `libclang.lib` is usually found in the LLVM `lib` directory. To |
| 140 | // keep things consistent with other platforms, only LLVM `lib` directories |
| 141 | // are included in the backup search directory globs so we need to search |
| 142 | // the LLVM `bin` directory here. |
| 143 | if cfg!(target_os = "windows") && directory.ends_with("lib") { |
| 144 | let sibling = directory.parent().unwrap().join("bin"); |
| 145 | results.extend(search_directory(&sibling, filenames).into_iter()); |
| 146 | } |
| 147 | |
| 148 | results |
| 149 | } |
| 150 | |
| 151 | /// Returns the paths to and the filenames of the `libclang` static or dynamic |
| 152 | /// libraries matching the supplied filename patterns. |
| 153 | pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> { |
| 154 | // Use the path provided by the relevant environment variable. |
| 155 | if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) { |
| 156 | // Check if the path is referring to a matching file already. |
| 157 | if let Some(parent) = path.parent() { |
| 158 | let filename = path.file_name().unwrap().to_str().unwrap(); |
| 159 | let libraries = search_directories(parent, files); |
| 160 | if libraries.iter().any(|(_, f)| f == filename) { |
| 161 | return vec![(parent.into(), filename.into())]; |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | return search_directories(&path, files); |
| 166 | } |
| 167 | |
| 168 | let mut found = vec![]; |
| 169 | |
| 170 | // Search the `bin` and `lib` directories in directory provided by |
| 171 | // `llvm-config --prefix`. |
| 172 | if let Some(output) = run_llvm_config(&["--prefix"]) { |
| 173 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 174 | found.extend(search_directories(&directory.join("bin"), files)); |
| 175 | found.extend(search_directories(&directory.join("lib"), files)); |
| 176 | found.extend(search_directories(&directory.join("lib64"), files)); |
| 177 | } |
| 178 | |
| 179 | // Search the toolchain directory in the directory provided by |
| 180 | // `xcode-select --print-path`. |
| 181 | if cfg!(target_os = "macos") { |
| 182 | if let Some(output) = run_command("xcode-select", &["--print-path"]) { |
| 183 | let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); |
| 184 | let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); |
| 185 | found.extend(search_directories(&directory, files)); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | // Search the directories provided by the `LD_LIBRARY_PATH` environment |
| 190 | // variable. |
| 191 | if let Ok(path) = env::var("LD_LIBRARY_PATH") { |
| 192 | for directory in path.split(':').map(Path::new) { |
| 193 | found.extend(search_directories(&directory, files)); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | // Determine the `libclang` directory patterns. |
| 198 | let directories = if cfg!(any(target_os = "freebsd", target_os = "linux")) { |
| 199 | DIRECTORIES_LINUX |
| 200 | } else if cfg!(target_os = "macos") { |
| 201 | DIRECTORIES_MACOS |
| 202 | } else if cfg!(target_os = "windows") { |
| 203 | DIRECTORIES_WINDOWS |
| 204 | } else { |
| 205 | &[] |
| 206 | }; |
| 207 | |
| 208 | // Search the directories provided by the `libclang` directory patterns. |
| 209 | let mut options = MatchOptions::new(); |
| 210 | options.case_sensitive = false; |
| 211 | options.require_literal_separator = true; |
| 212 | for directory in directories.iter().rev() { |
| 213 | if let Ok(directories) = glob::glob_with(directory, options) { |
| 214 | for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { |
| 215 | found.extend(search_directories(&directory, files)); |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | found |
| 221 | } |