clang_sys/
link.rs

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")]
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        $(
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            let library = libloading::Library::new(&path).map_err(|e| {
199                format!(
200                    "the `libclang` shared library at {} could not be opened: {}",
201                    path.display(),
202                    e,
203                )
204            });
205
206            let mut library = SharedLibrary::new(library?, path);
207            $(load::$name(&mut library);)+
208            Ok(library)
209        }
210
211        /// Loads a `libclang` shared library for use in the current thread.
212        ///
213        /// This functions attempts to load all the functions in the shared library. Whether a
214        /// function has been loaded can be tested by calling the `is_loaded` function on the
215        /// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for
216        /// the `clang_createIndex` function).
217        ///
218        /// # Failures
219        ///
220        /// * a `libclang` shared library could not be found
221        /// * the `libclang` shared library could not be opened
222        #[allow(dead_code)]
223        pub fn load() -> Result<(), String> {
224            let library = Arc::new(load_manually()?);
225            LIBRARY.with(|l| *l.borrow_mut() = Some(library));
226            Ok(())
227        }
228
229        /// Unloads the `libclang` shared library in use in the current thread.
230        ///
231        /// # Failures
232        ///
233        /// * a `libclang` shared library is not in use in the current thread
234        pub fn unload() -> Result<(), String> {
235            let library = set_library(None);
236            if library.is_some() {
237                Ok(())
238            } else {
239                Err("a `libclang` shared library is not in use in the current thread".into())
240            }
241        }
242
243        /// Returns the library instance stored in TLS.
244        ///
245        /// This functions allows for sharing library instances between threads.
246        pub fn get_library() -> Option<Arc<SharedLibrary>> {
247            LIBRARY.with(|l| l.borrow_mut().clone())
248        }
249
250        /// Sets the library instance stored in TLS and returns the previous library.
251        ///
252        /// This functions allows for sharing library instances between threads.
253        pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {
254            LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library))
255        }
256    )
257}
258
259#[cfg(not(feature = "runtime"))]
260macro_rules! link {
261    (
262        $(
263            $(#[doc=$doc:expr] #[cfg($cfg:meta)])*
264            pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;
265        )+
266    ) => (
267        extern {
268            $(
269                $(#[doc=$doc] #[cfg($cfg)])*
270                pub fn $name($($pname: $pty), *) $(-> $ret)*;
271            )+
272        }
273
274        $(
275            $(#[doc=$doc] #[cfg($cfg)])*
276            pub mod $name {
277                pub fn is_loaded() -> bool { true }
278            }
279        )+
280    )
281}