daqing_rucaptcha 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +188 -0
  3. data/Rakefile +71 -0
  4. data/app/controllers/ru_captcha/captcha_controller.rb +14 -0
  5. data/config/locales/rucaptcha.de-DE.yml +3 -0
  6. data/config/locales/rucaptcha.en.yml +3 -0
  7. data/config/locales/rucaptcha.pt-BR.yml +3 -0
  8. data/config/locales/rucaptcha.zh-CN.yml +3 -0
  9. data/config/locales/rucaptcha.zh-TW.yml +3 -0
  10. data/config/routes.rb +3 -0
  11. data/ext/rucaptcha/Cargo.lock +1226 -0
  12. data/ext/rucaptcha/Cargo.toml +14 -0
  13. data/ext/rucaptcha/extconf.rb +4 -0
  14. data/ext/rucaptcha/fonts/FuzzyBubbles-Regular.ttf +0 -0
  15. data/ext/rucaptcha/fonts/Handlee-Regular.ttf +0 -0
  16. data/ext/rucaptcha/src/captcha.rs +341 -0
  17. data/ext/rucaptcha/src/lib.rs +30 -0
  18. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/common.rs +355 -0
  19. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/dynamic.rs +257 -0
  20. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/macros.rs +38 -0
  21. data/ext/rucaptcha/target/release/build/num-bigint-34c75daf72c2b049/out/radix_bases.rs +780 -0
  22. data/ext/rucaptcha/target/release/build/rb-sys-6bdd5b2895b9570a/out/bindings-0.9.78-arm64-darwin22-3.2.2.rs +19775 -0
  23. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/consts.rs +2248 -0
  24. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/op.rs +1030 -0
  25. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/tests.rs +20565 -0
  26. data/lib/rucaptcha/cache.rb +12 -0
  27. data/lib/rucaptcha/configuration.rb +21 -0
  28. data/lib/rucaptcha/controller_helpers.rb +90 -0
  29. data/lib/rucaptcha/engine.rb +15 -0
  30. data/lib/rucaptcha/errors/configuration.rb +5 -0
  31. data/lib/rucaptcha/version.rb +3 -0
  32. data/lib/rucaptcha/view_helpers.rb +22 -0
  33. data/lib/rucaptcha.rb +86 -0
  34. metadata +103 -0
