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