com_scrape/
generator.rs

1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::env;
4use std::error::Error;
5use std::io::Write;
6use std::path::{Path, PathBuf};
7
8use crate::clang::*;
9use crate::parse::*;
10use crate::print::*;
11
12const HOST_TARGET: &'static str = include_str!(concat!(env!("OUT_DIR"), "/host-target.txt"));
13
14// Some target triples are different between rustc and clang.
15// See https://github.com/rust-lang/rust-bindgen/blob/05ebcace15a8784e5a5b1001a3b755b866fac901/bindgen/lib.rs#L670
16fn rust_to_clang_target(rust_target: &str) -> String {
17    if rust_target.starts_with("aarch64-apple-") {
18        return "arm64-apple-".to_owned() + &rust_target["aarch64-apple-".len()..];
19    }
20
21    rust_target.to_owned()
22}
23
24/// Builder struct for configuring and generating bindings.
25pub struct Generator {
26    pub(crate) include_paths: Vec<PathBuf>,
27    pub(crate) target: Option<String>,
28    pub(crate) skip_types: HashSet<String>,
29    pub(crate) skip_interface_traits: HashSet<String>,
30    pub(crate) constant_parser: Option<Box<dyn Fn(&[String]) -> Option<String>>>,
31    pub(crate) iid_generator: Option<Box<dyn Fn(&str) -> String>>,
32    pub(crate) query_interface_fn: Option<String>,
33    pub(crate) add_ref_fn: Option<String>,
34    pub(crate) release_fn: Option<String>,
35}
36
37impl Default for Generator {
38    fn default() -> Generator {
39        Generator {
40            include_paths: Vec::new(),
41            target: None,
42            skip_types: HashSet::new(),
43            skip_interface_traits: HashSet::new(),
44            constant_parser: None,
45            iid_generator: None,
46            query_interface_fn: None,
47            add_ref_fn: None,
48            release_fn: None,
49        }
50    }
51}
52
53impl Generator {
54    /// Adds `path` to the list of include paths to pass to `libclang`.
55    pub fn include_path<T: AsRef<Path>>(mut self, path: T) -> Self {
56        self.include_paths.push(path.as_ref().to_path_buf());
57        self
58    }
59
60    /// Specify the target triple for which bindings should be generated.
61    pub fn target<T: AsRef<str>>(mut self, target: T) -> Self {
62        self.target = Some(target.as_ref().to_string());
63        self
64    }
65
66    /// Do not generate bindings for `type_`.
67    pub fn skip_type<T: AsRef<str>>(mut self, type_: T) -> Self {
68        self.skip_types.insert(type_.as_ref().to_string());
69        self
70    }
71
72    /// Do not generate bindings for `types`.
73    pub fn skip_types<S: AsRef<str>, T: AsRef<[S]>>(mut self, types: T) -> Self {
74        self.skip_types
75            .extend(types.as_ref().iter().map(|s| s.as_ref().to_string()));
76        self
77    }
78
79    /// Do not generate an interface trait for `interface`.
80    pub fn skip_interface_trait<T: AsRef<str>>(mut self, interface: T) -> Self {
81        self.skip_interface_traits
82            .insert(interface.as_ref().to_string());
83        self
84    }
85
86    /// Do not generate interface traits for `interfaces`.
87    pub fn skip_interface_traits<'a, T: AsRef<[&'a str]>>(mut self, interfaces: T) -> Self {
88        self.skip_interface_traits
89            .extend(interfaces.as_ref().iter().map(|s| s.to_string()));
90        self
91    }
92
93    /// Registers a callback for parsing constant definitions which `libclang` is not able to
94    /// evaluate.
95    ///
96    /// The callback will be passed a slice of tokens, and its output (if not `None`) will be
97    /// included in the generated bindings.
98    pub fn constant_parser<F>(mut self, f: F) -> Self
99    where
100        F: Fn(&[String]) -> Option<String> + 'static,
101    {
102        self.constant_parser = Some(Box::new(f));
103        self
104    }
105
106    /// Registers a callback which should, when given the name of an interface as a string, return
107    /// a string containing a Rust expression evaluating to the `Guid` value for that interface.
108    pub fn iid_generator<F>(mut self, f: F) -> Self
109    where
110        F: Fn(&str) -> String + 'static,
111    {
112        self.iid_generator = Some(Box::new(f));
113        self
114    }
115
116    /// Registers a function which will be called by the implementations of
117    /// `Unknown::query_interface` for generated interface types.
118    ///
119    /// The function should be in scope where the resulting bindings are placed, and it should have
120    /// the same type signature as `Unknown::query_interface`.
121    pub fn query_interface_fn<T: AsRef<str>>(mut self, f: T) -> Self {
122        self.query_interface_fn = Some(f.as_ref().to_string());
123        self
124    }
125
126    /// Registers a function which will be called by the implementations of `Unknown::add_ref` for
127    /// generated interface types.
128    ///
129    /// The function should be in scope where the resulting bindings are placed, and it should have
130    /// the same type signature as `Unknown::add_ref`.
131    pub fn add_ref_fn<T: AsRef<str>>(mut self, f: T) -> Self {
132        self.add_ref_fn = Some(f.as_ref().to_string());
133        self
134    }
135
136    /// Registers a function which will be called by the implementations of `Unknown::release` for
137    /// generated interface types.
138    ///
139    /// The function should be in scope where the resulting bindings are placed, and it should have
140    /// the same type signature as `Unknown::release`.
141    pub fn release_fn<T: AsRef<str>>(mut self, f: T) -> Self {
142        self.release_fn = Some(f.as_ref().to_string());
143        self
144    }
145
146    /// Generates Rust bindings for the C++ definitions in `source` and outputs them via `sink`.
147    pub fn generate<T: AsRef<str>, W: Write>(
148        &self,
149        source: T,
150        sink: W,
151    ) -> Result<(), Box<dyn Error>> {
152        if !clang_sys::is_loaded() {
153            clang_sys::load()?;
154        }
155
156        let rust_target = if let Some(target) = &self.target {
157            Cow::from(target)
158        } else if let Ok(target) = env::var("TARGET") {
159            Cow::from(target)
160        } else {
161            Cow::from(HOST_TARGET)
162        };
163        let clang_target = rust_to_clang_target(&rust_target);
164
165        let unit = TranslationUnit::new(source.as_ref(), &self.include_paths, Some(&clang_target))?;
166
167        let namespace = Namespace::parse(&unit.cursor(), &self)?;
168
169        let mut printer = RustPrinter::new(sink, &self);
170        printer.print_namespace(&namespace)?;
171
172        Ok(())
173    }
174}