@@ -0,0 +1,355 @@
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
+ #[cfg(test)]
95
+ pub static RUN_COMMAND_MOCK: std::sync::Mutex<
96
+ Option<Box<dyn Fn(&str, &str, &[&str]) -> Option<String> + Send + Sync + 'static>>,
97
+ > = std::sync::Mutex::new(None);
98
+
99
+ /// Executes a command and returns the `stdout` output if the command was
100
+ /// successfully executed (errors are added to `COMMAND_ERRORS`).
101
+ fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> {
102
+ #[cfg(test)]
103
+ if let Some(command) = &*RUN_COMMAND_MOCK.lock().unwrap() {
104
+ return command(name, path, arguments);
105
+ }
106
+
107
+ let output = match Command::new(path).args(arguments).output() {
108
+ Ok(output) => output,
109
+ Err(error) => {
110
+ let message = format!("error: {}", error);
111
+ add_command_error(name, path, arguments, message);
112
+ return None;
113
+ }
114
+ };
115
+
116
+ if output.status.success() {
117
+ Some(String::from_utf8_lossy(&output.stdout).into_owned())
118
+ } else {
119
+ let message = format!("exit code: {}", output.status);
120
+ add_command_error(name, path, arguments, message);
121
+ None
122
+ }
123
+ }
124
+
125
+ /// Executes the `llvm-config` command and returns the `stdout` output if the
126
+ /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
127
+ pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
128
+ let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
129
+ run_command("llvm-config", &path, arguments)
130
+ }
131
+
132
+ /// Executes the `xcode-select` command and returns the `stdout` output if the
133
+ /// command was successfully executed (errors are added to `COMMAND_ERRORS`).
134
+ pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
135
+ run_command("xcode-select", "xcode-select", arguments)
136
+ }
137
+
138
+ //================================================
139
+ // Search Directories
140
+ //================================================
141
+ // These search directories are listed in order of
142
+ // preference, so if multiple `libclang` instances
143
+ // are found when searching matching directories,
144
+ // the `libclang` instances from earlier
145
+ // directories will be preferred (though version
146
+ // takes precedence over location).
147
+ //================================================
148
+
149
+ /// `libclang` directory patterns for Haiku.
150
+ const DIRECTORIES_HAIKU: &[&str] = &[
151
+ "/boot/home/config/non-packaged/develop/lib",
152
+ "/boot/home/config/non-packaged/lib",
153
+ "/boot/system/non-packaged/develop/lib",
154
+ "/boot/system/non-packaged/lib",
155
+ "/boot/system/develop/lib",
156
+ "/boot/system/lib",
157
+ ];
158
+
159
+ /// `libclang` directory patterns for Linux (and FreeBSD).
160
+ const DIRECTORIES_LINUX: &[&str] = &[
161
+ "/usr/local/llvm*/lib*",
162
+ "/usr/local/lib*/*/*",
163
+ "/usr/local/lib*/*",
164
+ "/usr/local/lib*",
165
+ "/usr/lib*/*/*",
166
+ "/usr/lib*/*",
167
+ "/usr/lib*",
168
+ ];
169
+
170
+ /// `libclang` directory patterns for macOS.
171
+ const DIRECTORIES_MACOS: &[&str] = &[
172
+ "/usr/local/opt/llvm*/lib/llvm*/lib",
173
+ "/Library/Developer/CommandLineTools/usr/lib",
174
+ "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
175
+ "/usr/local/opt/llvm*/lib",
176
+ ];
177
+
178
+ /// `libclang` directory patterns for Windows.
179
+ ///
180
+ /// The boolean indicates whether the directory pattern should be used when
181
+ /// compiling for an MSVC target environment.
182
+ const DIRECTORIES_WINDOWS: &[(&str, bool)] = &[
183
+ // LLVM + Clang can be installed using Scoop (https://scoop.sh).
184
+ // Other Windows package managers install LLVM + Clang to other listed
185
+ // system-wide directories.
186
+ ("C:\\Users\\*\\scoop\\apps\\llvm\\current\\lib", true),
187
+ ("C:\\MSYS*\\MinGW*\\lib", false),
188
+ ("C:\\Program Files*\\LLVM\\lib", true),
189
+ ("C:\\LLVM\\lib", true),
190
+ // LLVM + Clang can be installed as a component of Visual Studio.
191
+ // https://github.com/KyleMayes/clang-sys/issues/121
192
+ ("C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\lib", true),
193
+ ];
194
+
195
+ /// `libclang` directory patterns for illumos
196
+ const DIRECTORIES_ILLUMOS: &[&str] = &[
197
+ "/opt/ooce/llvm-*/lib",
198
+ "/opt/ooce/clang-*/lib",
199
+ ];
200
+
201
+ //================================================
202
+ // Searching
203
+ //================================================
204
+
205
+ /// Finds the files in a directory that match one or more filename glob patterns
206
+ /// and returns the paths to and filenames of those files.
207
+ fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
208
+ // Escape the specified directory in case it contains characters that have
209
+ // special meaning in glob patterns (e.g., `[` or `]`).
210
+ let directory = Pattern::escape(directory.to_str().unwrap());
211
+ let directory = Path::new(&directory);
212
+
213
+ // Join the escaped directory to the filename glob patterns to obtain
214
+ // complete glob patterns for the files being searched for.
215
+ let paths = filenames
216
+ .iter()
217
+ .map(|f| directory.join(f).to_str().unwrap().to_owned());
218
+
219
+ // Prevent wildcards from matching path separators to ensure that the search
220
+ // is limited to the specified directory.
221
+ let mut options = MatchOptions::new();
222
+ options.require_literal_separator = true;
223
+
224
+ paths
225
+ .map(|p| glob::glob_with(&p, options))
226
+ .filter_map(Result::ok)
227
+ .flatten()
228
+ .filter_map(|p| {
229
+ let path = p.ok()?;
230
+ let filename = path.file_name()?.to_str().unwrap();
231
+
232
+ // The `libclang_shared` library has been renamed to `libclang-cpp`
233
+ // in Clang 10. This can cause instances of this library (e.g.,
234
+ // `libclang-cpp.so.10`) to be matched by patterns looking for
235
+ // instances of `libclang`.
236
+ if filename.contains("-cpp.") {
237
+ return None;
238
+ }
239
+
240
+ Some((directory.to_owned(), filename.into()))
241
+ })
242
+ .collect::<Vec<_>>()
243
+ }
244
+
245
+ /// Finds the files in a directory (and any relevant sibling directories) that
246
+ /// match one or more filename glob patterns and returns the paths to and
247
+ /// filenames of those files.
248
+ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
249
+ let mut results = search_directory(directory, filenames);
250
+
251
+ // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
252
+ // while `libclang.lib` is usually found in the LLVM `lib` directory. To
253
+ // keep things consistent with other platforms, only LLVM `lib` directories
254
+ // are included in the backup search directory globs so we need to search
255
+ // the LLVM `bin` directory here.
256
+ if target_os!("windows") && directory.ends_with("lib") {
257
+ let sibling = directory.parent().unwrap().join("bin");
258
+ results.extend(search_directory(&sibling, filenames).into_iter());
259
+ }
260
+
261
+ results
262
+ }
263
+
264
+ /// Finds the `libclang` static or dynamic libraries matching one or more
265
+ /// filename glob patterns and returns the paths to and filenames of those files.
266
+ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
267
+ // Search only the path indicated by the relevant environment variable
268
+ // (e.g., `LIBCLANG_PATH`) if it is set.
269
+ if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
270
+ // Check if the path is a matching file.
271
+ if let Some(parent) = path.parent() {
272
+ let filename = path.file_name().unwrap().to_str().unwrap();
273
+ let libraries = search_directories(parent, filenames);
274
+ if libraries.iter().any(|(_, f)| f == filename) {
275
+ return vec![(parent.into(), filename.into())];
276
+ }
277
+ }
278
+
279
+ // Check if the path is directory containing a matching file.
280
+ return search_directories(&path, filenames);
281
+ }
282
+
283
+ let mut found = vec![];
284
+
285
+ // Search the `bin` and `lib` directories in the directory returned by
286
+ // `llvm-config --prefix`.
287
+ if let Some(output) = run_llvm_config(&["--prefix"]) {
288
+ let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
289
+ found.extend(search_directories(&directory.join("bin"), filenames));
290
+ found.extend(search_directories(&directory.join("lib"), filenames));
291
+ found.extend(search_directories(&directory.join("lib64"), filenames));
292
+ }
293
+
294
+ // Search the toolchain directory in the directory returned by
295
+ // `xcode-select --print-path`.
296
+ if target_os!("macos") {
297
+ if let Some(output) = run_xcode_select(&["--print-path"]) {
298
+ let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
299
+ let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
300
+ found.extend(search_directories(&directory, filenames));
301
+ }
302
+ }
303
+
304
+ // Search the directories in the `LD_LIBRARY_PATH` environment variable.
305
+ if let Ok(path) = env::var("LD_LIBRARY_PATH") {
306
+ for directory in env::split_paths(&path) {
307
+ found.extend(search_directories(&directory, filenames));
308
+ }
309
+ }
310
+
311
+ // Determine the `libclang` directory patterns.
312
+ let directories: Vec<&str> = if target_os!("haiku") {
313
+ DIRECTORIES_HAIKU.into()
314
+ } else if target_os!("linux") || target_os!("freebsd") {
315
+ DIRECTORIES_LINUX.into()
316
+ } else if target_os!("macos") {
317
+ DIRECTORIES_MACOS.into()
318
+ } else if target_os!("windows") {
319
+ let msvc = target_env!("msvc");
320
+ DIRECTORIES_WINDOWS
321
+ .iter()
322
+ .filter(|d| d.1 || !msvc)
323
+ .map(|d| d.0)
324
+ .collect()
325
+ } else if target_os!("illumos") {
326
+ DIRECTORIES_ILLUMOS.into()
327
+ } else {
328
+ vec![]
329
+ };
330
+
331
+ // We use temporary directories when testing the build script so we'll
332
+ // remove the prefixes that make the directories absolute.
333
+ let directories = if test!() {
334
+ directories
335
+ .iter()
336
+ .map(|d| d.strip_prefix('/').or_else(|| d.strip_prefix("C:\\")).unwrap_or(d))
337
+ .collect::<Vec<_>>()
338
+ } else {
339
+ directories
340
+ };
341
+
342
+ // Search the directories provided by the `libclang` directory patterns.
343
+ let mut options = MatchOptions::new();
344
+ options.case_sensitive = false;
345
+ options.require_literal_separator = true;
346
+ for directory in directories.iter() {
347
+ if let Ok(directories) = glob::glob_with(directory, options) {
348
+ for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
349
+ found.extend(search_directories(&directory, filenames));
350
+ }
351
+ }
352
+ }
353
+
354
+ found
355
+ }
@@ -0,0 +1,257 @@
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 target_os!("linux") || target_os!("freebsd") {
54
+ let class = parse_elf_header(path).map_err(|e| e.to_string())?;
55
+
56
+ if target_pointer_width!("32") && class != 1 {
57
+ return Err("invalid ELF class (64-bit)".into());
58
+ }
59
+
60
+ if target_pointer_width!("64") && class != 2 {
61
+ return Err("invalid ELF class (32-bit)".into());
62
+ }
63
+
64
+ Ok(())
65
+ } else if target_os!("windows") {
66
+ let magic = parse_pe_header(path).map_err(|e| e.to_string())?;
67
+
68
+ if target_pointer_width!("32") && magic != 267 {
69
+ return Err("invalid DLL (64-bit)".into());
70
+ }
71
+
72
+ if 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 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 target_os!("freebsd") || target_os!("haiku") || target_os!("netbsd") || target_os!("openbsd") {
125
+ // Some BSD distributions don't create a `libclang.so` symlink either,
126
+ // but use a different naming scheme for versioned files (e.g.,
127
+ // `libclang.so.7.0`).
128
+ files.push("libclang.so.*".into());
129
+ }
130
+
131
+ if target_os!("windows") {
132
+ // The official LLVM build uses `libclang.dll` on Windows instead of
133
+ // `clang.dll`. However, unofficial builds such as MinGW use `clang.dll`.
134
+ files.push("libclang.dll".into());
135
+ }
136
+
137
+ // Find and validate `libclang` shared libraries and collect the versions.
138
+ let mut valid = vec![];
139
+ let mut invalid = vec![];
140
+ for (directory, filename) in common::search_libclang_directories(&files, "LIBCLANG_PATH") {
141
+ let path = directory.join(&filename);
142
+ match validate_library(&path) {
143
+ Ok(()) => {
144
+ let version = parse_version(&filename);
145
+ valid.push((directory, filename, version))
146
+ }
147
+ Err(message) => invalid.push(format!("({}: {})", path.display(), message)),
148
+ }
149
+ }
150
+
151
+ if !valid.is_empty() {
152
+ return Ok(valid);
153
+ }
154
+
155
+ let message = format!(
156
+ "couldn't find any valid shared libraries matching: [{}], set the \
157
+ `LIBCLANG_PATH` environment variable to a path where one of these files \
158
+ can be found (invalid: [{}])",
159
+ files
160
+ .iter()
161
+ .map(|f| format!("'{}'", f))
162
+ .collect::<Vec<_>>()
163
+ .join(", "),
164
+ invalid.join(", "),
165
+ );
166
+
167
+ Err(message)
168
+ }
169
+
170
+ /// Finds the "best" `libclang` shared library and returns the directory and
171
+ /// filename of that library.
172
+ pub fn find(runtime: bool) -> Result<(PathBuf, String), String> {
173
+ search_libclang_directories(runtime)?
174
+ .iter()
175
+ // We want to find the `libclang` shared library with the highest
176
+ // version number, hence `max_by_key` below.
177
+ //
178
+ // However, in the case where there are multiple such `libclang` shared
179
+ // libraries, we want to use the order in which they appeared in the
180
+ // list returned by `search_libclang_directories` as a tiebreaker since
181
+ // that function returns `libclang` shared libraries in descending order
182
+ // of preference by how they were found.
183
+ //
184
+ // `max_by_key`, perhaps surprisingly, returns the *last* item with the
185
+ // maximum key rather than the first which results in the opposite of
186
+ // the tiebreaking behavior we want. This is easily fixed by reversing
187
+ // the list first.
188
+ .rev()
189
+ .max_by_key(|f| &f.2)
190
+ .cloned()
191
+ .map(|(path, filename, _)| (path, filename))
192
+ .ok_or_else(|| "unreachable".into())
193
+ }
194
+
195
+ //================================================
196
+ // Linking
197
+ //================================================
198
+
199
+ /// Finds and links to a `libclang` shared library.
200
+ #[cfg(not(feature = "runtime"))]
201
+ pub fn link() {
202
+ let cep = common::CommandErrorPrinter::default();
203
+
204
+ use std::fs;
205
+
206
+ let (directory, filename) = find(false).unwrap();
207
+ println!("cargo:rustc-link-search={}", directory.display());
208
+
209
+ if cfg!(all(target_os = "windows", target_env = "msvc")) {
210
+ // Find the `libclang` stub static library required for the MSVC
211
+ // toolchain.
212
+ let lib = if !directory.ends_with("bin") {
213
+ directory
214
+ } else {
215
+ directory.parent().unwrap().join("lib")
216
+ };
217
+
218
+ if lib.join("libclang.lib").exists() {
219
+ println!("cargo:rustc-link-search={}", lib.display());
220
+ } else if lib.join("libclang.dll.a").exists() {
221
+ // MSYS and MinGW use `libclang.dll.a` instead of `libclang.lib`.
222
+ // It is linkable with the MSVC linker, but Rust doesn't recognize
223
+ // the `.a` suffix, so we need to copy it with a different name.
224
+ //
225
+ // FIXME: Maybe we can just hardlink or symlink it?
226
+ let out = env::var("OUT_DIR").unwrap();
227
+ fs::copy(
228
+ lib.join("libclang.dll.a"),
229
+ Path::new(&out).join("libclang.lib"),
230
+ )
231
+ .unwrap();
232
+ println!("cargo:rustc-link-search=native={}", out);
233
+ } else {
234
+ panic!(
235
+ "using '{}', so 'libclang.lib' or 'libclang.dll.a' must be \
236
+ available in {}",
237
+ filename,
238
+ lib.display(),
239
+ );
240
+ }
241
+
242
+ println!("cargo:rustc-link-lib=dylib=libclang");
243
+ } else {
244
+ let name = filename.trim_start_matches("lib");
245
+
246
+ // Strip extensions and trailing version numbers (e.g., the `.so.7.0` in
247
+ // `libclang.so.7.0`).
248
+ let name = match name.find(".dylib").or_else(|| name.find(".so")) {
249
+ Some(index) => &name[0..index],
250
+ None => name,
251
+ };
252
+
253
+ println!("cargo:rustc-link-lib=dylib={}", name);
254
+ }
255
+
256
+ cep.discard();
257
+ }
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ macro_rules! test {
4
+ () => (cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok());
5
+ }
6
+
7
+ macro_rules! target_os {
8
+ ($os:expr) => {
9
+ if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() {
10
+ let var = ::std::env::var("_CLANG_SYS_TEST_OS");
11
+ var.map_or(false, |v| v == $os)
12
+ } else {
13
+ cfg!(target_os = $os)
14
+ }
15
+ };
16
+ }
17
+
18
+ macro_rules! target_pointer_width {
19
+ ($pointer_width:expr) => {
20
+ if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() {
21
+ let var = ::std::env::var("_CLANG_SYS_TEST_POINTER_WIDTH");
22
+ var.map_or(false, |v| v == $pointer_width)
23
+ } else {
24
+ cfg!(target_pointer_width = $pointer_width)
25
+ }
26
+ };
27
+ }
28
+
29
+ macro_rules! target_env {
30
+ ($env:expr) => {
31
+ if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() {
32
+ let var = ::std::env::var("_CLANG_SYS_TEST_ENV");
33
+ var.map_or(false, |v| v == $env)
34
+ } else {
35
+ cfg!(target_env = $env)
36
+ }
37
+ };
38
+ }