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 | $( |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 153 | #[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))] |
| 154 | #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))] |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 155 | $(#[doc=$doc] #[cfg($cfg)])* |
| 156 | pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { |
| 157 | let f = with_library(|l| { |
Chih-Hung Hsieh | 97132dc | 2020-10-26 17:21:51 -0700 | [diff] [blame] | 158 | l.functions.$name.expect(concat!( |
| 159 | "`libclang` function not loaded: `", |
| 160 | stringify!($name), |
| 161 | "`. This crate requires that `libclang` 3.9 or later be installed on your ", |
| 162 | "system. For more information on how to accomplish this, see here: ", |
| 163 | "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] | 164 | }).expect("a `libclang` shared library is not loaded on this thread"); |
| 165 | f($($pname), *) |
| 166 | } |
| 167 | |
| 168 | $(#[doc=$doc] #[cfg($cfg)])* |
| 169 | pub mod $name { |
| 170 | pub fn is_loaded() -> bool { |
| 171 | super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false) |
| 172 | } |
| 173 | } |
| 174 | )+ |
| 175 | |
| 176 | mod load { |
| 177 | $(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+ |
| 178 | } |
| 179 | |
| 180 | /// Loads a `libclang` shared library and returns the library instance. |
| 181 | /// |
| 182 | /// This function does not attempt to load any functions from the shared library. The caller |
| 183 | /// is responsible for loading the functions they require. |
| 184 | /// |
| 185 | /// # Failures |
| 186 | /// |
| 187 | /// * a `libclang` shared library could not be found |
| 188 | /// * the `libclang` shared library could not be opened |
| 189 | pub fn load_manually() -> Result<SharedLibrary, String> { |
| 190 | mod build { |
Chih-Hung Hsieh | e11a463 | 2020-10-24 23:10:41 -0700 | [diff] [blame] | 191 | pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); } |
| 192 | pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 193 | } |
| 194 | |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 195 | let (directory, filename) = build::dynamic::find(true)?; |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 196 | let path = directory.join(filename); |
| 197 | |
Jeff Vander Stoep | 582dabe | 2021-02-19 19:59:49 +0100 | [diff] [blame] | 198 | unsafe { |
| 199 | let library = libloading::Library::new(&path).map_err(|e| { |
| 200 | format!( |
| 201 | "the `libclang` shared library at {} could not be opened: {}", |
| 202 | path.display(), |
| 203 | e, |
| 204 | ) |
| 205 | }); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 206 | |
Jeff Vander Stoep | 582dabe | 2021-02-19 19:59:49 +0100 | [diff] [blame] | 207 | let mut library = SharedLibrary::new(library?, path); |
| 208 | $(load::$name(&mut library);)+ |
| 209 | Ok(library) |
| 210 | } |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | /// Loads a `libclang` shared library for use in the current thread. |
| 214 | /// |
| 215 | /// This functions attempts to load all the functions in the shared library. Whether a |
| 216 | /// function has been loaded can be tested by calling the `is_loaded` function on the |
| 217 | /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for |
| 218 | /// the `clang_createIndex` function). |
| 219 | /// |
| 220 | /// # Failures |
| 221 | /// |
| 222 | /// * a `libclang` shared library could not be found |
| 223 | /// * the `libclang` shared library could not be opened |
| 224 | #[allow(dead_code)] |
| 225 | pub fn load() -> Result<(), String> { |
Haibo Huang | 8b9513e | 2020-07-13 22:05:39 -0700 | [diff] [blame] | 226 | let library = Arc::new(load_manually()?); |
Chih-Hung Hsieh | fab4380 | 2020-04-07 14:24:01 -0700 | [diff] [blame] | 227 | LIBRARY.with(|l| *l.borrow_mut() = Some(library)); |
| 228 | Ok(()) |
| 229 | } |
| 230 | |
| 231 | /// Unloads the `libclang` shared library in use in the current thread. |
| 232 | /// |
| 233 | /// # Failures |
| 234 | /// |
| 235 | /// * a `libclang` shared library is not in use in the current thread |
| 236 | pub fn unload() -> Result<(), String> { |
| 237 | let library = set_library(None); |
| 238 | if library.is_some() { |
| 239 | Ok(()) |
| 240 | } else { |
| 241 | Err("a `libclang` shared library is not in use in the current thread".into()) |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /// Returns the library instance stored in TLS. |
| 246 | /// |
| 247 | /// This functions allows for sharing library instances between threads. |
| 248 | pub fn get_library() -> Option<Arc<SharedLibrary>> { |
| 249 | LIBRARY.with(|l| l.borrow_mut().clone()) |
| 250 | } |
| 251 | |
| 252 | /// Sets the library instance stored in TLS and returns the previous library. |
| 253 | /// |
| 254 | /// This functions allows for sharing library instances between threads. |
| 255 | pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> { |
| 256 | LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library)) |
| 257 | } |
| 258 | ) |
| 259 | } |
| 260 | |
| 261 | #[cfg(not(feature = "runtime"))] |
| 262 | macro_rules! link { |
| 263 | ( |
| 264 | $( |
| 265 | $(#[doc=$doc:expr] #[cfg($cfg:meta)])* |
| 266 | pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*; |
| 267 | )+ |
| 268 | ) => ( |
| 269 | extern { |
| 270 | $( |
| 271 | $(#[doc=$doc] #[cfg($cfg)])* |
| 272 | pub fn $name($($pname: $pty), *) $(-> $ret)*; |
| 273 | )+ |
| 274 | } |
| 275 | |
| 276 | $( |
| 277 | $(#[doc=$doc] #[cfg($cfg)])* |
| 278 | pub mod $name { |
| 279 | pub fn is_loaded() -> bool { true } |
| 280 | } |
| 281 | )+ |
| 282 | ) |
| 283 | } |