blob: 64a352865fc40412bd2901515aee78b5eca06239 [file] [log] [blame]
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -07001// 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")]
20macro_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 Huang8b9513e2020-07-13 22:05:39 -0700153 #[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))]
154 #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700155 $(#[doc=$doc] #[cfg($cfg)])*
156 pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
157 let f = with_library(|l| {
Chih-Hung Hsieh97132dc2020-10-26 17:21:51 -0700158 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 Hsiehfab43802020-04-07 14:24:01 -0700164 }).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 Hsiehe11a4632020-10-24 23:10:41 -0700191 pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
192 pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700193 }
194
Haibo Huang8b9513e2020-07-13 22:05:39 -0700195 let (directory, filename) = build::dynamic::find(true)?;
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700196 let path = directory.join(filename);
197
Jeff Vander Stoep582dabe2021-02-19 19:59:49 +0100198 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 Hsiehfab43802020-04-07 14:24:01 -0700206
Jeff Vander Stoep582dabe2021-02-19 19:59:49 +0100207 let mut library = SharedLibrary::new(library?, path);
208 $(load::$name(&mut library);)+
209 Ok(library)
210 }
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700211 }
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 Huang8b9513e2020-07-13 22:05:39 -0700226 let library = Arc::new(load_manually()?);
Chih-Hung Hsiehfab43802020-04-07 14:24:01 -0700227 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"))]
262macro_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}