blob: 029cfc66b7759fb93c0cc6849ef6dcfc1c471b92 [file] [log] [blame]
/* 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, Type, Typedef,
};
use crate::bindgen::language_backend::{
CLikeLanguageBackend, CythonLanguageBackend, LanguageBackend,
};
use crate::bindgen::writer::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>>>>,
pub globals: Vec<Static>,
pub constants: Vec<Constant>,
pub items: Vec<ItemContainer>,
pub functions: Vec<Function>,
source_files: Vec<path::PathBuf>,
/// Bindings are generated by a recursive call to cbindgen
/// and shouldn't do anything when written anywhere.
noop: bool,
pub package_version: String,
}
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>,
source_files: Vec<path::PathBuf>,
noop: bool,
package_version: String,
) -> Bindings {
Bindings {
config,
struct_map,
typedef_map,
struct_fileds_memo: Default::default(),
globals,
constants,
items,
functions,
source_files,
noop,
package_version,
}
}
// 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> {
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 generate_depfile<P: AsRef<path::Path>>(&self, header_path: P, depfile_path: P) {
if let Some(dir) = depfile_path.as_ref().parent() {
if !dir.exists() {
std::fs::create_dir_all(dir).unwrap()
}
}
let canon_header_path = header_path.as_ref().canonicalize().unwrap();
let mut canon_source_files: Vec<_> = self
.source_files
.iter()
.chain(self.config.config_path.as_ref())
.map(|p| p.canonicalize().unwrap())
.collect();
// Sorting makes testing easier by ensuring the output is ordered.
canon_source_files.sort_unstable();
// When writing the depfile we must escape whitespace in paths to avoid it being interpreted
// as a seperator.
// It is not clear how to otherwise _correctly_ replace whitespace in a non-unicode
// compliant slice, without knowing the encoding, so we lossy convert such cases,
// to avoid panics.
let mut depfile = File::create(depfile_path).unwrap();
write!(
&mut depfile,
"{}:",
canon_header_path.to_string_lossy().replace(' ', "\\ ")
)
.expect("Writing header name to depfile failed");
canon_source_files.into_iter().for_each(|source_file| {
// Add line-continue and line-break and then indent with 4 spaces.
// This makes the output more human-readable.
depfile.write_all(b" \\\n ").unwrap();
let escaped_path = source_file.to_string_lossy().replace(' ', "\\ ");
depfile.write_all(escaped_path.as_bytes()).unwrap();
});
writeln!(&mut depfile).unwrap();
depfile.flush().unwrap();
}
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<F: Write>(&self, file: F) {
match self.config.language {
Language::Cxx | Language::C => {
self.write_with_backend(file, &mut CLikeLanguageBackend::new(&self.config))
}
Language::Cython => {
self.write_with_backend(file, &mut CythonLanguageBackend::new(&self.config))
}
}
}
fn write_with_backend<F: Write, LB: LanguageBackend>(
&self,
file: F,
language_backend: &mut LB,
) {
if self.noop {
return;
}
let mut out = SourceWriter::new(file, self);
language_backend.write_bindings(&mut out, self);
}
}