Source code

Revision control

Copy as Markdown

Other Tools

// SPDX-License-Identifier: Apache-2.0↩
//================================================↩
// Macros↩
//================================================↩
#[cfg(feature = "runtime")]↩
macro_rules! link {↩
(↩
@LOAD:↩
$(#[doc=$doc:expr])*↩
#[cfg($cfg:meta)]↩
fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*↩
) => (↩
$(#[doc=$doc])*↩
#[cfg($cfg)]↩
pub fn $name(library: &mut super::SharedLibrary) {↩
let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.ok();↩
library.functions.$name = match symbol {↩
Some(s) => *s,↩
None => None,↩
};↩
}↩
#[cfg(not($cfg))]↩
pub fn $name(_: &mut super::SharedLibrary) {}↩
);↩
(↩
@LOAD:↩
fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*↩
) => (↩
link!(@LOAD: #[cfg(feature = "runtime")] fn $name($($pname: $pty), *) $(-> $ret)*);↩
);↩
(↩
$(↩
$(#[doc=$doc:expr] #[cfg($cfg:meta)])*↩
pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;↩
)+↩
) => (↩
use std::cell::{RefCell};↩
use std::sync::{Arc};↩
use std::path::{Path, PathBuf};↩
/// The (minimum) version of a `libclang` shared library.↩
#[allow(missing_docs)]↩
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]↩
pub enum Version {↩
V3_5 = 35,↩
V3_6 = 36,↩
V3_7 = 37,↩
V3_8 = 38,↩
V3_9 = 39,↩
V4_0 = 40,↩
V5_0 = 50,↩
V6_0 = 60,↩
V7_0 = 70,↩
V8_0 = 80,↩
V9_0 = 90,↩
}↩
/// The set of functions loaded dynamically.↩
#[derive(Debug, Default)]↩
pub struct Functions {↩
$(↩
$(#[doc=$doc] #[cfg($cfg)])*↩
pub $name: Option<unsafe extern fn($($pname: $pty), *) $(-> $ret)*>,↩
)+↩
}↩
/// A dynamically loaded instance of the `libclang` library.↩
#[derive(Debug)]↩
pub struct SharedLibrary {↩
library: libloading::Library,↩
path: PathBuf,↩
pub functions: Functions,↩
}↩
impl SharedLibrary {↩
fn new(library: libloading::Library, path: PathBuf) -> Self {↩
Self { library, path, functions: Functions::default() }↩
}↩
/// Returns the path to this `libclang` shared library.↩
pub fn path(&self) -> &Path {↩
&self.path↩
}↩
/// Returns the (minimum) version of this `libclang` shared library.↩
///↩
/// If this returns `None`, it indicates that the version is too old↩
/// to be supported by this crate (i.e., `3.4` or earlier). If the↩
/// version of this shared library is more recent than that fully↩
/// supported by this crate, the most recent fully supported version↩
/// will be returned.↩
pub fn version(&self) -> Option<Version> {↩
macro_rules! check {↩
($fn:expr, $version:ident) => {↩
if self.library.get::<unsafe extern fn()>($fn).is_ok() {↩
return Some(Version::$version);↩
}↩
};↩
}↩
unsafe {↩
check!(b"clang_Cursor_isAnonymousRecordDecl", V9_0);↩
check!(b"clang_Cursor_getObjCPropertyGetterName", V8_0);↩
check!(b"clang_File_tryGetRealPathName", V7_0);↩
check!(b"clang_CXIndex_setInvocationEmissionPathOption", V6_0);↩
check!(b"clang_Cursor_isExternalSymbol", V5_0);↩
check!(b"clang_EvalResult_getAsLongLong", V4_0);↩
check!(b"clang_CXXConstructor_isConvertingConstructor", V3_9);↩
check!(b"clang_CXXField_isMutable", V3_8);↩
check!(b"clang_Cursor_getOffsetOfField", V3_7);↩
check!(b"clang_Cursor_getStorageClass", V3_6);↩
check!(b"clang_Type_getNumTemplateArguments", V3_5);↩
}↩
None↩
}↩
}↩
thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None));↩
/// Returns whether a `libclang` shared library is loaded on this thread.↩
pub fn is_loaded() -> bool {↩
LIBRARY.with(|l| l.borrow().is_some())↩
}↩
fn with_library<T, F>(f: F) -> Option<T> where F: FnOnce(&SharedLibrary) -> T {↩
LIBRARY.with(|l| {↩
match l.borrow().as_ref() {↩
Some(library) => Some(f(&library)),↩
_ => None,↩
}↩
})↩
}↩
$(↩
#[cfg_attr(feature="cargo-clippy", allow(clippy::missing_safety_doc))]↩
#[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))]↩
$(#[doc=$doc] #[cfg($cfg)])*↩
pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {↩
let f = with_library(|l| {↩
l.functions.$name.expect(concat!(↩
"`libclang` function not loaded: `",↩
stringify!($name),↩
"`. This crate requires that `libclang` 3.9 or later be installed on your ",↩
"system. For more information on how to accomplish this, see here: ",↩
}).expect("a `libclang` shared library is not loaded on this thread");↩
f($($pname), *)↩
}↩
$(#[doc=$doc] #[cfg($cfg)])*↩
pub mod $name {↩
pub fn is_loaded() -> bool {↩
super::with_library(|l| l.functions.$name.is_some()).unwrap_or(false)↩
}↩
}↩
)+↩
mod load {↩
$(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+↩
}↩
/// Loads a `libclang` shared library and returns the library instance.↩
///↩
/// This function does not attempt to load any functions from the shared library. The caller↩
/// is responsible for loading the functions they require.↩
///↩
/// # Failures↩
///↩
/// * a `libclang` shared library could not be found↩
/// * the `libclang` shared library could not be opened↩
pub fn load_manually() -> Result<SharedLibrary, String> {↩
#[allow(dead_code)]↩
mod build {↩
include!(concat!(env!("OUT_DIR"), "/macros.rs"));↩
pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }↩
pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); }↩
}↩
let (directory, filename) = build::dynamic::find(true)?;↩
let path = directory.join(filename);↩
unsafe {↩
let library = libloading::Library::new(&path).map_err(|e| {↩
format!(↩
"the `libclang` shared library at {} could not be opened: {}",↩
path.display(),↩
e,↩
)↩
});↩
let mut library = SharedLibrary::new(library?, path);↩
$(load::$name(&mut library);)+↩
Ok(library)↩
}↩
}↩
/// Loads a `libclang` shared library for use in the current thread.↩
///↩
/// This functions attempts to load all the functions in the shared library. Whether a↩
/// function has been loaded can be tested by calling the `is_loaded` function on the↩
/// module with the same name as the function (e.g., `clang_createIndex::is_loaded()` for↩
/// the `clang_createIndex` function).↩
///↩
/// # Failures↩
///↩
/// * a `libclang` shared library could not be found↩
/// * the `libclang` shared library could not be opened↩
#[allow(dead_code)]↩
pub fn load() -> Result<(), String> {↩
let library = Arc::new(load_manually()?);↩
LIBRARY.with(|l| *l.borrow_mut() = Some(library));↩
Ok(())↩
}↩
/// Unloads the `libclang` shared library in use in the current thread.↩
///↩
/// # Failures↩
///↩
/// * a `libclang` shared library is not in use in the current thread↩
pub fn unload() -> Result<(), String> {↩
let library = set_library(None);↩
if library.is_some() {↩
Ok(())↩
} else {↩
Err("a `libclang` shared library is not in use in the current thread".into())↩
}↩
}↩
/// Returns the library instance stored in TLS.↩
///↩
/// This functions allows for sharing library instances between threads.↩
pub fn get_library() -> Option<Arc<SharedLibrary>> {↩
LIBRARY.with(|l| l.borrow_mut().clone())↩
}↩
/// Sets the library instance stored in TLS and returns the previous library.↩
///↩
/// This functions allows for sharing library instances between threads.↩
pub fn set_library(library: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {↩
LIBRARY.with(|l| mem::replace(&mut *l.borrow_mut(), library))↩
}↩
)↩
}↩
#[cfg(not(feature = "runtime"))]↩
macro_rules! link {↩
(↩
$(↩
$(#[doc=$doc:expr] #[cfg($cfg:meta)])*↩
pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;↩
)+↩
) => (↩
extern {↩
$(↩
$(#[doc=$doc] #[cfg($cfg)])*↩
pub fn $name($($pname: $pty), *) $(-> $ret)*;↩
)+↩
}↩
$(↩
$(#[doc=$doc] #[cfg($cfg)])*↩
pub mod $name {↩
pub fn is_loaded() -> bool { true }↩
}↩
)+↩
)↩
}↩