1// SPDX-License-Identifier: Apache-2.0
2
3//================================================
4// Macros
5//================================================
6
7#[cfg(feature = "runtime")]
8macro_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#"
183A `libclang` function was called that is not supported by the loaded `libclang` instance.
184
185 called function = `{0}`
186 loaded `libclang` instance = {1}
187
188This crate only supports `libclang` 3.5 and later.
189The minimum `libclang` requirement for this particular function can be found here:
190https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html
191
192Instructions for installing `libclang` can be found here:
193https://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"))]
302macro_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