blob: f06aff516e685f16fb7ade1ad009c0d2986b307b [file] [log] [blame]
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -07001// 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
15extern crate glob;
16
Haibo Huang8b9513e2020-07-13 22:05:39 -070017use std::cell::RefCell;
18use std::collections::HashMap;
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070019use std::env;
20use std::path::{Path, PathBuf};
21use std::process::Command;
22
23use glob::MatchOptions;
24
25/// `libclang` directory patterns for FreeBSD and Linux.
26const 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.
37const 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.
45const DIRECTORIES_WINDOWS: &[&str] = &[
46 "C:\\LLVM\\lib",
47 "C:\\Program Files*\\LLVM\\lib",
48 "C:\\MSYS*\\MinGW*\\lib",
Haibo Huang2e1e83e2021-02-09 23:57:34 -080049 // 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 Hsiehfab43802020-04-07 14:24:01 -070052];
53
Haibo Huang8b9513e2020-07-13 22:05:39 -070054thread_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 Hsiehfab43802020-04-07 14:24:01 -070059/// Executes the supplied console command, returning the `stdout` output if the
60/// command was successfully executed.
Haibo Huang8b9513e2020-07-13 22:05:39 -070061fn run_command(name: &str, command: &str, arguments: &[&str]) -> Option<String> {
62 macro_rules! error {
63 ($error:expr) => {{
Joel Galenson83337ed2021-05-19 14:55:23 -070064 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 Huang8b9513e2020-07-13 22:05:39 -070075 }};
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070076 }
77
78 let output = match Command::new(command).args(arguments).output() {
79 Ok(output) => output,
80 Err(error) => {
Haibo Huang8b9513e2020-07-13 22:05:39 -070081 error!(format!("error: {}", error));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070082 return None;
83 }
84 };
85
86 if !output.status.success() {
Haibo Huang8b9513e2020-07-13 22:05:39 -070087 error!(format!("exit code: {}", output.status));
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -070088 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.
96pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
97 let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
Haibo Huang8b9513e2020-07-13 22:05:39 -070098 run_command("llvm-config", &path, arguments)
99}
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700100
Haibo Huang8b9513e2020-07-13 22:05:39 -0700101/// A struct that prints errors encountered when attempting to execute console
102/// commands on drop if not discarded.
103#[derive(Default)]
104pub struct CommandErrorPrinter {
Joel Galenson83337ed2021-05-19 14:55:23 -0700105 discard: bool,
Haibo Huang8b9513e2020-07-13 22:05:39 -0700106}
107
108impl CommandErrorPrinter {
109 pub fn discard(mut self) {
110 self.discard = true;
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700111 }
Haibo Huang8b9513e2020-07-13 22:05:39 -0700112}
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700113
Haibo Huang8b9513e2020-07-13 22:05:39 -0700114impl 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 Galenson83337ed2021-05-19 14:55:23 -0700128 errors
129 .iter()
130 .map(|e| format!("\"{}\"", e))
131 .collect::<Vec<_>>()
132 .join("\n "),
Haibo Huang8b9513e2020-07-13 22:05:39 -0700133 )
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 Galenson83337ed2021-05-19 14:55:23 -0700142 errors
143 .iter()
144 .map(|e| format!("\"{}\"", e))
145 .collect::<Vec<_>>()
146 .join("\n "),
Haibo Huang8b9513e2020-07-13 22:05:39 -0700147 )
148 }
149 }
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700150}
151
152/// Returns the paths to and the filenames of the files matching the supplied
153/// filename patterns in the supplied directory.
154fn 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.
191fn 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.
209pub 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 Huang8b9513e2020-07-13 22:05:39 -0700238 if let Some(output) = run_command("xcode-select", "xcode-select", &["--print-path"]) {
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700239 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 Galenson83337ed2021-05-19 14:55:23 -0700248 for directory in env::split_paths(&path) {
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700249 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}