dart_bindgen/
lib.rs

1#![deny(
2    unsafe_code,
3    missing_docs,
4    missing_debug_implementations,
5    missing_copy_implementations,
6    elided_lifetimes_in_paths,
7    rust_2018_idioms,
8    clippy::fallible_impl_from,
9    clippy::missing_const_for_fn
10)]
11#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55122894")]
12//! ## Dart Bindgen
13//! Generate Dart FFI bindings to C Header file.
14//!
15//! ### Supported C Language Features
16//! - Functions
17//! - Function Pointer (aka `callback`)
18//! - Simple structs (NOTE: Nested structs is not supported yet, open a PR?)
19//!
20//! ## Example
21//! in your `build.rs`:
22//! ```rust,ignore
23//!  let config = DynamicLibraryConfig {
24//!       ios: DynamicLibraryCreationMode::Executable.into(),
25//!       android: DynamicLibraryCreationMode::open("libsimple.so").into(),
26//!       ..Default::default()
27//!   };
28//!   // load the c header file, with config and lib name
29//!   let codegen = Codegen::builder()
30//!       .with_src_header("simple-ffi/include/simple.h")
31//!       .with_lib_name("libsimple")
32//!       .with_config(config)
33//!       .build()?;
34//!   // generate the dart code and get the bindings back
35//!   let bindings = codegen.generate()?;
36//!   // write the bindings to your dart package
37//!   // and start using it to write your own high level abstraction.
38//!   bindings.write_to_file("simple/lib/ffi.dart")?;
39//! ```
40use std::{
41    collections::HashMap,
42    fmt, fs,
43    io::{self, Write},
44    path::PathBuf,
45};
46
47use clang::{Clang, Entity, EntityKind, Index, Type, TypeKind};
48use log::debug;
49
50use config::DynamicLibraryConfig;
51use dart_source_writer::{DartSourceWriter, ImportedUri};
52use enumeration::{Enum, EnumField};
53use errors::CodegenError;
54use func::{Func, Param};
55use structure::{Field, Struct};
56
57/// Bindgens config for loading `DynamicLibrary` on each Platform.
58pub mod config;
59mod dart_source_writer;
60mod enumeration;
61mod errors;
62mod func;
63mod structure;
64
65/// Abstract over Func, Struct and Global.
66trait Element {
67    /// Get the name of this element
68    fn name(&self) -> &str;
69
70    /// Optional documentation of this element
71    fn documentation(&self) -> Option<&str>;
72
73    /// Used to Write the Current Element to the Final Source File
74    fn generate_source(&self, w: &mut DartSourceWriter) -> io::Result<()>;
75}
76
77/// Dart Code Generator
78pub struct Codegen {
79    src_header: PathBuf,
80    lib_name: String,
81    allo_isolate: bool,
82    config: DynamicLibraryConfig,
83    elements: HashMap<String, Box<dyn Element>>,
84}
85
86impl Codegen {
87    /// Create new [`Codegen`] using it's Builder
88    pub fn builder() -> CodegenBuilder { CodegenBuilder::default() }
89
90    /// Generate the [`Bindings`]
91    pub fn generate(mut self) -> Result<Bindings, CodegenError> {
92        debug!("Starting Codegen!");
93        debug!("Building dsw");
94        let mut dsw = Self::build_dsw();
95        debug!("dsw is ready");
96        self.generate_open_dl(&mut dsw)?;
97        let clang = Clang::new()?;
98        let index = Index::new(&clang, true, false);
99        debug!("start parsing the C header file at {:?}.", self.src_header);
100        let parser = index.parser(self.src_header);
101        let tu = parser.parse()?;
102        debug!("Done Parsed the header file");
103        let entity = tu.get_entity();
104        let entities = entity
105            .get_children()
106            .into_iter()
107            .filter(|e| !e.is_in_system_header())
108            .peekable();
109
110        for e in entities {
111            let kind = e.get_kind();
112            debug!("Entity: {:?}", e);
113
114            match kind {
115                EntityKind::FunctionDecl => {
116                    debug!("Got Function: {:?}", e);
117                    // handle functions
118                    let func = Self::parse_function(e)?;
119                    self.elements
120                        .insert(func.name().to_owned(), Box::new(func));
121                },
122                EntityKind::StructDecl => {
123                    debug!("Got Struct: {:?}", e);
124
125                    match Self::parse_struct(e, None) {
126                        // if its unnamed in this case and not anonymous, then
127                        // its ok, as it will be discovered by the typedef
128                        // parser
129                        Err(CodegenError::UnnamedStruct) => Ok(()),
130                        Err(err) => Err(err),
131                        Ok(s) => {
132                            self.elements
133                                .insert(s.name().to_owned(), Box::new(s));
134
135                            Ok(())
136                        },
137                    }?;
138                },
139                EntityKind::EnumDecl => {
140                    debug!("Got Enum: {:?}", e);
141
142                    match Self::parse_enum(e, None) {
143                        // if its unnamed in this case and not anonymous, then
144                        // its ok, as it will be discovered by the typedef
145                        // parser
146                        Err(CodegenError::UnnamedEnum) => Ok(()),
147                        Err(err) => Err(err),
148                        Ok(s) => {
149                            self.elements
150                                .insert(s.name().to_owned(), Box::new(s));
151
152                            Ok(())
153                        },
154                    }?;
155                },
156                EntityKind::TypedefDecl => {
157                    debug!("Got Typedef: {:?}", e);
158
159                    for child in e.get_children() {
160                        match child.get_kind() {
161                            EntityKind::StructDecl => {
162                                debug!("Got struct in Typedef: {:?}", child);
163                                let s =
164                                    Self::parse_struct(child, e.get_name())?;
165
166                                self.elements
167                                    .insert(s.name().to_owned(), Box::new(s));
168                            },
169                            EntityKind::EnumDecl => {
170                                debug!("Got enum in Typedef: {:?}", child);
171                                let s = Self::parse_enum(child, e.get_name())?;
172
173                                self.elements
174                                    .insert(s.name().to_owned(), Box::new(s));
175                            },
176                            _ => {},
177                        }
178                    }
179                },
180                _ => {},
181            }
182        }
183        if self.allo_isolate {
184            let func = Func::new(
185                "store_dart_post_cobject".to_string(),
186                Some(String::from("Binding to `allo-isolate` crate")),
187                vec![Param::new(
188                    Some("ptr".to_string()),
189                    String::from("Pointer<NativeFunction<Int8 Function(Int64, Pointer<Dart_CObject>)>>"),
190                )],
191                String::from("void"),
192            );
193            // insert new element
194            self.elements.insert(
195                String::from("store_dart_post_cobject"),
196                Box::new(func),
197            );
198        }
199        debug!("Generating Dart Source...");
200        // trying to sort the elements to avoid useless changes in git for
201        // example since HashMap is not `Ord`
202        let mut elements: Vec<_> = self.elements.values().collect();
203        elements.sort_by_key(|k| k.name());
204
205        for el in elements {
206            el.generate_source(&mut dsw)?;
207        }
208        debug!("Done.");
209        Ok(Bindings::new(dsw))
210    }
211
212    fn generate_open_dl(
213        &self,
214        dsw: &mut DartSourceWriter,
215    ) -> Result<(), CodegenError> {
216        dsw.set_lib_name(&self.lib_name);
217        debug!("Generating Code for opening DynamicLibrary");
218        writeln!(dsw, "final DynamicLibrary _dl = _open();")?;
219        writeln!(dsw, "/// Reference to the Dynamic Library, it should be only used for low-level access")?;
220        writeln!(dsw, "final DynamicLibrary dl = _dl;")?;
221        writeln!(dsw, "DynamicLibrary _open() {{")?;
222        if let Some(ref config) = self.config.windows {
223            debug!("Generating _open Code for Windows");
224            writeln!(dsw, "  if (Platform.isWindows) return {};", config)?;
225        }
226        if let Some(ref config) = self.config.linux {
227            debug!("Generating _open Code for Linux");
228            writeln!(dsw, "  if (Platform.isLinux) return {};", config)?;
229        }
230        if let Some(ref config) = self.config.android {
231            debug!("Generating _open Code for Android");
232            writeln!(dsw, "  if (Platform.isAndroid) return {};", config)?;
233        }
234        if let Some(ref config) = self.config.ios {
235            debug!("Generating _open Code for iOS");
236            writeln!(dsw, "  if (Platform.isIOS) return {};", config)?;
237        }
238        if let Some(ref config) = self.config.macos {
239            debug!("Generating _open Code for macOS");
240            writeln!(dsw, "  if (Platform.isMacOS) return {};", config)?;
241        }
242        if let Some(ref config) = self.config.fuchsia {
243            debug!("Generating _open Code for Fuchsia");
244            writeln!(dsw, "  if (Platform.isFuchsia) return {};", config)?;
245        }
246        writeln!(
247            dsw,
248            "  throw UnsupportedError('This platform is not supported.');"
249        )?;
250        writeln!(dsw, "}}")?;
251        debug!("Generating Code for opening DynamicLibrary done.");
252        Ok(())
253    }
254
255    fn parse_function(
256        entity: Entity<'_>,
257    ) -> Result<impl Element, CodegenError> {
258        let name = entity.get_name().ok_or(CodegenError::UnnamedFunction)?;
259        debug!("Function: {}", name);
260        let params = match entity.get_arguments() {
261            Some(entities) => Self::parse_fn_params(entities)?,
262            None => Vec::new(),
263        };
264        debug!("Function Params: {:?}", params);
265        let docs = entity.get_parsed_comment().map(|c| c.as_html());
266        debug!("Function Docs: {:?}", docs);
267        let return_ty = entity
268            .get_result_type()
269            .ok_or(CodegenError::UnknownFunctionReturnType)?
270            .get_canonical_type()
271            .get_display_name();
272        debug!("Function Return Type: {}", return_ty);
273        Ok(Func::new(name, docs, params, return_ty))
274    }
275
276    fn parse_fn_params(
277        entities: Vec<Entity<'_>>,
278    ) -> Result<Vec<Param>, CodegenError> {
279        let mut params = Vec::with_capacity(entities.capacity());
280        for e in entities {
281            debug!("Param: {:?}", e);
282            let name = e.get_name();
283            debug!("Param Name: {:?}", name);
284            let ty = e
285                .get_type()
286                .ok_or(CodegenError::UnknownParamType)?
287                .get_canonical_type();
288            debug!("Param Type: {:?}", ty);
289            let ty = Self::parse_ty(ty)?;
290            debug!("Param Type Display Name: {}", ty);
291            params.push(Param::new(name, ty));
292        }
293        Ok(params)
294    }
295
296    fn parse_fn_proto(ty: Type<'_>) -> Result<String, CodegenError> {
297        let mut dsw = DartSourceWriter::new();
298        debug!("Function Proto: {:?}", ty);
299        let return_ty = ty
300            .get_canonical_type()
301            .get_result_type()
302            .ok_or(CodegenError::UnknownFunctionReturnType)?
303            .get_canonical_type()
304            .get_display_name();
305        let params = match ty.get_argument_types() {
306            Some(arg_ty) => arg_ty
307                .iter()
308                .map(|ty| ty.get_display_name())
309                .map(|ty| dsw.get_ctype(&ty))
310                .collect(),
311            None => Vec::new(),
312        };
313        write!(
314            dsw,
315            "Pointer<NativeFunction<{} Function({})>>",
316            dsw.get_ctype(&return_ty),
317            params.join(", ")
318        )?;
319        Ok(dsw.to_string())
320    }
321
322    fn parse_struct(
323        entity: Entity<'_>,
324        name: Option<String>,
325    ) -> Result<impl Element, CodegenError> {
326        if entity.is_anonymous() {
327            return Err(CodegenError::AnonymousEntity);
328        }
329
330        let name = name
331            .or_else(|| entity.get_name())
332            .ok_or(CodegenError::UnnamedStruct)?;
333
334        debug!("Struct: {}", name);
335        let children = entity.get_children();
336        let mut fields = Vec::with_capacity(children.capacity());
337
338        for child in children {
339            let name =
340                child.get_name().ok_or(CodegenError::UnnamedStructField)?;
341
342            let ty = child
343                .get_type()
344                .ok_or(CodegenError::UnknownParamType)?
345                .get_canonical_type();
346            let ty = Self::parse_ty(ty)?;
347            fields.push(Field::new(name, ty));
348        }
349        let docs = entity.get_parsed_comment().map(|c| c.as_html());
350        Ok(Struct::new(name, docs, fields))
351    }
352
353    fn parse_enum(
354        entity: Entity<'_>,
355        name: Option<String>,
356    ) -> Result<impl Element, CodegenError> {
357        if entity.is_anonymous() {
358            return Err(CodegenError::AnonymousEntity);
359        }
360
361        let name = name
362            .or_else(|| entity.get_name())
363            .ok_or(CodegenError::UnnamedEnum)?;
364
365        debug!("Enum: {}", name);
366        let children = entity.get_children();
367        let mut fields = Vec::with_capacity(children.capacity());
368
369        for child in children {
370            debug!("Enum field: {:?}", child);
371            let name =
372                child.get_name().ok_or(CodegenError::UnnamedEnumField)?;
373
374            let value = child
375                .get_enum_constant_value()
376                .ok_or(CodegenError::UnknownEnumFieldConstantValue)?
377                .1;
378
379            fields.push(EnumField::new(name, value));
380        }
381        let docs = entity.get_parsed_comment().map(|c| c.as_html());
382        Ok(Enum::new(name, docs, fields))
383    }
384
385    fn parse_ty(ty: Type<'_>) -> Result<String, CodegenError> {
386        use TypeKind::*;
387        if ty.get_kind() == Pointer {
388            let pointee_type = ty
389                .get_pointee_type()
390                .ok_or(CodegenError::UnknownPointeeType)?;
391            let kind = pointee_type.get_kind();
392            if kind == FunctionPrototype || kind == FunctionNoPrototype {
393                Self::parse_fn_proto(pointee_type)
394            } else {
395                Ok(ty.get_display_name())
396            }
397        } else {
398            Ok(ty.get_display_name())
399        }
400    }
401
402    fn build_dsw() -> DartSourceWriter {
403        let mut dsw = DartSourceWriter::new();
404        // disable some lints
405        writeln!(dsw, "// ignore_for_file: unused_import, camel_case_types, non_constant_identifier_names").unwrap();
406        dsw.import(ImportedUri::new(String::from("dart:ffi")));
407        dsw.import(ImportedUri::new(String::from("dart:io")));
408        let mut ffi = ImportedUri::new(String::from("package:ffi/ffi.dart"));
409        ffi.with_prefix(String::from("ffi"));
410        dsw.import(ffi);
411        dsw
412    }
413}
414
415impl fmt::Debug for Codegen {
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        f.debug_struct("Codegen")
418            .field("src_header", &self.src_header)
419            .field("lib_name", &self.lib_name)
420            .field("config", &self.config)
421            .finish()
422    }
423}
424
425/// The [`Codegen`] Builder
426///
427/// start by calling [`Codegen::builder()`]
428#[derive(Clone, Debug, Default)]
429pub struct CodegenBuilder {
430    src_header: PathBuf,
431    lib_name: String,
432    allo_isolate: bool,
433    config: Option<DynamicLibraryConfig>,
434}
435
436impl CodegenBuilder {
437    /// The Input `C` header file
438    pub fn with_src_header(mut self, path: impl Into<PathBuf>) -> Self {
439        self.src_header = path.into();
440        self
441    }
442
443    /// The output lib name, for example `libfoo`
444    ///
445    /// used for docs
446    pub fn with_lib_name(mut self, name: impl Into<String>) -> Self {
447        self.lib_name = name.into();
448        self
449    }
450
451    /// Defines, how the dynamic library should be loaded on each of dart's
452    /// known platforms.
453    #[allow(clippy::missing_const_for_fn)]
454    pub fn with_config(mut self, config: DynamicLibraryConfig) -> Self {
455        self.config = Some(config);
456        self
457    }
458
459    /// Integration with [`allo-isolate`](https://crates.io/crates/allo-isolate)
460    ///
461    /// This allow dart-bindgen to add the code required by allo-isolate
462    /// i.e `store_dart_post_cobject` fn
463    pub const fn with_allo_isolate(mut self) -> Self {
464        self.allo_isolate = true;
465        self
466    }
467
468    /// Consumes the builder and validate everyting, then create the [`Codegen`]
469    pub fn build(self) -> Result<Codegen, CodegenError> {
470        if self.lib_name.is_empty() {
471            return Err(CodegenError::Builder(
472                "Please Provide the C lib name.",
473            ));
474        }
475
476        let config = self.config.ok_or(
477            CodegenError::Builder("Missing `DynamicLibraryConfig` did you forget to call `with_config` builder method?.")
478        )?;
479
480        Ok(Codegen {
481            src_header: self.src_header,
482            lib_name: self.lib_name,
483            allo_isolate: self.allo_isolate,
484            config,
485            elements: HashMap::new(),
486        })
487    }
488}
489
490/// A bindings using `dart:ffi` that could be written.
491#[derive(Debug)]
492pub struct Bindings {
493    dsw: DartSourceWriter,
494}
495
496impl Bindings {
497    pub(crate) const fn new(dsw: DartSourceWriter) -> Self { Self { dsw } }
498
499    /// Write dart ffi bindings to a file
500    pub fn write_to_file(
501        &self,
502        path: impl Into<PathBuf>,
503    ) -> Result<(), CodegenError> {
504        let mut out = fs::OpenOptions::new()
505            .read(false)
506            .write(true)
507            .truncate(true)
508            .create(true)
509            .open(path.into())?;
510        debug!("Writing Dart Source File...");
511        write!(out, "{}", self.dsw)?;
512        Ok(())
513    }
514
515    /// Write dart ffi bindings to anything that can we write into :D
516    ///
517    /// see also:
518    ///  * [`Bindings::write_to_file`]
519    pub fn write(&self, w: &mut impl Write) -> Result<(), CodegenError> {
520        debug!("Writing Dart Source File...");
521        write!(w, "{}", self.dsw)?;
522        Ok(())
523    }
524}