blob: 958ef9b2fd2da0b07d13f99307c78cf860c7a22b [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
17use std::env;
18use std::path::{Path, PathBuf};
19use std::process::Command;
20
21use glob::MatchOptions;
22
23/// `libclang` directory patterns for FreeBSD and Linux.
24const 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.
35const 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.
43const 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.
51fn 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.
81pub 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.
98fn 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.
135fn 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.
153pub 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}