| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| use std::borrow::Cow; |
| use std::cell::RefCell; |
| use std::collections::HashMap; |
| use std::fs; |
| use std::fs::File; |
| use std::io::{Read, Write}; |
| use std::path; |
| use std::rc::Rc; |
| |
| use crate::bindgen::config::{Config, Language}; |
| use crate::bindgen::ir::{ |
| Constant, Function, ItemContainer, ItemMap, Path as BindgenPath, Static, Struct, Typedef, |
| }; |
| use crate::bindgen::writer::{Source, SourceWriter}; |
| |
| /// A bindings header that can be written. |
| pub struct Bindings { |
| pub config: Config, |
| /// The map from path to struct, used to lookup whether a given type is a |
| /// transparent struct. This is needed to generate code for constants. |
| struct_map: ItemMap<Struct>, |
| typedef_map: ItemMap<Typedef>, |
| struct_fileds_memo: RefCell<HashMap<BindgenPath, Rc<Vec<String>>>>, |
| globals: Vec<Static>, |
| constants: Vec<Constant>, |
| items: Vec<ItemContainer>, |
| functions: Vec<Function>, |
| /// Bindings are generated by a recursive call to cbindgen |
| /// and shouldn't do anything when written anywhere. |
| noop: bool, |
| } |
| |
| #[derive(PartialEq, Eq)] |
| enum NamespaceOperation { |
| Open, |
| Close, |
| } |
| |
| impl Bindings { |
| #[allow(clippy::too_many_arguments)] |
| pub(crate) fn new( |
| config: Config, |
| struct_map: ItemMap<Struct>, |
| typedef_map: ItemMap<Typedef>, |
| constants: Vec<Constant>, |
| globals: Vec<Static>, |
| items: Vec<ItemContainer>, |
| functions: Vec<Function>, |
| noop: bool, |
| ) -> Bindings { |
| Bindings { |
| config, |
| struct_map, |
| typedef_map, |
| struct_fileds_memo: Default::default(), |
| globals, |
| constants, |
| items, |
| functions, |
| noop, |
| } |
| } |
| |
| // FIXME(emilio): What to do when the configuration doesn't match? |
| pub fn struct_is_transparent(&self, path: &BindgenPath) -> bool { |
| let mut any = false; |
| self.struct_map.for_items(path, |s| any |= s.is_transparent); |
| any |
| } |
| |
| /// Peels through typedefs to allow resolving structs. |
| fn resolved_struct_path<'a>(&self, path: &'a BindgenPath) -> Cow<'a, BindgenPath> { |
| use crate::bindgen::ir::Type; |
| |
| let mut resolved_path = Cow::Borrowed(path); |
| loop { |
| let mut found = None; |
| self.typedef_map.for_items(&resolved_path, |item| { |
| if let Type::Path(ref p) = item.aliased { |
| found = Some(p.path().clone()); |
| } |
| }); |
| resolved_path = match found { |
| Some(p) => Cow::Owned(p), |
| None => break, |
| } |
| } |
| resolved_path |
| } |
| |
| pub fn struct_exists(&self, path: &BindgenPath) -> bool { |
| let mut any = false; |
| self.struct_map |
| .for_items(&self.resolved_struct_path(path), |_| any = true); |
| any |
| } |
| |
| pub fn struct_field_names(&self, path: &BindgenPath) -> Rc<Vec<String>> { |
| let mut memos = self.struct_fileds_memo.borrow_mut(); |
| if let Some(memo) = memos.get(path) { |
| return memo.clone(); |
| } |
| |
| let resolved_path = self.resolved_struct_path(path); |
| |
| let mut fields = Vec::<String>::new(); |
| self.struct_map.for_items(&resolved_path, |st| { |
| let mut pos: usize = 0; |
| for field in &st.fields { |
| if let Some(found_pos) = fields.iter().position(|v| *v == field.name) { |
| pos = found_pos + 1; |
| } else { |
| fields.insert(pos, field.name.clone()); |
| pos += 1; |
| } |
| } |
| }); |
| |
| let fields = Rc::new(fields); |
| memos.insert(path.clone(), fields.clone()); |
| if let Cow::Owned(p) = resolved_path { |
| memos.insert(p, fields.clone()); |
| } |
| fields |
| } |
| |
| pub fn write_to_file<P: AsRef<path::Path>>(&self, path: P) -> bool { |
| if self.noop { |
| return false; |
| } |
| |
| // Don't compare files if we've never written this file before |
| if !path.as_ref().is_file() { |
| if let Some(parent) = path::Path::new(path.as_ref()).parent() { |
| fs::create_dir_all(parent).unwrap(); |
| } |
| self.write(File::create(path).unwrap()); |
| return true; |
| } |
| |
| let mut new_file_contents = Vec::new(); |
| self.write(&mut new_file_contents); |
| |
| let mut old_file_contents = Vec::new(); |
| { |
| let mut old_file = File::open(&path).unwrap(); |
| old_file.read_to_end(&mut old_file_contents).unwrap(); |
| } |
| |
| if old_file_contents != new_file_contents { |
| let mut new_file = File::create(&path).unwrap(); |
| new_file.write_all(&new_file_contents).unwrap(); |
| true |
| } else { |
| false |
| } |
| } |
| |
| pub fn write_headers<F: Write>(&self, out: &mut SourceWriter<F>) { |
| if self.noop { |
| return; |
| } |
| |
| if let Some(ref f) = self.config.header { |
| out.new_line_if_not_start(); |
| write!(out, "{}", f); |
| out.new_line(); |
| } |
| if let Some(f) = self.config.include_guard() { |
| out.new_line_if_not_start(); |
| write!(out, "#ifndef {}", f); |
| out.new_line(); |
| write!(out, "#define {}", f); |
| out.new_line(); |
| } |
| if self.config.pragma_once && self.config.language != Language::Cython { |
| out.new_line_if_not_start(); |
| write!(out, "#pragma once"); |
| out.new_line(); |
| } |
| if self.config.include_version { |
| out.new_line_if_not_start(); |
| write!( |
| out, |
| "/* Generated with cbindgen:{} */", |
| crate::bindgen::config::VERSION |
| ); |
| out.new_line(); |
| } |
| if let Some(ref f) = self.config.autogen_warning { |
| out.new_line_if_not_start(); |
| write!(out, "{}", f); |
| out.new_line(); |
| } |
| |
| if self.config.no_includes |
| && self.config.sys_includes().is_empty() |
| && self.config.includes().is_empty() |
| && (self.config.cython.cimports.is_empty() || self.config.language != Language::Cython) |
| && self.config.after_includes.is_none() |
| { |
| return; |
| } |
| |
| out.new_line_if_not_start(); |
| |
| if !self.config.no_includes { |
| match self.config.language { |
| Language::C => { |
| out.write("#include <stdarg.h>"); |
| out.new_line(); |
| out.write("#include <stdbool.h>"); |
| out.new_line(); |
| if self.config.usize_is_size_t { |
| out.write("#include <stddef.h>"); |
| out.new_line(); |
| } |
| out.write("#include <stdint.h>"); |
| out.new_line(); |
| out.write("#include <stdlib.h>"); |
| out.new_line(); |
| } |
| Language::Cxx => { |
| out.write("#include <cstdarg>"); |
| out.new_line(); |
| if self.config.usize_is_size_t { |
| out.write("#include <cstddef>"); |
| out.new_line(); |
| } |
| out.write("#include <cstdint>"); |
| out.new_line(); |
| out.write("#include <cstdlib>"); |
| out.new_line(); |
| out.write("#include <ostream>"); |
| out.new_line(); |
| out.write("#include <new>"); |
| out.new_line(); |
| if self.config.enumeration.cast_assert_name.is_none() |
| && (self.config.enumeration.derive_mut_casts |
| || self.config.enumeration.derive_const_casts) |
| { |
| out.write("#include <cassert>"); |
| out.new_line(); |
| } |
| } |
| Language::Cython => { |
| out.write( |
| "from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t", |
| ); |
| out.new_line(); |
| out.write( |
| "from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t", |
| ); |
| out.new_line(); |
| out.write("cdef extern from *"); |
| out.open_brace(); |
| out.write("ctypedef bint bool"); |
| out.new_line(); |
| out.write("ctypedef struct va_list"); |
| out.new_line(); |
| out.close_brace(false); |
| } |
| } |
| } |
| |
| for include in self.config.sys_includes() { |
| write!(out, "#include <{}>", include); |
| out.new_line(); |
| } |
| |
| for include in self.config.includes() { |
| write!(out, "#include \"{}\"", include); |
| out.new_line(); |
| } |
| |
| if self.config.language == Language::Cython { |
| for (module, names) in &self.config.cython.cimports { |
| write!(out, "from {} cimport {}", module, names.join(", ")); |
| out.new_line(); |
| } |
| } |
| |
| if let Some(ref line) = self.config.after_includes { |
| write!(out, "{}", line); |
| out.new_line(); |
| } |
| } |
| |
| pub fn write<F: Write>(&self, file: F) { |
| if self.noop { |
| return; |
| } |
| |
| let mut out = SourceWriter::new(file, self); |
| |
| self.write_headers(&mut out); |
| |
| self.open_namespaces(&mut out); |
| |
| for constant in &self.constants { |
| if constant.uses_only_primitive_types() { |
| out.new_line_if_not_start(); |
| constant.write(&self.config, &mut out, None); |
| out.new_line(); |
| } |
| } |
| |
| for item in &self.items { |
| if item |
| .deref() |
| .annotations() |
| .bool("no-export") |
| .unwrap_or(false) |
| { |
| continue; |
| } |
| |
| out.new_line_if_not_start(); |
| match *item { |
| ItemContainer::Constant(..) => unreachable!(), |
| ItemContainer::Static(..) => unreachable!(), |
| ItemContainer::Enum(ref x) => x.write(&self.config, &mut out), |
| ItemContainer::Struct(ref x) => x.write(&self.config, &mut out), |
| ItemContainer::Union(ref x) => x.write(&self.config, &mut out), |
| ItemContainer::OpaqueItem(ref x) => x.write(&self.config, &mut out), |
| ItemContainer::Typedef(ref x) => x.write(&self.config, &mut out), |
| } |
| out.new_line(); |
| } |
| |
| for constant in &self.constants { |
| if !constant.uses_only_primitive_types() { |
| out.new_line_if_not_start(); |
| constant.write(&self.config, &mut out, None); |
| out.new_line(); |
| } |
| } |
| |
| if !self.functions.is_empty() || !self.globals.is_empty() { |
| if self.config.cpp_compatible_c() { |
| out.new_line_if_not_start(); |
| out.write("#ifdef __cplusplus"); |
| } |
| |
| if self.config.language == Language::Cxx { |
| if let Some(ref using_namespaces) = self.config.using_namespaces { |
| for namespace in using_namespaces { |
| out.new_line(); |
| write!(out, "using namespace {};", namespace); |
| } |
| out.new_line(); |
| } |
| } |
| |
| if self.config.language == Language::Cxx || self.config.cpp_compatible_c() { |
| out.new_line(); |
| out.write("extern \"C\" {"); |
| out.new_line(); |
| } |
| |
| if self.config.cpp_compatible_c() { |
| out.write("#endif // __cplusplus"); |
| out.new_line(); |
| } |
| |
| for global in &self.globals { |
| out.new_line_if_not_start(); |
| global.write(&self.config, &mut out); |
| out.new_line(); |
| } |
| |
| for function in &self.functions { |
| out.new_line_if_not_start(); |
| function.write(&self.config, &mut out); |
| out.new_line(); |
| } |
| |
| if self.config.cpp_compatible_c() { |
| out.new_line(); |
| out.write("#ifdef __cplusplus"); |
| } |
| |
| if self.config.language == Language::Cxx || self.config.cpp_compatible_c() { |
| out.new_line(); |
| out.write("} // extern \"C\""); |
| out.new_line(); |
| } |
| |
| if self.config.cpp_compatible_c() { |
| out.write("#endif // __cplusplus"); |
| out.new_line(); |
| } |
| } |
| |
| if self.config.language == Language::Cython |
| && self.globals.is_empty() |
| && self.constants.is_empty() |
| && self.items.is_empty() |
| && self.functions.is_empty() |
| { |
| out.write("pass"); |
| } |
| |
| self.close_namespaces(&mut out); |
| |
| if let Some(f) = self.config.include_guard() { |
| out.new_line_if_not_start(); |
| if self.config.language == Language::C { |
| write!(out, "#endif /* {} */", f); |
| } else { |
| write!(out, "#endif // {}", f); |
| } |
| out.new_line(); |
| } |
| if let Some(ref f) = self.config.trailer { |
| out.new_line_if_not_start(); |
| write!(out, "{}", f); |
| if !f.ends_with('\n') { |
| out.new_line(); |
| } |
| } |
| } |
| |
| fn all_namespaces(&self) -> Vec<&str> { |
| if self.config.language != Language::Cxx && !self.config.cpp_compatible_c() { |
| return vec![]; |
| } |
| let mut ret = vec![]; |
| if let Some(ref namespace) = self.config.namespace { |
| ret.push(&**namespace); |
| } |
| if let Some(ref namespaces) = self.config.namespaces { |
| for namespace in namespaces { |
| ret.push(&**namespace); |
| } |
| } |
| ret |
| } |
| |
| fn open_close_namespaces<F: Write>(&self, op: NamespaceOperation, out: &mut SourceWriter<F>) { |
| if self.config.language == Language::Cython { |
| if op == NamespaceOperation::Open { |
| out.new_line(); |
| let header = self.config.cython.header.as_deref().unwrap_or("*"); |
| write!(out, "cdef extern from {}", header); |
| out.open_brace(); |
| } else { |
| out.close_brace(false); |
| } |
| return; |
| } |
| |
| let mut namespaces = self.all_namespaces(); |
| if namespaces.is_empty() { |
| return; |
| } |
| |
| if op == NamespaceOperation::Close { |
| namespaces.reverse(); |
| } |
| |
| if self.config.cpp_compatible_c() { |
| out.new_line_if_not_start(); |
| out.write("#ifdef __cplusplus"); |
| } |
| |
| for namespace in namespaces { |
| out.new_line(); |
| match op { |
| NamespaceOperation::Open => write!(out, "namespace {} {{", namespace), |
| NamespaceOperation::Close => write!(out, "}} // namespace {}", namespace), |
| } |
| } |
| |
| out.new_line(); |
| if self.config.cpp_compatible_c() { |
| out.write("#endif // __cplusplus"); |
| out.new_line(); |
| } |
| } |
| |
| pub(crate) fn open_namespaces<F: Write>(&self, out: &mut SourceWriter<F>) { |
| self.open_close_namespaces(NamespaceOperation::Open, out); |
| } |
| |
| pub(crate) fn close_namespaces<F: Write>(&self, out: &mut SourceWriter<F>) { |
| self.open_close_namespaces(NamespaceOperation::Close, out); |
| } |
| } |