| /* 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::fmt; |
| use std::io::Write; |
| |
| use crate::bindgen::cargo::cargo_metadata::Dependency; |
| use crate::bindgen::config::{Config, Language}; |
| use crate::bindgen::writer::SourceWriter; |
| |
| #[derive(PartialEq, Eq)] |
| enum DefineKey<'a> { |
| Boolean(&'a str), |
| Named(&'a str, &'a str), |
| } |
| |
| impl<'a> DefineKey<'a> { |
| fn load(key: &str) -> DefineKey { |
| // TODO: dirty parser |
| if !key.contains('=') { |
| return DefineKey::Boolean(key); |
| } |
| |
| let mut splits = key.trim().split('='); |
| |
| let name = match splits.next() { |
| Some(n) => n.trim(), |
| None => return DefineKey::Boolean(key), |
| }; |
| |
| let value = match splits.next() { |
| Some(v) => v.trim(), |
| None => return DefineKey::Boolean(key), |
| }; |
| |
| if splits.next().is_some() { |
| return DefineKey::Boolean(key); |
| } |
| |
| DefineKey::Named(name, value) |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| pub enum Cfg { |
| Boolean(String), |
| Named(String, String), |
| Any(Vec<Cfg>), |
| All(Vec<Cfg>), |
| Not(Box<Cfg>), |
| } |
| |
| impl fmt::Display for Cfg { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match self { |
| Cfg::Boolean(key) => write!(f, "{}", key), |
| Cfg::Named(key, value) => write!(f, "{} = {:?}", key, value), |
| Cfg::Any(cfgs) => { |
| write!(f, "any(")?; |
| for (index, cfg) in cfgs.iter().enumerate() { |
| if index > 0 { |
| write!(f, ", ")?; |
| } |
| write!(f, "{}", cfg)?; |
| } |
| write!(f, ")") |
| } |
| Cfg::All(cfgs) => { |
| write!(f, "all(")?; |
| for (index, cfg) in cfgs.iter().enumerate() { |
| if index > 0 { |
| write!(f, ", ")?; |
| } |
| write!(f, "{}", cfg)?; |
| } |
| write!(f, ")") |
| } |
| Cfg::Not(cfg) => write!(f, "not({})", cfg), |
| } |
| } |
| } |
| |
| impl Cfg { |
| pub fn join(cfgs: &[Cfg]) -> Option<Cfg> { |
| if cfgs.is_empty() { |
| None |
| } else { |
| Some(Cfg::All(cfgs.to_owned())) |
| } |
| } |
| |
| pub fn append(parent: Option<&Cfg>, child: Option<Cfg>) -> Option<Cfg> { |
| match (parent, child) { |
| (None, None) => None, |
| (None, Some(child)) => Some(child), |
| (Some(parent), None) => Some(parent.clone()), |
| (Some(parent), Some(child)) => Some(Cfg::All(vec![parent.clone(), child])), |
| } |
| } |
| |
| pub fn load(attrs: &[syn::Attribute]) -> Option<Cfg> { |
| let mut configs = Vec::new(); |
| |
| for attr in attrs { |
| if let Ok(syn::Meta::List(syn::MetaList { path, nested, .. })) = attr.parse_meta() { |
| if !path.is_ident("cfg") || nested.len() != 1 { |
| continue; |
| } |
| |
| if let Some(config) = Cfg::load_single(nested.first().unwrap()) { |
| configs.push(config); |
| } |
| } |
| } |
| |
| match configs.len() { |
| 0 => None, |
| 1 => Some(configs.pop().unwrap()), |
| _ => Some(Cfg::All(configs)), |
| } |
| } |
| |
| pub fn load_metadata(dependency: &Dependency) -> Option<Cfg> { |
| let target = dependency.target.as_ref()?; |
| match syn::parse_str::<syn::Meta>(target) { |
| Ok(target) => { |
| // Parsing succeeded using the #[cfg] syntax |
| if let syn::Meta::List(syn::MetaList { path, nested, .. }) = target { |
| if !path.is_ident("cfg") || nested.len() != 1 { |
| return None; |
| } |
| Cfg::load_single(nested.first().unwrap()) |
| } else { |
| None |
| } |
| } |
| Err(_) => { |
| // Parsing failed using #[cfg], this may be a literal target |
| // name |
| Cfg::load_single(&syn::NestedMeta::Lit(syn::Lit::Str(syn::LitStr::new( |
| target, |
| proc_macro2::Span::call_site(), |
| )))) |
| } |
| } |
| } |
| |
| fn load_single(item: &syn::NestedMeta) -> Option<Cfg> { |
| Some(match *item { |
| syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => { |
| Cfg::Boolean(format!("{}", path.segments.first().unwrap().ident)) |
| } |
| syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { |
| ref path, |
| lit: syn::Lit::Str(ref value), |
| .. |
| })) => Cfg::Named( |
| format!("{}", path.segments.first().unwrap().ident), |
| value.value(), |
| ), |
| syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { |
| ref path, |
| ref nested, |
| .. |
| })) => { |
| if path.is_ident("any") { |
| Cfg::Any(Cfg::load_list(nested.iter())?) |
| } else if path.is_ident("all") { |
| Cfg::All(Cfg::load_list(nested.iter())?) |
| } else if path.is_ident("not") { |
| if nested.len() != 1 { |
| return None; |
| } |
| |
| Cfg::Not(Box::new(Cfg::load_single(&nested[0])?)) |
| } else { |
| return None; |
| } |
| } |
| _ => return None, |
| }) |
| } |
| |
| fn load_list<'a, I: Iterator<Item = &'a syn::NestedMeta>>(attrs: I) -> Option<Vec<Cfg>> { |
| let mut configs = Vec::new(); |
| |
| for attr in attrs { |
| configs.push(Cfg::load_single(attr)?); |
| } |
| |
| if configs.is_empty() { |
| None |
| } else { |
| Some(configs) |
| } |
| } |
| } |
| |
| pub trait ToCondition: Sized { |
| fn to_condition(&self, config: &Config) -> Option<Condition>; |
| } |
| |
| impl ToCondition for Option<Cfg> { |
| fn to_condition(&self, config: &Config) -> Option<Condition> { |
| self.as_ref()?.to_condition(config) |
| } |
| } |
| |
| impl ToCondition for Cfg { |
| fn to_condition(&self, config: &Config) -> Option<Condition> { |
| match *self { |
| Cfg::Boolean(ref cfg_name) => { |
| let define = config |
| .defines |
| .iter() |
| .find(|(key, ..)| DefineKey::Boolean(cfg_name) == DefineKey::load(key)); |
| if let Some((_, define)) = define { |
| Some(Condition::Define(define.to_owned())) |
| } else { |
| warn!( |
| "Missing `[defines]` entry for `{}` in cbindgen config.", |
| self, |
| ); |
| None |
| } |
| } |
| Cfg::Named(ref cfg_name, ref cfg_value) => { |
| let define = config.defines.iter().find(|(key, ..)| { |
| DefineKey::Named(cfg_name, cfg_value) == DefineKey::load(key) |
| }); |
| if let Some((_, define)) = define { |
| Some(Condition::Define(define.to_owned())) |
| } else { |
| warn!( |
| "Missing `[defines]` entry for `{}` in cbindgen config.", |
| self, |
| ); |
| None |
| } |
| } |
| Cfg::Any(ref children) => { |
| let conditions: Vec<_> = children |
| .iter() |
| .filter_map(|x| x.to_condition(config)) |
| .collect(); |
| match conditions.len() { |
| 0 => None, |
| 1 => conditions.into_iter().next(), |
| _ => Some(Condition::Any(conditions)), |
| } |
| } |
| Cfg::All(ref children) => { |
| let cfgs: Vec<_> = children |
| .iter() |
| .filter_map(|x| x.to_condition(config)) |
| .collect(); |
| match cfgs.len() { |
| 0 => None, |
| 1 => cfgs.into_iter().next(), |
| _ => Some(Condition::All(cfgs)), |
| } |
| } |
| Cfg::Not(ref child) => child |
| .to_condition(config) |
| .map(|cfg| Condition::Not(Box::new(cfg))), |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| pub enum Condition { |
| Define(String), |
| Any(Vec<Condition>), |
| All(Vec<Condition>), |
| Not(Box<Condition>), |
| } |
| |
| impl Condition { |
| fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { |
| match *self { |
| Condition::Define(ref define) => { |
| if config.language == Language::Cython { |
| write!(out, "{}", define); |
| } else { |
| out.write("defined("); |
| write!(out, "{}", define); |
| out.write(")"); |
| } |
| } |
| Condition::Any(ref conditions) => { |
| out.write("("); |
| for (i, condition) in conditions.iter().enumerate() { |
| if i != 0 { |
| out.write(if config.language == Language::Cython { |
| " or " |
| } else { |
| " || " |
| }); |
| } |
| condition.write(config, out); |
| } |
| out.write(")"); |
| } |
| Condition::All(ref conditions) => { |
| out.write("("); |
| for (i, condition) in conditions.iter().enumerate() { |
| if i != 0 { |
| out.write(if config.language == Language::Cython { |
| " and " |
| } else { |
| " && " |
| }); |
| } |
| condition.write(config, out); |
| } |
| out.write(")"); |
| } |
| Condition::Not(ref condition) => { |
| out.write(if config.language == Language::Cython { |
| "not " |
| } else { |
| "!" |
| }); |
| condition.write(config, out); |
| } |
| } |
| } |
| } |
| |
| pub trait ConditionWrite { |
| fn write_before<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>); |
| fn write_after<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>); |
| } |
| |
| impl ConditionWrite for Option<Condition> { |
| fn write_before<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { |
| if let Some(ref cfg) = *self { |
| if config.language == Language::Cython { |
| out.write("IF "); |
| cfg.write(config, out); |
| out.open_brace(); |
| } else { |
| out.push_set_spaces(0); |
| out.write("#if "); |
| cfg.write(config, out); |
| out.pop_set_spaces(); |
| out.new_line(); |
| } |
| } |
| } |
| |
| fn write_after<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { |
| if self.is_some() { |
| if config.language == Language::Cython { |
| out.close_brace(false); |
| } else { |
| out.new_line(); |
| out.push_set_spaces(0); |
| out.write("#endif"); |
| out.pop_set_spaces(); |
| } |
| } |
| } |
| } |