clang_sys/
link.rs

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
188The minimum `libclang` requirement for this particular function can be found here:
189https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html
190
191Instructions for installing `libclang` can be found here:
192https://rust-lang.github.io/rust-bindgen/requirements.html
193"#, 
194                            stringify!($name),
195                            library
196                                .version()
197                                .map(|v| format!("{}", v))
198                                .unwrap_or_else(|| "unsupported version".into()),
199                        );
200                    }
201                }).expect("a `libclang` shared library is not loaded on this thread");
202                f($($pname), *)
203            }
204
205            $(#[doc=$doc] #[cfg($cfg)])*
206            pub mod $name {
207                pub fn is_loaded() -> bool {
208                    super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false)
209                }
210            }
211        )+
212
213        mod load {
214            $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+
215        }
216
217        /// Loads a `libclang` shared library and returns the library instance.
218        ///
219        /// This function does not attempt to load any functions from the shared library. The caller
220        /// is responsible for loading the functions they require.
221        ///
222        /// # Failures
223        ///
224        /// * a `libclang` shared library could not be found
225        /// * the `libclang` shared library could not be opened
226        pub fn load_manually() -> Result<SharedLibrary, String> {
227            #[allow(dead_code)]
228            mod build {
229                include!(concat!(env!("OUT_DIR"), "/macros.rs"));
230                pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
231                pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }
232            }
233
234            let (directory, filename) = build::dynamic::find(true)?;
235            let path = directory.join(filename);
236
237            unsafe {
238                let library = libloading::Library::new(&path).map_err(|e| {
239                    format!(
240                        "the `libclang` shared library at {} could not be opened: {}",
241                        path.display(),
242                        e,
243                    )
244                });
245
246                let mut library = SharedLibrary::new(library?, path);
247                $(load::$name(&mut library);)+
248                Ok(library)
249            }
250        }
251
252        /// Loads a `libclang` shared library for use in the current thread.
253        ///
254        /// This functions attempts to load all the functions in the shared library. Whether a
255        /// function has been loaded can be tested by calling the `is_loaded` function on the
256        /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for
257        /// the `clang_createIndex` function).
258        ///
259        /// # Failures
260        ///
261        /// * a `libclang` shared library could not be found
262        /// * the `libclang` shared library could not be opened
263        #[allow(dead_code)]
264        pub fn load() -> Result<(), String> {
265            let library = Arc::new(load_manually()?);
266            LIBRARY.with(|l| *l.borrow_mut() = Some(library));
267            Ok(())
268        }
269
270        /// Unloads the `libclang` shared library in use in the current thread.
271        ///
272        /// # Failures
273        ///
274        /// * a `libclang` shared library is not in use in the current thread
275        pub fn unload() -> Result<(), String> {
276            let library = set_library(None);
277            if library.is_some() {
278                Ok(())
279            } else {
280                Err("a `libclang` shared library is not in use in the current thread".into())
281            }
282        }
283
284        /// Returns the library instance stored in TLS.
285        ///
286        /// This functions allows for sharing library instances between threads.
287        pub fn get_library() -> Option<Arc<SharedLibrary>> {
288            LIBRARY.with(|l| l.borrow_mut().clone())
289        }
290
291        /// Sets the library instance stored in TLS and returns the previous library.
292        ///
293        /// This functions allows for sharing library instances between threads.
294        pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {
295            LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library))
296        }
297    )
298}
299
300#[cfg(not(feature = "runtime"))]
301macro_rules! link {
302    (
303        $(
304            $(#[doc=$doc:expr] #[cfg($cfg:meta)])*
305            pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;
306        )+
307    ) => (
308        extern {
309            $(
310                $(#[doc=$doc] #[cfg($cfg)])*
311                pub fn $name($($pname: $pty), *) $(-> $ret)*;
312            )+
313        }
314
315        $(
316            $(#[doc=$doc] #[cfg($cfg)])*
317            pub mod $name {
318                pub fn is_loaded() -> bool { true }
319            }
320        )+
321    )
322}