y-rb 0.1.4.alpha.3-x86_64-linux-musl → 0.1.4.alpha.4-x86_64-linux-musl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,319 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ extern crate glob;
4
+
5
+ use std::cell::RefCell;
6
+ use std::collections::HashMap;
7
+ use std::env;
8
+ use std::path::{Path, PathBuf};
9
+ use std::process::Command;
10
+
11
+ use glob::{MatchOptions, Pattern};
12
+
13
+ //================================================
14
+ // Commands
15
+ //================================================
16
+
17
+ thread_local! {
18
+ /// The errors encountered by the build script while executing commands.
19
+ static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
20
+ }
21
+
22
+ /// Adds an error encountered by the build script while executing a command.
23
+ fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) {
24
+ COMMAND_ERRORS.with(|e| {
25
+ e.borrow_mut()
26
+ .entry(name.into())
27
+ .or_insert_with(Vec::new)
28
+ .push(format!(
29
+ "couldn't execute `{} {}` (path={}) ({})",
30
+ name,
31
+ arguments.join(" "),
32
+ path,
33
+ message,
34
+ ))
35
+ });
36
+ }
37
+
38
+ /// A struct that prints the errors encountered by the build script while
39
+ /// executing commands when dropped (unless explictly discarded).
40
+ ///
41
+ /// This is handy because we only want to print these errors when the build
42
+ /// script fails to link to an instance of `libclang`. For example, if
43
+ /// `llvm-config` couldn't be executed but an instance of `libclang` was found
44
+ /// anyway we don't want to pollute the build output with irrelevant errors.
45
+ #[derive(Default)]
46
+ pub struct CommandErrorPrinter {
47
+ discard: bool,
48
+ }
49
+
50
+ impl CommandErrorPrinter {
51
+ pub fn discard(mut self) {
52
+ self.discard = true;
53
+ }
54
+ }
55
+
56
+ impl Drop for CommandErrorPrinter {
57
+ fn drop(&mut self) {
58
+ if self.discard {
59
+ return;
60
+ }
61
+
62
+ let errors = COMMAND_ERRORS.with(|e| e.borrow().clone());
63
+
64
+ if let Some(errors) = errors.get("llvm-config") {
65
+ println!(
66
+ "cargo:warning=could not execute `llvm-config` one or more \
67
+ times, if the LLVM_CONFIG_PATH environment variable is set to \
68
+ a full path to valid `llvm-config` executable it will be used \
69
+ to try to find an instance of `libclang` on your system: {}",
70
+ errors
71
+ .iter()
72
+ .map(|e| format!("\"{}\"", e))
73
+ .collect::<Vec<_>>()
74
+ .join("\n "),
75
+ )
76
+ }
77
+
78
+ if let Some(errors) = errors.get("xcode-select") {
79
+ println!(
80
+ "cargo:warning=could not execute `xcode-select` one or more \
81
+ times, if a valid instance of this executable is on your PATH \
82
+ it will be used to try to find an instance of `libclang` on \
83
+ your system: {}",
84
+ errors
85
+ .iter()
86
+ .map(|e| format!("\"{}\"", e))
87
+ .collect::<Vec<_>>()
88
+ .join("\n "),
89
+ )
90
+ }
91
+ }
92
+ }
93
+
94
+ /// Executes a command and returns the `stdout` output if the command was
95
+ /// successfully executed (errors are added to `COMMAND_ERRORS`).
96
+ fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> {
97
+ let output = match Command::new(path).args(arguments).output() {
98
+ Ok(output) => output,
99
+ Err(error) => {
100
+ let message = format!("error: {}", error);
101
+ add_command_error(name, path, arguments, message);
102
+ return None;
103
+ }
104
+ };
105
+
106
+ if output.status.success() {
107
+ Some(String::from_utf8_lossy(&output.stdout).into_owned())
108
+ } else {
109
+ let message = format!("exit code: {}", output.status);
110
+ add_command_error(name, path, arguments, message);
111
+ None
112
+ }
113
+ }
114
+
115
+ /// Executes the `llvm-config` command and returns the `stdout` output if the
116
+ /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
117
+ pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
118
+ let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
119
+ run_command("llvm-config", &path, arguments)
120
+ }
121
+
122
+ /// Executes the `xcode-select` command and returns the `stdout` output if the
123
+ /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
124
+ pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
125
+ run_command("xcode-select", "xcode-select", arguments)
126
+ }
127
+
128
+ //================================================
129
+ // Search Directories
130
+ //================================================
131
+
132
+ /// `libclang` directory patterns for Haiku.
133
+ const DIRECTORIES_HAIKU: &[&str] = &[
134
+ "/boot/system/lib",
135
+ "/boot/system/develop/lib",
136
+ "/boot/system/non-packaged/lib",
137
+ "/boot/system/non-packaged/develop/lib",
138
+ "/boot/home/config/non-packaged/lib",
139
+ "/boot/home/config/non-packaged/develop/lib",
140
+ ];
141
+
142
+ /// `libclang` directory patterns for Linux (and FreeBSD).
143
+ const DIRECTORIES_LINUX: &[&str] = &[
144
+ "/usr/lib*",
145
+ "/usr/lib*/*",
146
+ "/usr/lib*/*/*",
147
+ "/usr/local/lib*",
148
+ "/usr/local/lib*/*",
149
+ "/usr/local/lib*/*/*",
150
+ "/usr/local/llvm*/lib*",
151
+ ];
152
+
153
+ /// `libclang` directory patterns for macOS.
154
+ const DIRECTORIES_MACOS: &[&str] = &[
155
+ "/usr/local/opt/llvm*/lib",
156
+ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
157
+ "/Library/Developer/CommandLineTools/usr/lib",
158
+ "/usr/local/opt/llvm*/lib/llvm*/lib",
159
+ ];
160
+
161
+ /// `libclang` directory patterns for Windows.
162
+ const DIRECTORIES_WINDOWS: &[&str] = &[
163
+ "C:\\LLVM\\lib",
164
+ "C:\\Program Files*\\LLVM\\lib",
165
+ "C:\\MSYS*\\MinGW*\\lib",
166
+ // LLVM + Clang can be installed as a component of Visual Studio.
167
+ // https://github.com/KyleMayes/clang-sys/issues/121
168
+ "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin",
169
+ // LLVM + Clang can be installed using Scoop (https://scoop.sh).
170
+ // Other Windows package managers install LLVM + Clang to previously listed
171
+ // system-wide directories.
172
+ "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
173
+ ];
174
+
175
+ /// `libclang` directory patterns for illumos
176
+ const DIRECTORIES_ILLUMOS: &[&str] = &[
177
+ "/opt/ooce/clang-*/lib",
178
+ "/opt/ooce/llvm-*/lib",
179
+ ];
180
+
181
+ //================================================
182
+ // Searching
183
+ //================================================
184
+
185
+ /// Finds the files in a directory that match one or more filename glob patterns
186
+ /// and returns the paths to and filenames of those files.
187
+ fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
188
+ // Escape the specified directory in case it contains characters that have
189
+ // special meaning in glob patterns (e.g., `[` or `]`).
190
+ let directory = Pattern::escape(directory.to_str().unwrap());
191
+ let directory = Path::new(&directory);
192
+
193
+ // Join the escaped directory to the filename glob patterns to obtain
194
+ // complete glob patterns for the files being searched for.
195
+ let paths = filenames
196
+ .iter()
197
+ .map(|f| directory.join(f).to_str().unwrap().to_owned());
198
+
199
+ // Prevent wildcards from matching path separators to ensure that the search
200
+ // is limited to the specified directory.
201
+ let mut options = MatchOptions::new();
202
+ options.require_literal_separator = true;
203
+
204
+ paths
205
+ .map(|p| glob::glob_with(&p, options))
206
+ .filter_map(Result::ok)
207
+ .flatten()
208
+ .filter_map(|p| {
209
+ let path = p.ok()?;
210
+ let filename = path.file_name()?.to_str().unwrap();
211
+
212
+ // The `libclang_shared` library has been renamed to `libclang-cpp`
213
+ // in Clang 10. This can cause instances of this library (e.g.,
214
+ // `libclang-cpp.so.10`) to be matched by patterns looking for
215
+ // instances of `libclang`.
216
+ if filename.contains("-cpp.") {
217
+ return None;
218
+ }
219
+
220
+ Some((directory.to_owned(), filename.into()))
221
+ })
222
+ .collect::<Vec<_>>()
223
+ }
224
+
225
+ /// Finds the files in a directory (and any relevant sibling directories) that
226
+ /// match one or more filename glob patterns and returns the paths to and
227
+ /// filenames of those files.
228
+ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
229
+ let mut results = search_directory(directory, filenames);
230
+
231
+ // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
232
+ // while `libclang.lib` is usually found in the LLVM `lib` directory. To
233
+ // keep things consistent with other platforms, only LLVM `lib` directories
234
+ // are included in the backup search directory globs so we need to search
235
+ // the LLVM `bin` directory here.
236
+ if cfg!(target_os = "windows") && directory.ends_with("lib") {
237
+ let sibling = directory.parent().unwrap().join("bin");
238
+ results.extend(search_directory(&sibling, filenames).into_iter());
239
+ }
240
+
241
+ results
242
+ }
243
+
244
+ /// Finds the `libclang` static or dynamic libraries matching one or more
245
+ /// filename glob patterns and returns the paths to and filenames of those files.
246
+ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
247
+ // Search only the path indicated by the relevant environment variable
248
+ // (e.g., `LIBCLANG_PATH`) if it is set.
249
+ if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
250
+ // Check if the path is a matching file.
251
+ if let Some(parent) = path.parent() {
252
+ let filename = path.file_name().unwrap().to_str().unwrap();
253
+ let libraries = search_directories(parent, filenames);
254
+ if libraries.iter().any(|(_, f)| f == filename) {
255
+ return vec![(parent.into(), filename.into())];
256
+ }
257
+ }
258
+
259
+ // Check if the path is directory containing a matching file.
260
+ return search_directories(&path, filenames);
261
+ }
262
+
263
+ let mut found = vec![];
264
+
265
+ // Search the `bin` and `lib` directories in the directory returned by
266
+ // `llvm-config --prefix`.
267
+ if let Some(output) = run_llvm_config(&["--prefix"]) {
268
+ let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
269
+ found.extend(search_directories(&directory.join("bin"), filenames));
270
+ found.extend(search_directories(&directory.join("lib"), filenames));
271
+ found.extend(search_directories(&directory.join("lib64"), filenames));
272
+ }
273
+
274
+ // Search the toolchain directory in the directory returned by
275
+ // `xcode-select --print-path`.
276
+ if cfg!(target_os = "macos") {
277
+ if let Some(output) = run_xcode_select(&["--print-path"]) {
278
+ let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
279
+ let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
280
+ found.extend(search_directories(&directory, filenames));
281
+ }
282
+ }
283
+
284
+ // Search the directories in the `LD_LIBRARY_PATH` environment variable.
285
+ if let Ok(path) = env::var("LD_LIBRARY_PATH") {
286
+ for directory in env::split_paths(&path) {
287
+ found.extend(search_directories(&directory, filenames));
288
+ }
289
+ }
290
+
291
+ // Determine the `libclang` directory patterns.
292
+ let directories = if cfg!(target_os = "haiku") {
293
+ DIRECTORIES_HAIKU
294
+ } else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
295
+ DIRECTORIES_LINUX
296
+ } else if cfg!(target_os = "macos") {
297
+ DIRECTORIES_MACOS
298
+ } else if cfg!(target_os = "windows") {
299
+ DIRECTORIES_WINDOWS
300
+ } else if cfg!(target_os = "illumos") {
301
+ DIRECTORIES_ILLUMOS
302
+ } else {
303
+ &[]
304
+ };
305
+
306
+ // Search the directories provided by the `libclang` directory patterns.
307
+ let mut options = MatchOptions::new();
308
+ options.case_sensitive = false;
309
+ options.require_literal_separator = true;
310
+ for directory in directories.iter().rev() {
311
+ if let Ok(directories) = glob::glob_with(directory, options) {
312
+ for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
313
+ found.extend(search_directories(&directory, filenames));
314
+ }
315
+ }
316
+ }
317
+
318
+ found
319
+ }
@@ -0,0 +1,262 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ use std::env;
4
+ use std::fs::File;
5
+ use std::io::{self, Error, ErrorKind, Read, Seek, SeekFrom};
6
+ use std::path::{Path, PathBuf};
7
+
8
+ use super::common;
9
+
10
+ //================================================
11
+ // Validation
12
+ //================================================
13
+
14
+ /// Extracts the ELF class from the ELF header in a shared library.
15
+ fn parse_elf_header(path: &Path) -> io::Result<u8> {
16
+ let mut file = File::open(path)?;
17
+ let mut buffer = [0; 5];
18
+ file.read_exact(&mut buffer)?;
19
+ if buffer[..4] == [127, 69, 76, 70] {
20
+ Ok(buffer[4])
21
+ } else {
22
+ Err(Error::new(ErrorKind::InvalidData, "invalid ELF header"))
23
+ }
24
+ }
25
+
26
+ /// Extracts the magic number from the PE header in a shared library.
27
+ fn parse_pe_header(path: &Path) -> io::Result<u16> {
28
+ let mut file = File::open(path)?;
29
+
30
+ // Extract the header offset.
31
+ let mut buffer = [0; 4];
32
+ let start = SeekFrom::Start(0x3C);
33
+ file.seek(start)?;
34
+ file.read_exact(&mut buffer)?;
35
+ let offset = i32::from_le_bytes(buffer);
36
+
37
+ // Check the validity of the header.
38
+ file.seek(SeekFrom::Start(offset as u64))?;
39
+ file.read_exact(&mut buffer)?;
40
+ if buffer != [80, 69, 0, 0] {
41
+ return Err(Error::new(ErrorKind::InvalidData, "invalid PE header"));
42
+ }
43
+
44
+ // Extract the magic number.
45
+ let mut buffer = [0; 2];
46
+ file.seek(SeekFrom::Current(20))?;
47
+ file.read_exact(&mut buffer)?;
48
+ Ok(u16::from_le_bytes(buffer))
49
+ }
50
+
51
+ /// Checks that a `libclang` shared library matches the target platform.
52
+ fn validate_library(path: &Path) -> Result<(), String> {
53
+ if cfg!(any(target_os = "linux", target_os = "freebsd")) {
54
+ let class = parse_elf_header(path).map_err(|e| e.to_string())?;
55
+
56
+ if cfg!(target_pointer_width = "32") && class != 1 {
57
+ return Err("invalid ELF class (64-bit)".into());
58
+ }
59
+
60
+ if cfg!(target_pointer_width = "64") && class != 2 {
61
+ return Err("invalid ELF class (32-bit)".into());
62
+ }
63
+
64
+ Ok(())
65
+ } else if cfg!(target_os = "windows") {
66
+ let magic = parse_pe_header(path).map_err(|e| e.to_string())?;
67
+
68
+ if cfg!(target_pointer_width = "32") && magic != 267 {
69
+ return Err("invalid DLL (64-bit)".into());
70
+ }
71
+
72
+ if cfg!(target_pointer_width = "64") && magic != 523 {
73
+ return Err("invalid DLL (32-bit)".into());
74
+ }
75
+
76
+ Ok(())
77
+ } else {
78
+ Ok(())
79
+ }
80
+ }
81
+
82
+ //================================================
83
+ // Searching
84
+ //================================================
85
+
86
+ /// Extracts the version components in a `libclang` shared library filename.
87
+ fn parse_version(filename: &str) -> Vec<u32> {
88
+ let version = if let Some(version) = filename.strip_prefix("libclang.so.") {
89
+ version
90
+ } else if filename.starts_with("libclang-") {
91
+ &filename[9..filename.len() - 3]
92
+ } else {
93
+ return vec![];
94
+ };
95
+
96
+ version.split('.').map(|s| s.parse().unwrap_or(0)).collect()
97
+ }
98
+
99
+ /// Finds `libclang` shared libraries and returns the paths to, filenames of,
100
+ /// and versions of those shared libraries.
101
+ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Vec<u32>)>, String> {
102
+ let mut files = vec![format!(
103
+ "{}clang{}",
104
+ env::consts::DLL_PREFIX,
105
+ env::consts::DLL_SUFFIX
106
+ )];
107
+
108
+ if cfg!(target_os = "linux") {
109
+ // Some Linux distributions don't create a `libclang.so` symlink, so we
110
+ // need to look for versioned files (e.g., `libclang-3.9.so`).
111
+ files.push("libclang-*.so".into());
112
+
113
+ // Some Linux distributions don't create a `libclang.so` symlink and
114
+ // don't have versioned files as described above, so we need to look for
115
+ // suffix versioned files (e.g., `libclang.so.1`). However, `ld` cannot
116
+ // link to these files, so this will only be included when linking at
117
+ // runtime.
118
+ if runtime {
119
+ files.push("libclang.so.*".into());
120
+ files.push("libclang-*.so.*".into());
121
+ }
122
+ }
123
+
124
+ if cfg!(any(
125
+ target_os = "freebsd",
126
+ target_os = "haiku",
127
+ target_os = "netbsd",
128
+ target_os = "openbsd",
129
+ )) {
130
+ // Some BSD distributions don't create a `libclang.so` symlink either,
131
+ // but use a different naming scheme for versioned files (e.g.,
132
+ // `libclang.so.7.0`).
133
+ files.push("libclang.so.*".into());
134
+ }
135
+
136
+ if cfg!(target_os = "windows") {
137
+ // The official LLVM build uses `libclang.dll` on Windows instead of
138
+ // `clang.dll`. However, unofficial builds such as MinGW use `clang.dll`.
139
+ files.push("libclang.dll".into());
140
+ }
141
+
142
+ // Find and validate `libclang` shared libraries and collect the versions.
143
+ let mut valid = vec![];
144
+ let mut invalid = vec![];
145
+ for (directory, filename) in common::search_libclang_directories(&files, "LIBCLANG_PATH") {
146
+ let path = directory.join(&filename);
147
+ match validate_library(&path) {
148
+ Ok(()) => {
149
+ let version = parse_version(&filename);
150
+ valid.push((directory, filename, version))
151
+ }
152
+ Err(message) => invalid.push(format!("({}: {})", path.display(), message)),
153
+ }
154
+ }
155
+
156
+ if !valid.is_empty() {
157
+ return Ok(valid);
158
+ }
159
+
160
+ let message = format!(
161
+ "couldn't find any valid shared libraries matching: [{}], set the \
162
+ `LIBCLANG_PATH` environment variable to a path where one of these files \
163
+ can be found (invalid: [{}])",
164
+ files
165
+ .iter()
166
+ .map(|f| format!("'{}'", f))
167
+ .collect::<Vec<_>>()
168
+ .join(", "),
169
+ invalid.join(", "),
170
+ );
171
+
172
+ Err(message)
173
+ }
174
+
175
+ /// Finds the "best" `libclang` shared library and returns the directory and
176
+ /// filename of that library.
177
+ pub fn find(runtime: bool) -> Result<(PathBuf, String), String> {
178
+ search_libclang_directories(runtime)?
179
+ .iter()
180
+ // We want to find the `libclang` shared library with the highest
181
+ // version number, hence `max_by_key` below.
182
+ //
183
+ // However, in the case where there are multiple such `libclang` shared
184
+ // libraries, we want to use the order in which they appeared in the
185
+ // list returned by `search_libclang_directories` as a tiebreaker since
186
+ // that function returns `libclang` shared libraries in descending order
187
+ // of preference by how they were found.
188
+ //
189
+ // `max_by_key`, perhaps surprisingly, returns the *last* item with the
190
+ // maximum key rather than the first which results in the opposite of
191
+ // the tiebreaking behavior we want. This is easily fixed by reversing
192
+ // the list first.
193
+ .rev()
194
+ .max_by_key(|f| &f.2)
195
+ .cloned()
196
+ .map(|(path, filename, _)| (path, filename))
197
+ .ok_or_else(|| "unreachable".into())
198
+ }
199
+
200
+ //================================================
201
+ // Linking
202
+ //================================================
203
+
204
+ /// Finds and links to a `libclang` shared library.
205
+ #[cfg(not(feature = "runtime"))]
206
+ pub fn link() {
207
+ let cep = common::CommandErrorPrinter::default();
208
+
209
+ use std::fs;
210
+
211
+ let (directory, filename) = find(false).unwrap();
212
+ println!("cargo:rustc-link-search={}", directory.display());
213
+
214
+ if cfg!(all(target_os = "windows", target_env = "msvc")) {
215
+ // Find the `libclang` stub static library required for the MSVC
216
+ // toolchain.
217
+ let lib = if !directory.ends_with("bin") {
218
+ directory
219
+ } else {
220
+ directory.parent().unwrap().join("lib")
221
+ };
222
+
223
+ if lib.join("libclang.lib").exists() {
224
+ println!("cargo:rustc-link-search={}", lib.display());
225
+ } else if lib.join("libclang.dll.a").exists() {
226
+ // MSYS and MinGW use `libclang.dll.a` instead of `libclang.lib`.
227
+ // It is linkable with the MSVC linker, but Rust doesn't recognize
228
+ // the `.a` suffix, so we need to copy it with a different name.
229
+ //
230
+ // FIXME: Maybe we can just hardlink or symlink it?
231
+ let out = env::var("OUT_DIR").unwrap();
232
+ fs::copy(
233
+ lib.join("libclang.dll.a"),
234
+ Path::new(&out).join("libclang.lib"),
235
+ )
236
+ .unwrap();
237
+ println!("cargo:rustc-link-search=native={}", out);
238
+ } else {
239
+ panic!(
240
+ "using '{}', so 'libclang.lib' or 'libclang.dll.a' must be \
241
+ available in {}",
242
+ filename,
243
+ lib.display(),
244
+ );
245
+ }
246
+
247
+ println!("cargo:rustc-link-lib=dylib=libclang");
248
+ } else {
249
+ let name = filename.trim_start_matches("lib");
250
+
251
+ // Strip extensions and trailing version numbers (e.g., the `.so.7.0` in
252
+ // `libclang.so.7.0`).
253
+ let name = match name.find(".dylib").or_else(|| name.find(".so")) {
254
+ Some(index) => &name[0..index],
255
+ None => name,
256
+ };
257
+
258
+ println!("cargo:rustc-link-lib=dylib={}", name);
259
+ }
260
+
261
+ cep.discard();
262
+ }