// 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::fmt; | |
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, | |
V11_0 = 110, | |
V12_0 = 120, | |
V16_0 = 160, | |
V17_0 = 170, | |
} | |
impl fmt::Display for Version { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
use Version::*; | |
match self { | |
V3_5 => write!(f, "3.5.x"), | |
V3_6 => write!(f, "3.6.x"), | |
V3_7 => write!(f, "3.7.x"), | |
V3_8 => write!(f, "3.8.x"), | |
V3_9 => write!(f, "3.9.x"), | |
V4_0 => write!(f, "4.0.x"), | |
V5_0 => write!(f, "5.0.x"), | |
V6_0 => write!(f, "6.0.x"), | |
V7_0 => write!(f, "7.0.x"), | |
V8_0 => write!(f, "8.0.x"), | |
V9_0 => write!(f, "9.0.x - 10.0.x"), | |
V11_0 => write!(f, "11.0.x"), | |
V12_0 => write!(f, "12.0.x - 15.0.x"), | |
V16_0 => write!(f, "16.0.x"), | |
V17_0 => write!(f, "17.0.x or later"), | |
} | |
} | |
} | |
/// 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_CXXMethod_isExplicit", V17_0); | |
check!(b"clang_CXXMethod_isCopyAssignmentOperator", V16_0); | |
check!(b"clang_Cursor_getVarDeclInitializer", V12_0); | |
check!(b"clang_Type_getValueType", V11_0); | |
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(|library| { | |
if let Some(function) = library.functions.$name { | |
function | |
} else { | |
panic!( | |
r#" | |
A `libclang` function was called that is not supported by the loaded `libclang` instance. | |
called function = `{0}` | |
loaded `libclang` instance = {1} | |
The minimum `libclang` requirement for this particular function can be found here: | |
https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html | |
Instructions for installing `libclang` can be found here: | |
https://rust-lang.github.io/rust-bindgen/requirements.html | |
"#, | |
stringify!($name), | |
library | |
.version() | |
.map(|v| format!("{}", v)) | |
.unwrap_or_else(|| "unsupported version".into()), | |
); | |
} | |
}).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 } | |
} | |
)+ | |
) | |
} |