• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::sync::{Arc};
44         use std::path::{Path, PathBuf};
45 
46         /// The (minimum) version of a `libclang` shared library.
47         #[allow(missing_docs)]
48         #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49         pub enum Version {
50             V3_5 = 35,
51             V3_6 = 36,
52             V3_7 = 37,
53             V3_8 = 38,
54             V3_9 = 39,
55             V4_0 = 40,
56             V5_0 = 50,
57             V6_0 = 60,
58             V7_0 = 70,
59             V8_0 = 80,
60             V9_0 = 90,
61         }
62 
63         /// The set of functions loaded dynamically.
64         #[derive(Debug, Default)]
65         pub struct Functions {
66             $(
67                 $(#[doc=$doc] #[cfg($cfg)])*
68                 pub $name: Option<unsafe extern fn($($pname: $pty), *) $(-> $ret)*>,
69             )+
70         }
71 
72         /// A dynamically loaded instance of the `libclang` library.
73         #[derive(Debug)]
74         pub struct SharedLibrary {
75             library: libloading::Library,
76             path: PathBuf,
77             pub functions: Functions,
78         }
79 
80         impl SharedLibrary {
81             fn new(library: libloading::Library, path: PathBuf) -> Self {
82                 Self { library, path, functions: Functions::default() }
83             }
84 
85             /// Returns the path to this `libclang` shared library.
86             pub fn path(&self) -> &Path {
87                 &self.path
88             }
89 
90             /// Returns the (minimum) version of this `libclang` shared library.
91             ///
92             /// If this returns `None`, it indicates that the version is too old
93             /// to be supported by this crate (i.e., `3.4` or earlier). If the
94             /// version of this shared library is more recent than that fully
95             /// supported by this crate, the most recent fully supported version
96             /// will be returned.
97             pub fn version(&self) -> Option<Version> {
98                 macro_rules! check {
99                     ($fn:expr, $version:ident) => {
100                         if self.library.get::<unsafe extern fn()>($fn).is_ok() {
101                             return Some(Version::$version);
102                         }
103                     };
104                 }
105 
106                 unsafe {
107                     check!(b"clang_Cursor_isAnonymousRecordDecl", V9_0);
108                     check!(b"clang_Cursor_getObjCPropertyGetterName", V8_0);
109                     check!(b"clang_File_tryGetRealPathName", V7_0);
110                     check!(b"clang_CXIndex_setInvocationEmissionPathOption", V6_0);
111                     check!(b"clang_Cursor_isExternalSymbol", V5_0);
112                     check!(b"clang_EvalResult_getAsLongLong", V4_0);
113                     check!(b"clang_CXXConstructor_isConvertingConstructor", V3_9);
114                     check!(b"clang_CXXField_isMutable", V3_8);
115                     check!(b"clang_Cursor_getOffsetOfField", V3_7);
116                     check!(b"clang_Cursor_getStorageClass", V3_6);
117                     check!(b"clang_Type_getNumTemplateArguments", V3_5);
118                 }
119 
120                 None
121             }
122         }
123 
124         thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None));
125 
126         /// Returns whether a `libclang` shared library is loaded on this thread.
127         pub fn is_loaded() -> bool {
128             LIBRARY.with(|l| l.borrow().is_some())
129         }
130 
131         fn with_library<T, F>(f: F) -> Option<T> where F: FnOnce(&SharedLibrary) -> T {
132             LIBRARY.with(|l| {
133                 match l.borrow().as_ref() {
134                     Some(library) => Some(f(&library)),
135                     _ => None,
136                 }
137             })
138         }
139 
140         $(
141             #[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))]
142             #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]
143             $(#[doc=$doc] #[cfg($cfg)])*
144             pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
145                 let f = with_library(|l| {
146                     l.functions.$name.expect(concat!(
147                         "`libclang` function not loaded: `",
148                         stringify!($name),
149                         "`. This crate requires that `libclang` 3.9 or later be installed on your ",
150                         "system. For more information on how to accomplish this, see here: ",
151                         "https://rust-lang.github.io/rust-bindgen/requirements.html#installing-clang-39"))
152                 }).expect("a `libclang` shared library is not loaded on this thread");
153                 f($($pname), *)
154             }
155 
156             $(#[doc=$doc] #[cfg($cfg)])*
157             pub mod $name {
158                 pub fn is_loaded() -> bool {
159                     super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false)
160                 }
161             }
162         )+
163 
164         mod load {
165             $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+
166         }
167 
168         /// Loads a `libclang` shared library and returns the library instance.
169         ///
170         /// This function does not attempt to load any functions from the shared library. The caller
171         /// is responsible for loading the functions they require.
172         ///
173         /// # Failures
174         ///
175         /// * a `libclang` shared library could not be found
176         /// * the `libclang` shared library could not be opened
177         pub fn load_manually() -> Result<SharedLibrary, String> {
178             mod build {
179                 pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
180                 pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }
181             }
182 
183             let (directory, filename) = build::dynamic::find(true)?;
184             let path = directory.join(filename);
185 
186             unsafe {
187                 let library = libloading::Library::new(&path).map_err(|e| {
188                     format!(
189                         "the `libclang` shared library at {} could not be opened: {}",
190                         path.display(),
191                         e,
192                     )
193                 });
194 
195                 let mut library = SharedLibrary::new(library?, path);
196                 $(load::$name(&mut library);)+
197                 Ok(library)
198             }
199         }
200 
201         /// Loads a `libclang` shared library for use in the current thread.
202         ///
203         /// This functions attempts to load all the functions in the shared library. Whether a
204         /// function has been loaded can be tested by calling the `is_loaded` function on the
205         /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for
206         /// the `clang_createIndex` function).
207         ///
208         /// # Failures
209         ///
210         /// * a `libclang` shared library could not be found
211         /// * the `libclang` shared library could not be opened
212         #[allow(dead_code)]
213         pub fn load() -> Result<(), String> {
214             let library = Arc::new(load_manually()?);
215             LIBRARY.with(|l| *l.borrow_mut() = Some(library));
216             Ok(())
217         }
218 
219         /// Unloads the `libclang` shared library in use in the current thread.
220         ///
221         /// # Failures
222         ///
223         /// * a `libclang` shared library is not in use in the current thread
224         pub fn unload() -> Result<(), String> {
225             let library = set_library(None);
226             if library.is_some() {
227                 Ok(())
228             } else {
229                 Err("a `libclang` shared library is not in use in the current thread".into())
230             }
231         }
232 
233         /// Returns the library instance stored in TLS.
234         ///
235         /// This functions allows for sharing library instances between threads.
236         pub fn get_library() -> Option<Arc<SharedLibrary>> {
237             LIBRARY.with(|l| l.borrow_mut().clone())
238         }
239 
240         /// Sets the library instance stored in TLS and returns the previous library.
241         ///
242         /// This functions allows for sharing library instances between threads.
243         pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {
244             LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library))
245         }
246     )
247 }
248 
249 #[cfg(not(feature = "runtime"))]
250 macro_rules! link {
251     (
252         $(
253             $(#[doc=$doc:expr] #[cfg($cfg:meta)])*
254             pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;
255         )+
256     ) => (
257         extern {
258             $(
259                 $(#[doc=$doc] #[cfg($cfg)])*
260                 pub fn $name($($pname: $pty), *) $(-> $ret)*;
261             )+
262         }
263 
264         $(
265             $(#[doc=$doc] #[cfg($cfg)])*
266             pub mod $name {
267                 pub fn is_loaded() -> bool { true }
268             }
269         )+
270     )
271 }
272