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(clippy::missing_safety_doc))]
154             #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]
155             $(#[doc=$doc] #[cfg($cfg)])*
156             pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
157                 let f = with_library(|l| {
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"))
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 {
191                 pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
192                 pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }
193             }
194 
195             let (directory, filename) = build::dynamic::find(true)?;
196             let path = directory.join(filename);
197 
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                 });
206 
207                 let mut library = SharedLibrary::new(library?, path);
208                 $(load::$name(&mut library);)+
209                 Ok(library)
210             }
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> {
226             let library = Arc::new(load_manually()?);
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 }
284