1 | // SPDX-License-Identifier: Apache-2.0 |
2 | |
3 | //================================================ |
4 | // Macros |
5 | //================================================ |
6 | |
7 | #[cfg (feature = "runtime" )] |
8 | macro_rules! link { |
9 | ( |
10 | @LOAD: |
11 | $(#[doc=$doc:expr])* |
12 | #[cfg($cfg:meta)] |
13 | fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)* |
14 | ) => ( |
15 | $(#[doc=$doc])* |
16 | #[cfg($cfg)] |
17 | pub fn $name(library: &mut super::SharedLibrary) { |
18 | let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.ok(); |
19 | library.functions.$name = match symbol { |
20 | Some(s) => *s, |
21 | None => None, |
22 | }; |
23 | } |
24 | |
25 | #[cfg(not($cfg))] |
26 | pub fn $name(_: &mut super::SharedLibrary) {} |
27 | ); |
28 | |
29 | ( |
30 | @LOAD: |
31 | fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)* |
32 | ) => ( |
33 | link!(@LOAD: #[cfg(feature = "runtime" )] fn $name($($pname: $pty), *) $(-> $ret)*); |
34 | ); |
35 | |
36 | ( |
37 | $( |
38 | $(#[doc=$doc:expr] #[cfg($cfg:meta)])* |
39 | pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; |
40 | )+ |
41 | ) => ( |
42 | use std::cell::{RefCell}; |
43 | use std::fmt; |
44 | use std::sync::{Arc}; |
45 | use std::path::{Path, PathBuf}; |
46 | |
47 | /// The (minimum) version of a `libclang` shared library. |
48 | #[allow(missing_docs)] |
49 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
50 | pub enum Version { |
51 | V3_5 = 35, |
52 | V3_6 = 36, |
53 | V3_7 = 37, |
54 | V3_8 = 38, |
55 | V3_9 = 39, |
56 | V4_0 = 40, |
57 | V5_0 = 50, |
58 | V6_0 = 60, |
59 | V7_0 = 70, |
60 | V8_0 = 80, |
61 | V9_0 = 90, |
62 | V11_0 = 110, |
63 | V12_0 = 120, |
64 | V16_0 = 160, |
65 | V17_0 = 170, |
66 | } |
67 | |
68 | impl fmt::Display for Version { |
69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
70 | use Version::*; |
71 | match self { |
72 | V3_5 => write!(f, "3.5.x" ), |
73 | V3_6 => write!(f, "3.6.x" ), |
74 | V3_7 => write!(f, "3.7.x" ), |
75 | V3_8 => write!(f, "3.8.x" ), |
76 | V3_9 => write!(f, "3.9.x" ), |
77 | V4_0 => write!(f, "4.0.x" ), |
78 | V5_0 => write!(f, "5.0.x" ), |
79 | V6_0 => write!(f, "6.0.x" ), |
80 | V7_0 => write!(f, "7.0.x" ), |
81 | V8_0 => write!(f, "8.0.x" ), |
82 | V9_0 => write!(f, "9.0.x - 10.0.x" ), |
83 | V11_0 => write!(f, "11.0.x" ), |
84 | V12_0 => write!(f, "12.0.x - 15.0.x" ), |
85 | V16_0 => write!(f, "16.0.x" ), |
86 | V17_0 => write!(f, "17.0.x or later" ), |
87 | } |
88 | } |
89 | } |
90 | |
91 | /// The set of functions loaded dynamically. |
92 | #[derive(Debug, Default)] |
93 | pub struct Functions { |
94 | $( |
95 | $(#[doc=$doc] #[cfg($cfg)])* |
96 | pub $name: Option<unsafe extern fn($($pname: $pty), *) $(-> $ret)*>, |
97 | )+ |
98 | } |
99 | |
100 | /// A dynamically loaded instance of the `libclang` library. |
101 | #[derive(Debug)] |
102 | pub struct SharedLibrary { |
103 | library: libloading::Library, |
104 | path: PathBuf, |
105 | pub functions: Functions, |
106 | } |
107 | |
108 | impl SharedLibrary { |
109 | fn new(library: libloading::Library, path: PathBuf) -> Self { |
110 | Self { library, path, functions: Functions::default() } |
111 | } |
112 | |
113 | /// Returns the path to this `libclang` shared library. |
114 | pub fn path(&self) -> &Path { |
115 | &self.path |
116 | } |
117 | |
118 | /// Returns the (minimum) version of this `libclang` shared library. |
119 | /// |
120 | /// If this returns `None`, it indicates that the version is too old |
121 | /// to be supported by this crate (i.e., `3.4` or earlier). If the |
122 | /// version of this shared library is more recent than that fully |
123 | /// supported by this crate, the most recent fully supported version |
124 | /// will be returned. |
125 | pub fn version(&self) -> Option<Version> { |
126 | macro_rules! check { |
127 | ($fn:expr, $version:ident) => { |
128 | if self.library.get::<unsafe extern fn()>($fn).is_ok() { |
129 | return Some(Version::$version); |
130 | } |
131 | }; |
132 | } |
133 | |
134 | unsafe { |
135 | check!(b"clang_CXXMethod_isExplicit" , V17_0); |
136 | check!(b"clang_CXXMethod_isCopyAssignmentOperator" , V16_0); |
137 | check!(b"clang_Cursor_getVarDeclInitializer" , V12_0); |
138 | check!(b"clang_Type_getValueType" , V11_0); |
139 | check!(b"clang_Cursor_isAnonymousRecordDecl" , V9_0); |
140 | check!(b"clang_Cursor_getObjCPropertyGetterName" , V8_0); |
141 | check!(b"clang_File_tryGetRealPathName" , V7_0); |
142 | check!(b"clang_CXIndex_setInvocationEmissionPathOption" , V6_0); |
143 | check!(b"clang_Cursor_isExternalSymbol" , V5_0); |
144 | check!(b"clang_EvalResult_getAsLongLong" , V4_0); |
145 | check!(b"clang_CXXConstructor_isConvertingConstructor" , V3_9); |
146 | check!(b"clang_CXXField_isMutable" , V3_8); |
147 | check!(b"clang_Cursor_getOffsetOfField" , V3_7); |
148 | check!(b"clang_Cursor_getStorageClass" , V3_6); |
149 | check!(b"clang_Type_getNumTemplateArguments" , V3_5); |
150 | } |
151 | |
152 | None |
153 | } |
154 | } |
155 | |
156 | thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None)); |
157 | |
158 | /// Returns whether a `libclang` shared library is loaded on this thread. |
159 | pub fn is_loaded() -> bool { |
160 | LIBRARY.with(|l| l.borrow().is_some()) |
161 | } |
162 | |
163 | fn with_library<T, F>(f: F) -> Option<T> where F: FnOnce(&SharedLibrary) -> T { |
164 | LIBRARY.with(|l| { |
165 | match l.borrow().as_ref() { |
166 | Some(library) => Some(f(&library)), |
167 | _ => None, |
168 | } |
169 | }) |
170 | } |
171 | |
172 | $( |
173 | #[cfg_attr(feature="cargo-clippy" , allow(clippy::missing_safety_doc))] |
174 | #[cfg_attr(feature="cargo-clippy" , allow(clippy::too_many_arguments))] |
175 | $(#[doc=$doc] #[cfg($cfg)])* |
176 | pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { |
177 | let f = with_library(|library| { |
178 | if let Some(function) = library.functions.$name { |
179 | function |
180 | } else { |
181 | panic!( |
182 | r#" |
183 | A `libclang` function was called that is not supported by the loaded `libclang` instance. |
184 | |
185 | called function = `{0}` |
186 | loaded `libclang` instance = {1} |
187 | |
188 | This crate only supports `libclang` 3.5 and later. |
189 | The minimum `libclang` requirement for this particular function can be found here: |
190 | https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html |
191 | |
192 | Instructions for installing `libclang` can be found here: |
193 | https://rust-lang.github.io/rust-bindgen/requirements.html |
194 | "# , |
195 | stringify!($name), |
196 | library |
197 | .version() |
198 | .map(|v| format!("{}" , v)) |
199 | .unwrap_or_else(|| "unsupported version" .into()), |
200 | ); |
201 | } |
202 | }).expect("a `libclang` shared library is not loaded on this thread" ); |
203 | f($($pname), *) |
204 | } |
205 | |
206 | $(#[doc=$doc] #[cfg($cfg)])* |
207 | pub mod $name { |
208 | pub fn is_loaded() -> bool { |
209 | super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false) |
210 | } |
211 | } |
212 | )+ |
213 | |
214 | mod load { |
215 | $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+ |
216 | } |
217 | |
218 | /// Loads a `libclang` shared library and returns the library instance. |
219 | /// |
220 | /// This function does not attempt to load any functions from the shared library. The caller |
221 | /// is responsible for loading the functions they require. |
222 | /// |
223 | /// # Failures |
224 | /// |
225 | /// * a `libclang` shared library could not be found |
226 | /// * the `libclang` shared library could not be opened |
227 | pub fn load_manually() -> Result<SharedLibrary, String> { |
228 | #[allow(dead_code)] |
229 | mod build { |
230 | include!(concat!(env!("OUT_DIR" ), "/macros.rs" )); |
231 | pub mod common { include!(concat!(env!("OUT_DIR" ), "/common.rs" )); } |
232 | pub mod dynamic { include!(concat!(env!("OUT_DIR" ), "/dynamic.rs" )); } |
233 | } |
234 | |
235 | let (directory, filename) = build::dynamic::find(true)?; |
236 | let path = directory.join(filename); |
237 | |
238 | unsafe { |
239 | let library = libloading::Library::new(&path).map_err(|e| { |
240 | format!( |
241 | "the `libclang` shared library at {} could not be opened: {}" , |
242 | path.display(), |
243 | e, |
244 | ) |
245 | }); |
246 | |
247 | let mut library = SharedLibrary::new(library?, path); |
248 | $(load::$name(&mut library);)+ |
249 | Ok(library) |
250 | } |
251 | } |
252 | |
253 | /// Loads a `libclang` shared library for use in the current thread. |
254 | /// |
255 | /// This functions attempts to load all the functions in the shared library. Whether a |
256 | /// function has been loaded can be tested by calling the `is_loaded` function on the |
257 | /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for |
258 | /// the `clang_createIndex` function). |
259 | /// |
260 | /// # Failures |
261 | /// |
262 | /// * a `libclang` shared library could not be found |
263 | /// * the `libclang` shared library could not be opened |
264 | #[allow(dead_code)] |
265 | pub fn load() -> Result<(), String> { |
266 | let library = Arc::new(load_manually()?); |
267 | LIBRARY.with(|l| *l.borrow_mut() = Some(library)); |
268 | Ok(()) |
269 | } |
270 | |
271 | /// Unloads the `libclang` shared library in use in the current thread. |
272 | /// |
273 | /// # Failures |
274 | /// |
275 | /// * a `libclang` shared library is not in use in the current thread |
276 | pub fn unload() -> Result<(), String> { |
277 | let library = set_library(None); |
278 | if library.is_some() { |
279 | Ok(()) |
280 | } else { |
281 | Err("a `libclang` shared library is not in use in the current thread" .into()) |
282 | } |
283 | } |
284 | |
285 | /// Returns the library instance stored in TLS. |
286 | /// |
287 | /// This functions allows for sharing library instances between threads. |
288 | pub fn get_library() -> Option<Arc<SharedLibrary>> { |
289 | LIBRARY.with(|l| l.borrow_mut().clone()) |
290 | } |
291 | |
292 | /// Sets the library instance stored in TLS and returns the previous library. |
293 | /// |
294 | /// This functions allows for sharing library instances between threads. |
295 | pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> { |
296 | LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library)) |
297 | } |
298 | ) |
299 | } |
300 | |
301 | #[cfg (not(feature = "runtime" ))] |
302 | macro_rules! link { |
303 | ( |
304 | $( |
305 | $(#[doc=$doc:expr] #[cfg($cfg:meta)])* |
306 | pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; |
307 | )+ |
308 | ) => ( |
309 | extern { |
310 | $( |
311 | $(#[doc=$doc] #[cfg($cfg)])* |
312 | pub fn $name($($pname: $pty), *) $(-> $ret)*; |
313 | )+ |
314 | } |
315 | |
316 | $( |
317 | $(#[doc=$doc] #[cfg($cfg)])* |
318 | pub mod $name { |
319 | pub fn is_loaded() -> bool { true } |
320 | } |
321 | )+ |
322 | ) |
323 | } |
324 | |