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