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 OS X.
35 const DIRECTORIES_OSX: &[&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 command was
50 /// successfully executed.
run_command(command: &str, arguments: &[&str]) -> Option<String>51 fn run_command(command: &str, arguments: &[&str]) -> Option<String> {
52     let output = Command::new(command).args(arguments).output().ok()?;
53     Some(String::from_utf8_lossy(&output.stdout).into_owned())
54 }
55 
56 /// Executes `llvm-config`, returning the `stdout` output if the command was successfully executed.
run_llvm_config(arguments: &[&str]) -> Result<String, String>57 pub fn run_llvm_config(arguments: &[&str]) -> Result<String, String> {
58     let command = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
59     match run_command(&command, arguments) {
60         Some(output) => Ok(output),
61         None => Err(format!(
62             "couldn't execute `llvm-config {}`, set the LLVM_CONFIG_PATH environment variable to a \
63             path to a valid `llvm-config` executable",
64             arguments.join(" "),
65         )),
66     }
67 }
68 
69 /// Returns the paths to and the filenames of the files matching the supplied filename patterns in
70 /// the supplied directory.
search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)>71 fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
72     // Join the directory to the filename patterns to obtain the path patterns.
73     let paths = filenames.iter().filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned));
74 
75     // Prevent wildcards from matching path separators.
76     let mut options = MatchOptions::new();
77     options.require_literal_separator = true;
78 
79     paths.flat_map(|p| {
80         if let Ok(paths) = glob::glob_with(&p, &options) {
81             paths.filter_map(Result::ok).collect()
82         } else {
83             vec![]
84         }
85     }).filter_map(|p| {
86         let filename = p.file_name().and_then(|f| f.to_str())?;
87         Some((directory.to_owned(), filename.into()))
88     }).collect::<Vec<_>>()
89 }
90 
91 /// Returns the paths to and the filenames of the files matching the supplied filename patterns in
92 /// the supplied directory, checking any relevant sibling directories.
search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)>93 fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
94     let mut results = search_directory(directory, filenames);
95 
96     // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory while
97     // `libclang.lib` is usually found in the LLVM `lib` directory. To keep things
98     // consistent with other platforms, only LLVM `lib` directories are included in the
99     // backup search directory globs so we need to search the LLVM `bin` directory here.
100     if cfg!(target_os="windows") && directory.ends_with("lib") {
101         let sibling = directory.parent().unwrap().join("bin");
102         results.extend(search_directory(&sibling, filenames).into_iter());
103     }
104 
105     results
106 }
107 
108 /// Returns the paths to and the filenames of the `libclang` static or dynamic libraries matching
109 /// the supplied filename patterns.
search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)>110 pub fn search_libclang_directories(files: &[String], variable: &str) -> Vec<(PathBuf, String)> {
111     // Search the directory provided by the relevant environment variable.
112     if let Ok(directory) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
113         return search_directories(&directory, files);
114     }
115 
116     let mut found = vec![];
117 
118     // Search the toolchain directory in the directory provided by `xcode-select --print-path`.
119     if cfg!(target_os="macos") {
120         if let Some(output) = run_command("xcode-select", &["--print-path"]) {
121             let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
122             let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
123             found.extend(search_directories(&directory, files));
124         }
125     }
126 
127     // Search the `bin` and `lib` directories in directory provided by `llvm-config --prefix`.
128     if let Ok(output) = run_llvm_config(&["--prefix"]) {
129         let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
130         found.extend(search_directories(&directory.join("bin"), files));
131         found.extend(search_directories(&directory.join("lib"), files));
132     }
133 
134     // Search the directories provided by the `LD_LIBRARY_PATH` environment variable.
135     if let Ok(path) = env::var("LD_LIBRARY_PATH") {
136         for directory in path.split(':').map(Path::new) {
137             found.extend(search_directories(&directory, files));
138         }
139     }
140 
141     // Determine the `libclang` directory patterns.
142     let directories = if cfg!(any(target_os="freebsd", target_os="linux")) {
143         DIRECTORIES_LINUX
144     } else if cfg!(target_os="macos") {
145         DIRECTORIES_OSX
146     } else if cfg!(target_os="windows") {
147         DIRECTORIES_WINDOWS
148     } else {
149         &[]
150     };
151 
152     // Search the directories provided by the `libclang` directory patterns.
153     let mut options = MatchOptions::new();
154     options.case_sensitive = false;
155     options.require_literal_separator = true;
156     for directory in directories.iter().rev() {
157         if let Ok(directories) = glob::glob_with(directory, &options) {
158             for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
159                 found.extend(search_directories(&directory, files));
160             }
161         }
162     }
163 
164     found
165 }
166