David LeGare | 82e2b17 | 2022-03-01 18:53:05 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: Apache-2.0 |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 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 | $( |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 141 | #[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))] |
| 142 | #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))] |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 143 | $(#[doc=$doc] #[cfg($cfg)])* |
| 144 | pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { |
| 145 | let f = with_library(|l| { |
Chih-Hung Hsieh | 97132dc | 2020-10-26 17:21:51 -0700 | [diff] [blame] | 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")) |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 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 { |
Chih-Hung Hsieh | e11a463 | 2020-10-24 23:10:41 -0700 | [diff] [blame] | 179 | pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); } |
| 180 | pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 181 | } |
| 182 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 183 | let (directory, filename) = build::dynamic::find(true)?; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 184 | let path = directory.join(filename); |
| 185 | |
Jeff Vander Stoep | 582dabe | 2021-02-19 19:59:49 +0100 | [diff] [blame] | 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 | }); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 194 | |
Jeff Vander Stoep | 582dabe | 2021-02-19 19:59:49 +0100 | [diff] [blame] | 195 | let mut library = SharedLibrary::new(library?, path); |
| 196 | $(load::$name(&mut library);)+ |
| 197 | Ok(library) |
| 198 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 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> { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 214 | let library = Arc::new(load_manually()?); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 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 | } |