Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 1 | // Copyright 2016 Kyle Mayes |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //================================================ |
| 16 | // Macros |
| 17 | //================================================ |
| 18 | |
| 19 | #[cfg(feature = "runtime")] |
| 20 | macro_rules! link { |
| 21 | ( |
| 22 | @LOAD: |
| 23 | $(#[doc=$doc:expr])* |
| 24 | #[cfg($cfg:meta)] |
| 25 | fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)* |
| 26 | ) => ( |
| 27 | $(#[doc=$doc])* |
| 28 | #[cfg($cfg)] |
| 29 | pub fn $name(library: &mut super::SharedLibrary) { |
| 30 | let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.ok(); |
| 31 | library.functions.$name = match symbol { |
| 32 | Some(s) => *s, |
| 33 | None => None, |
| 34 | }; |
| 35 | } |
| 36 | |
| 37 | #[cfg(not($cfg))] |
| 38 | pub fn $name(_: &mut super::SharedLibrary) {} |
| 39 | ); |
| 40 | |
| 41 | ( |
| 42 | @LOAD: |
| 43 | fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)* |
| 44 | ) => ( |
| 45 | link!(@LOAD: #[cfg(feature = "runtime")] fn $name($($pname: $pty), *) $(-> $ret)*); |
| 46 | ); |
| 47 | |
| 48 | ( |
| 49 | $( |
| 50 | $(#[doc=$doc:expr] #[cfg($cfg:meta)])* |
| 51 | pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; |
| 52 | )+ |
| 53 | ) => ( |
| 54 | use std::cell::{RefCell}; |
| 55 | use std::sync::{Arc}; |
| 56 | use std::path::{Path, PathBuf}; |
| 57 | |
| 58 | /// The (minimum) version of a `libclang` shared library. |
| 59 | #[allow(missing_docs)] |
| 60 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 61 | pub enum Version { |
| 62 | V3_5 = 35, |
| 63 | V3_6 = 36, |
| 64 | V3_7 = 37, |
| 65 | V3_8 = 38, |
| 66 | V3_9 = 39, |
| 67 | V4_0 = 40, |
| 68 | V5_0 = 50, |
| 69 | V6_0 = 60, |
| 70 | V7_0 = 70, |
| 71 | V8_0 = 80, |
| 72 | V9_0 = 90, |
| 73 | } |
| 74 | |
| 75 | /// The set of functions loaded dynamically. |
| 76 | #[derive(Debug, Default)] |
| 77 | pub struct Functions { |
| 78 | $( |
| 79 | $(#[doc=$doc] #[cfg($cfg)])* |
| 80 | pub $name: Option<unsafe extern fn($($pname: $pty), *) $(-> $ret)*>, |
| 81 | )+ |
| 82 | } |
| 83 | |
| 84 | /// A dynamically loaded instance of the `libclang` library. |
| 85 | #[derive(Debug)] |
| 86 | pub struct SharedLibrary { |
| 87 | library: libloading::Library, |
| 88 | path: PathBuf, |
| 89 | pub functions: Functions, |
| 90 | } |
| 91 | |
| 92 | impl SharedLibrary { |
| 93 | fn new(library: libloading::Library, path: PathBuf) -> Self { |
| 94 | Self { library, path, functions: Functions::default() } |
| 95 | } |
| 96 | |
| 97 | /// Returns the path to this `libclang` shared library. |
| 98 | pub fn path(&self) -> &Path { |
| 99 | &self.path |
| 100 | } |
| 101 | |
| 102 | /// Returns the (minimum) version of this `libclang` shared library. |
| 103 | /// |
| 104 | /// If this returns `None`, it indicates that the version is too old |
| 105 | /// to be supported by this crate (i.e., `3.4` or earlier). If the |
| 106 | /// version of this shared library is more recent than that fully |
| 107 | /// supported by this crate, the most recent fully supported version |
| 108 | /// will be returned. |
| 109 | pub fn version(&self) -> Option<Version> { |
| 110 | macro_rules! check { |
| 111 | ($fn:expr, $version:ident) => { |
| 112 | if self.library.get::<unsafe extern fn()>($fn).is_ok() { |
| 113 | return Some(Version::$version); |
| 114 | } |
| 115 | }; |
| 116 | } |
| 117 | |
| 118 | unsafe { |
| 119 | check!(b"clang_Cursor_isAnonymousRecordDecl", V9_0); |
| 120 | check!(b"clang_Cursor_getObjCPropertyGetterName", V8_0); |
| 121 | check!(b"clang_File_tryGetRealPathName", V7_0); |
| 122 | check!(b"clang_CXIndex_setInvocationEmissionPathOption", V6_0); |
| 123 | check!(b"clang_Cursor_isExternalSymbol", V5_0); |
| 124 | check!(b"clang_EvalResult_getAsLongLong", V4_0); |
| 125 | check!(b"clang_CXXConstructor_isConvertingConstructor", V3_9); |
| 126 | check!(b"clang_CXXField_isMutable", V3_8); |
| 127 | check!(b"clang_Cursor_getOffsetOfField", V3_7); |
| 128 | check!(b"clang_Cursor_getStorageClass", V3_6); |
| 129 | check!(b"clang_Type_getNumTemplateArguments", V3_5); |
| 130 | } |
| 131 | |
| 132 | None |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None)); |
| 137 | |
| 138 | /// Returns whether a `libclang` shared library is loaded on this thread. |
| 139 | pub fn is_loaded() -> bool { |
| 140 | LIBRARY.with(|l| l.borrow().is_some()) |
| 141 | } |
| 142 | |
| 143 | fn with_library<T, F>(f: F) -> Option<T> where F: FnOnce(&SharedLibrary) -> T { |
| 144 | LIBRARY.with(|l| { |
| 145 | match l.borrow().as_ref() { |
| 146 | Some(library) => Some(f(&library)), |
| 147 | _ => None, |
| 148 | } |
| 149 | }) |
| 150 | } |
| 151 | |
| 152 | $( |
| 153 | #[cfg_attr(feature="cargo-clippy", allow(too_many_arguments))] |
| 154 | $(#[doc=$doc] #[cfg($cfg)])* |
| 155 | pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { |
| 156 | let f = with_library(|l| { |
| 157 | match l.functions.$name { |
| 158 | Some(f) => f, |
| 159 | _ => panic!(concat!("function not loaded: ", stringify!($name))), |
| 160 | } |
| 161 | }).expect("a `libclang` shared library is not loaded on this thread"); |
| 162 | f($($pname), *) |
| 163 | } |
| 164 | |
| 165 | $(#[doc=$doc] #[cfg($cfg)])* |
| 166 | pub mod $name { |
| 167 | pub fn is_loaded() -> bool { |
| 168 | super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false) |
| 169 | } |
| 170 | } |
| 171 | )+ |
| 172 | |
| 173 | mod load { |
| 174 | $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+ |
| 175 | } |
| 176 | |
| 177 | /// Loads a `libclang` shared library and returns the library instance. |
| 178 | /// |
| 179 | /// This function does not attempt to load any functions from the shared library. The caller |
| 180 | /// is responsible for loading the functions they require. |
| 181 | /// |
| 182 | /// # Failures |
| 183 | /// |
| 184 | /// * a `libclang` shared library could not be found |
| 185 | /// * the `libclang` shared library could not be opened |
| 186 | pub fn load_manually() -> Result<SharedLibrary, String> { |
| 187 | mod build { |
| 188 | pub mod common { include!("../out/common.rs"); } |
| 189 | pub mod dynamic { include!("../out/dynamic.rs"); } |
| 190 | } |
| 191 | |
| 192 | let (directory, filename) = try!(build::dynamic::find(true)); |
| 193 | let path = directory.join(filename); |
| 194 | |
| 195 | let library = libloading::Library::new(&path).map_err(|e| { |
| 196 | format!( |
| 197 | "the `libclang` shared library at {} could not be opened: {}", |
| 198 | path.display(), |
| 199 | e, |
| 200 | ) |
| 201 | }); |
| 202 | |
| 203 | let mut library = SharedLibrary::new(try!(library), path); |
| 204 | $(load::$name(&mut library);)+ |
| 205 | Ok(library) |
| 206 | } |
| 207 | |
| 208 | /// Loads a `libclang` shared library for use in the current thread. |
| 209 | /// |
| 210 | /// This functions attempts to load all the functions in the shared library. Whether a |
| 211 | /// function has been loaded can be tested by calling the `is_loaded` function on the |
| 212 | /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for |
| 213 | /// the `clang_createIndex` function). |
| 214 | /// |
| 215 | /// # Failures |
| 216 | /// |
| 217 | /// * a `libclang` shared library could not be found |
| 218 | /// * the `libclang` shared library could not be opened |
| 219 | #[allow(dead_code)] |
| 220 | pub fn load() -> Result<(), String> { |
| 221 | let library = Arc::new(try!(load_manually())); |
| 222 | LIBRARY.with(|l| *l.borrow_mut() = Some(library)); |
| 223 | Ok(()) |
| 224 | } |
| 225 | |
| 226 | /// Unloads the `libclang` shared library in use in the current thread. |
| 227 | /// |
| 228 | /// # Failures |
| 229 | /// |
| 230 | /// * a `libclang` shared library is not in use in the current thread |
| 231 | pub fn unload() -> Result<(), String> { |
| 232 | let library = set_library(None); |
| 233 | if library.is_some() { |
| 234 | Ok(()) |
| 235 | } else { |
| 236 | Err("a `libclang` shared library is not in use in the current thread".into()) |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | /// Returns the library instance stored in TLS. |
| 241 | /// |
| 242 | /// This functions allows for sharing library instances between threads. |
| 243 | pub fn get_library() -> Option<Arc<SharedLibrary>> { |
| 244 | LIBRARY.with(|l| l.borrow_mut().clone()) |
| 245 | } |
| 246 | |
| 247 | /// Sets the library instance stored in TLS and returns the previous library. |
| 248 | /// |
| 249 | /// This functions allows for sharing library instances between threads. |
| 250 | pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> { |
| 251 | LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library)) |
| 252 | } |
| 253 | ) |
| 254 | } |
| 255 | |
| 256 | #[cfg(not(feature = "runtime"))] |
| 257 | macro_rules! link { |
| 258 | ( |
| 259 | $( |
| 260 | $(#[doc=$doc:expr] #[cfg($cfg:meta)])* |
| 261 | pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; |
| 262 | )+ |
| 263 | ) => ( |
| 264 | extern { |
| 265 | $( |
| 266 | $(#[doc=$doc] #[cfg($cfg)])* |
| 267 | pub fn $name($($pname: $pty), *) $(-> $ret)*; |
| 268 | )+ |
| 269 | } |
| 270 | |
| 271 | $( |
| 272 | $(#[doc=$doc] #[cfg($cfg)])* |
| 273 | pub mod $name { |
| 274 | pub fn is_loaded() -> bool { true } |
| 275 | } |
| 276 | )+ |
| 277 | ) |
| 278 | } |