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