| 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 | The minimum `libclang` requirement for this particular function can be found here:
|
| 189 | https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html
|
| 190 |
|
| 191 | Instructions for installing `libclang` can be found here:
|
| 192 | https://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" ))]
|
| 301 | macro_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 | }
|
| 323 | |