From 76eaefc2a1b7c271b5017307a38c6cc257c017c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Thu, 18 Mar 2021 12:02:36 +0400 Subject: [PATCH 1/2] Add a new binary specialized for GObject, gbindgen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most of the cbindgen options are unnecessary or break GObject binding generation. Instead, this binary will tweak the config to do GObject C code generation suitable for gobject-introspection and C programs, using the GObject style. Signed-off-by: Marc-André Lureau --- Cargo.toml | 8 + src/bindgen/bindings.rs | 6 +- src/bindgen/builder.rs | 15 +- src/bindgen/cdecl.rs | 2 +- src/bindgen/config.rs | 5 + src/bindgen/ir/gobject.rs | 478 +++++++++++++++++++++++++ src/bindgen/ir/item.rs | 6 +- src/bindgen/ir/mod.rs | 2 + src/bindgen/language_backend/clike.rs | 8 +- src/bindgen/language_backend/cython.rs | 8 +- src/bindgen/language_backend/mod.rs | 17 +- src/bindgen/library.rs | 57 ++- src/bindgen/parser.rs | 199 +++++++++- src/gmain.rs | 189 ++++++++++ 14 files changed, 983 insertions(+), 17 deletions(-) create mode 100644 src/bindgen/ir/gobject.rs create mode 100644 src/gmain.rs diff --git a/Cargo.toml b/Cargo.toml index 1b684aca..fce475d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.74" exclude = [ "tests/profile.rs", # Test relies in a sub-crate, see https://github.com/rust-lang/cargo/issues/9017 ] +default-run = "cbindgen" [dependencies] clap = { version = "4.3", optional = true } @@ -42,6 +43,7 @@ pretty_assertions = "1.4.0" [features] default = ["clap"] unstable_ir = [] +gobject = [] [[bin]] name = "cbindgen" @@ -49,6 +51,12 @@ path = "src/main.rs" doc = false required-features = ["clap"] +[[bin]] +name = "gbindgen" +path = "src/gmain.rs" +doc = false +required-features = ["clap", "gobject"] + [lib] name = "cbindgen" path = "src/lib.rs" diff --git a/src/bindgen/bindings.rs b/src/bindgen/bindings.rs index 981da85f..8610d26e 100644 --- a/src/bindgen/bindings.rs +++ b/src/bindgen/bindings.rs @@ -13,7 +13,8 @@ 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, + Constant, Function, GObject, ItemContainer, ItemMap, Path as BindgenPath, Static, Struct, Type, + Typedef, }; use crate::bindgen::language_backend::{ CLikeLanguageBackend, CythonLanguageBackend, LanguageBackend, @@ -32,6 +33,7 @@ pub struct Bindings { pub constants: Vec, pub items: Vec, pub functions: Vec, + pub gobjects: Vec, source_files: Vec, /// Bindings are generated by a recursive call to cbindgen /// and shouldn't do anything when written anywhere. @@ -52,6 +54,7 @@ impl Bindings { source_files: Vec, noop: bool, package_version: String, + gobjects: Vec, ) -> Bindings { Bindings { config, @@ -65,6 +68,7 @@ impl Bindings { source_files, noop, package_version, + gobjects, } } diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs index b206ced9..020e978e 100644 --- a/src/bindgen/builder.rs +++ b/src/bindgen/builder.rs @@ -6,7 +6,7 @@ use std::path; use crate::bindgen::bindings::Bindings; use crate::bindgen::cargo::Cargo; -use crate::bindgen::config::{Braces, Config, Language, LineEndingStyle, Profile, Style}; +use crate::bindgen::config::{Braces, Config, Language, LineEndingStyle, Profile, RenameRule, Style}; use crate::bindgen::error::Error; use crate::bindgen::library::Library; use crate::bindgen::parser::{self, Parse}; @@ -155,6 +155,15 @@ impl Builder { self } + #[allow(unused)] + pub fn with_gobject(mut self, gobject: bool) -> Builder { + self.config.gobject = gobject; + self.config.cpp_compat = true; + self.config.enumeration.rename_variants = RenameRule::QualifiedScreamingSnakeCase; + self.config.language = Language::C; + self + } + #[allow(unused)] pub fn with_style(mut self, style: Style) -> Builder { self.config.style = style; @@ -367,13 +376,14 @@ impl Builder { Default::default(), true, String::new(), + Vec::new(), )); } let mut result = Parse::new(); if self.std_types { - result.add_std_types(); + result.add_std_types(&self.config); } for x in &self.srcs { @@ -412,6 +422,7 @@ impl Builder { result.functions, result.source_files, result.package_version, + result.gobjects, ) .generate() } diff --git a/src/bindgen/cdecl.rs b/src/bindgen/cdecl.rs index c1baf983..b3782271 100644 --- a/src/bindgen/cdecl.rs +++ b/src/bindgen/cdecl.rs @@ -199,7 +199,7 @@ impl CDecl { write!(out, "{} ", self.type_qualifers); } - if config.language != Language::Cython { + if config.language != Language::Cython && !config.gobject { if let Some(ref ctype) = self.type_ctype { write!(out, "{} ", ctype.to_str()); } diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index e92b6547..f3602214 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -1032,6 +1032,8 @@ pub struct Config { /// and creating a new InternalConfig struct would require more breaking /// changes to our public API. pub config_path: Option, + /// Enable GObject generation + pub gobject: bool, } impl Default for Config { @@ -1076,6 +1078,7 @@ impl Default for Config { only_target_dependencies: false, cython: CythonConfig::default(), config_path: None, + gobject: false, } } } @@ -1109,6 +1112,7 @@ impl Config { } } + #[allow(unused)] pub fn from_file>(file_name: P) -> Result { let config_text = fs::read_to_string(file_name.as_ref()).map_err(|_| { format!( @@ -1123,6 +1127,7 @@ impl Config { Ok(config) } + #[allow(unused)] pub fn from_root_or_default>(root: P) -> Config { let c = root.as_ref().join("cbindgen.toml"); diff --git a/src/bindgen/ir/gobject.rs b/src/bindgen/ir/gobject.rs new file mode 100644 index 00000000..26d06a7e --- /dev/null +++ b/src/bindgen/ir/gobject.rs @@ -0,0 +1,478 @@ +/* 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::io::Write; + +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use syn::LitStr; + +use crate::bindgen::config::Config; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, Documentation, GenericPath, Item, ItemContainer, Path, Struct, Type, +}; +use crate::bindgen::language_backend::LanguageBackend; +use crate::bindgen::library::Library; +use crate::bindgen::writer::SourceWriter; +use crate::bindgen::Language; + +#[derive(Debug, Clone)] +pub enum GType { + Object { + instance: Option, + class: Option, + parent_type: Type, + }, + Interface { + type_: Type, + }, + Boxed, + Enum { + type_: Type, + }, + Error { + type_: Type, + }, +} + +#[derive(Debug, Clone)] +pub struct GObject { + pub path: Path, + pub name: String, + pub gtype: GType, + pub cfg: Option, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl GObject { + pub fn load_error_domain( + mod_cfg: Option<&Cfg>, + input: &syn::ItemEnum, + list: &syn::MetaList, + ) -> Result { + let mut name = None; + + list.parse_nested_meta(|meta| { + if meta.path.is_ident("name") { + let value = meta.value()?; + let s: LitStr = value.parse()?; + name = Some(s.value()); + } + + Ok(()) + }) + .map_err(|e| format!("Syntax error: {}", e))?; + + let name = name.unwrap().to_upper_camel_case(); + let path = Path::new(input.ident.to_string()); + let type_ = Type::Path(GenericPath::new(path.clone(), vec![])); + + Ok(Self::new( + path, + name, + GType::Error { type_ }, + Cfg::append(mod_cfg, Cfg::load(&input.attrs)), + AnnotationSet::load(&input.attrs)?, + Documentation::load(&input.attrs), + )) + } + + pub fn load_enum( + mod_cfg: Option<&Cfg>, + input: &syn::ItemEnum, + list: &syn::MetaList, + ) -> Result { + let mut type_name = None; + + if list.path.is_ident("enum_type") { + list.parse_nested_meta(|meta| { + if meta.path.is_ident("name") { + let value = meta.value()?; + let s: LitStr = value.parse()?; + type_name = Some(s.value()); + } + + Ok(()) + }) + .map_err(|e| format!("Syntax error: {}", e))?; + } + + if list.path.is_ident("flags") { + list.parse_nested_meta(|meta| { + if meta.path.is_ident("name") { + let value = meta.value()?; + let s: LitStr = value.parse()?; + type_name = Some(s.value()); + } + + Ok(()) + }) + .map_err(|e| format!("Syntax error: {}", e))?; + } + + let type_name = type_name.unwrap(); + let path = Path::new(input.ident.to_string()); + let type_ = Type::Path(GenericPath::new(path.clone(), vec![])); + + Ok(Self::new( + path, + type_name, + GType::Enum { type_ }, + Cfg::append(mod_cfg, Cfg::load(&input.attrs)), + AnnotationSet::load(&input.attrs)?, + Documentation::load(&input.attrs), + )) + } + + pub fn load_boxed( + mod_cfg: Option<&Cfg>, + input: &syn::ItemStruct, + list: &syn::MetaList, + ) -> Result { + let mut type_name = None; + + list.parse_nested_meta(|meta| { + if meta.path.is_ident("name") { + let value = meta.value()?; + let s: LitStr = value.parse()?; + type_name = Some(s.value()); + } + + Ok(()) + }) + .map_err(|e| format!("Syntax error: {}", e))?; + + let path = Path::new(input.ident.to_string()); + + Ok(Self::new( + path, + type_name.unwrap(), + GType::Boxed, + Cfg::append(mod_cfg, Cfg::load(&input.attrs)), + AnnotationSet::load(&input.attrs)?, + Documentation::load(&input.attrs), + )) + } + + pub fn load_interface( + path: &Path, + mod_cfg: Option<&Cfg>, + input: &syn::ItemImpl, + ) -> Result { + let mut name = None; + for item in &input.items { + match item { + syn::ImplItem::Const(const_) => { + let const_name = const_.ident.to_string(); + if const_name == "NAME" { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(ref lit), + .. + }) = const_.expr + { + name = Some(lit.value()); + } + } + } + _ => {} + } + } + + let type_ = Type::load(&*input.self_ty)?.unwrap(); + + Ok(Self::new( + path.clone(), + name.unwrap(), + GType::Interface { type_ }, + Cfg::append(mod_cfg, Cfg::load(&input.attrs)), + AnnotationSet::load(&input.attrs)?, + Documentation::load(&input.attrs), + )) + } + + pub fn load_object( + path: &Path, + mod_cfg: Option<&Cfg>, + input: &syn::ItemImpl, + ) -> Result { + let mut name = None; + let mut class = None; + let mut parent_type = None; + let mut instance = None; + for item in &input.items { + match item { + syn::ImplItem::Type(type_) => { + let name = type_.ident.to_string(); + if name == "Instance" { + instance = Type::load(&type_.ty)?; + } else if name == "Class" { + class = Type::load(&type_.ty)?; + } else if name == "ParentType" { + parent_type = Type::load(&type_.ty)?; + } + } + syn::ImplItem::Const(const_) => { + let const_name = const_.ident.to_string(); + if const_name == "NAME" { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(ref lit), + .. + }) = const_.expr + { + name = Some(lit.value()); + } + } + } + _ => {} + } + } + + let gtype = GType::Object { + instance, + class, + parent_type: parent_type.unwrap(), + }; + + Ok(Self::new( + path.clone(), + name.unwrap(), + gtype, + Cfg::append(mod_cfg, Cfg::load(&input.attrs)), + AnnotationSet::load(&input.attrs)?, + Documentation::load(&input.attrs), + )) + } + + pub fn new( + path: Path, + name: String, + gtype: GType, + cfg: Option, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + Self { + path, + name, + gtype, + cfg, + annotations, + documentation, + } + } +} + +impl Item for GObject { + fn path(&self) -> &Path { + &self.path + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + match &self.gtype { + GType::Object { + parent_type, + instance, + class, + } => { + parent_type.add_dependencies(library, out); + if let Some(instance) = instance { + instance.add_dependencies(library, out); + } + if let Some(class) = class { + class.add_dependencies(library, out); + } + } + GType::Interface { type_ } => { + type_.add_dependencies(library, out); + } + GType::Boxed => {} + GType::Enum { type_ } | GType::Error { type_ } => { + type_.add_dependencies(library, out); + } + } + } + + fn export_name(&self) -> &str { + &self.name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::GObject(self.clone()) + } + + fn rename_for_config(&mut self, _config: &Config) {} + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + match &mut self.gtype { + GType::Object { + parent_type, + instance, + class, + } => { + parent_type.resolve_declaration_types(resolver); + if let Some(instance) = instance { + instance.resolve_declaration_types(resolver); + } + if let Some(class) = class { + class.resolve_declaration_types(resolver); + } + } + GType::Interface { type_ } => { + type_.resolve_declaration_types(resolver); + } + GType::Boxed => {} + GType::Enum { .. } => {} + GType::Error { .. } => {} + } + } + + fn documentation(&self) -> &Documentation { + &self.documentation + } + + fn generic_params(&self) -> &super::GenericParams { + todo!() + } +} + +impl GObject { + pub fn write( + &self, + config: &Config, + language_backend: &mut LB, + out: &mut SourceWriter, + _associated_to_struct: Option<&Struct>, + ) { + if config.language == Language::Cxx || config.language == Language::C { + self.write_clike(out, language_backend); + } + // There is no Cython bindings for GObject, so, bail out. + unimplemented!() + } + pub fn write_clike( + &self, + out: &mut SourceWriter, + language_backend: &mut LB, + ) { + let (prefix, name) = match self.gtype { + GType::Object { .. } | GType::Boxed | GType::Enum { .. } | GType::Error { .. } => { + let prefix = self.name.strip_suffix(self.path.name()).unwrap(); + let name = self.name.strip_prefix(prefix).unwrap(); + (prefix, name) + } + GType::Interface { .. } => { + let path_name = self.path.name().strip_suffix("Interface").unwrap(); + let prefix = self.name.strip_suffix(path_name).unwrap(); + let name = self.name.strip_prefix(prefix).unwrap(); + (prefix, name) + } + }; + let name_up = name.to_shouty_snake_case(); + let prefix_up = prefix.to_shouty_snake_case(); + let snake = self.name.to_snake_case(); + let type_up = format!("{}_TYPE_{}", prefix_up, name_up); + + language_backend.write_documentation(out, self.documentation()); + + if matches!(self.gtype, GType::Error { .. }) { + write!( + out, + "#define {}_{} ({}_quark())", + prefix_up, name_up, snake + ); + out.new_line(); + } else { + write!( + out, + "#define {} ({}_get_type())", + type_up, snake + ); + out.new_line(); + } + + match self.gtype { + GType::Object { .. } | GType::Interface { .. } => { + write!( + out, + "#define {}_{}(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),{},{}))", + prefix_up, name_up, type_up, self.name + ); + out.new_line(); + write!( + out, + "#define {}_IS_{}(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),{}))", + prefix_up, name_up, type_up + ); + out.new_line(); + } + _ => {} + } + + match self.gtype { + GType::Object { .. } => { + write!( + out, + "#define {}_{}_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),{},{}Class))", + prefix_up, name_up, type_up, self.name + ); + out.new_line(); + write!( + out, + "#define {}_IS_{}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),{}))", + prefix_up, name_up, type_up + ); + out.new_line(); + write!( + out, + "#define {}_{}_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),{},{}Class))", + prefix_up, name_up, type_up, self.name + ); + out.new_line(); + write!( + out, + "G_DEFINE_AUTOPTR_CLEANUP_FUNC({}, g_object_unref)", + self.name + ); + } + GType::Interface { .. } => { + write!( + out, + "#define {}_{}_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),{},{}Interface))", + prefix_up, + name_up, + type_up, + self.name + ); + } + GType::Boxed => { + write!( + out, + "static inline void {}_free({}* ptr) {{ g_boxed_free({}, ptr); }} G_DEFINE_AUTOPTR_CLEANUP_FUNC({},{}_free);", + snake, + self.name, + type_up, + self.name, + snake + ) + } + GType::Enum { .. } => {} + GType::Error { .. } => {} + } + out.new_line(); + } +} diff --git a/src/bindgen/ir/item.rs b/src/bindgen/ir/item.rs index 03e1c153..2bbad011 100644 --- a/src/bindgen/ir/item.rs +++ b/src/bindgen/ir/item.rs @@ -9,8 +9,8 @@ use crate::bindgen::config::Config; use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; use crate::bindgen::dependencies::Dependencies; use crate::bindgen::ir::{ - AnnotationSet, Cfg, Constant, Documentation, Enum, GenericArgument, GenericParams, OpaqueItem, - Path, Static, Struct, Typedef, Union, + AnnotationSet, Cfg, Constant, Documentation, Enum, GObject, GenericArgument, GenericParams, + OpaqueItem, Path, Static, Struct, Typedef, Union, }; use crate::bindgen::library::Library; use crate::bindgen::monomorph::Monomorphs; @@ -64,6 +64,7 @@ pub enum ItemContainer { Union(Union), Enum(Enum), Typedef(Typedef), + GObject(GObject), } impl ItemContainer { @@ -76,6 +77,7 @@ impl ItemContainer { ItemContainer::Union(ref x) => x, ItemContainer::Enum(ref x) => x, ItemContainer::Typedef(ref x) => x, + ItemContainer::GObject(ref x) => x, } } } diff --git a/src/bindgen/ir/mod.rs b/src/bindgen/ir/mod.rs index 3566f0d7..04bb29f6 100644 --- a/src/bindgen/ir/mod.rs +++ b/src/bindgen/ir/mod.rs @@ -11,6 +11,7 @@ pub mod field; pub mod function; pub mod generic_path; pub mod global; +pub mod gobject; pub mod item; pub mod opaque; pub mod path; @@ -29,6 +30,7 @@ pub use self::field::*; pub use self::function::*; pub use self::generic_path::*; pub use self::global::*; +pub use self::gobject::*; pub use self::item::*; pub use self::opaque::*; pub use self::path::*; diff --git a/src/bindgen/language_backend/clike.rs b/src/bindgen/language_backend/clike.rs index dba5a10a..f6c50f2a 100644 --- a/src/bindgen/language_backend/clike.rs +++ b/src/bindgen/language_backend/clike.rs @@ -1,7 +1,7 @@ use crate::bindgen::ir::{ to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant, - Field, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type, - Typedef, Union, + Field, GObject, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, + ToCondition, Type, Typedef, Union, }; use crate::bindgen::language_backend::LanguageBackend; use crate::bindgen::rename::IdentifierType; @@ -1009,4 +1009,8 @@ impl LanguageBackend for CLikeLanguageBackend<'_> { } } } + + fn write_gobject(&mut self, out: &mut SourceWriter, t: &GObject) { + t.write_clike(out, self); + } } diff --git a/src/bindgen/language_backend/cython.rs b/src/bindgen/language_backend/cython.rs index b6b2ba37..f5c84a73 100644 --- a/src/bindgen/language_backend/cython.rs +++ b/src/bindgen/language_backend/cython.rs @@ -1,6 +1,7 @@ use crate::bindgen::ir::{ to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant, - Field, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type, Typedef, Union, + Field, GObject, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type, + Typedef, Union, }; use crate::bindgen::language_backend::LanguageBackend; use crate::bindgen::writer::{ListType, SourceWriter}; @@ -418,4 +419,9 @@ impl LanguageBackend for CythonLanguageBackend<'_> { out.write("pass"); } } + + fn write_gobject(&mut self, _out: &mut SourceWriter, _t: &GObject) { + // There is no Cython bindings for GObject, so, bail out. + unimplemented!() + } } diff --git a/src/bindgen/language_backend/mod.rs b/src/bindgen/language_backend/mod.rs index 0e8f99f4..4f18530c 100644 --- a/src/bindgen/language_backend/mod.rs +++ b/src/bindgen/language_backend/mod.rs @@ -1,10 +1,10 @@ use crate::bindgen::ir::{ - cfg::ConditionWrite, DeprecatedNoteKind, Documentation, Enum, Function, ItemContainer, Literal, - OpaqueItem, Static, Struct, ToCondition, Type, Typedef, Union, + cfg::ConditionWrite, DeprecatedNoteKind, Documentation, Enum, Function, GObject, ItemContainer, + Literal, OpaqueItem, Static, Struct, ToCondition, Type, Typedef, Union, }; use crate::bindgen::writer::SourceWriter; +use crate::bindgen::Config; use crate::bindgen::{cdecl, Bindings, Layout}; -use crate::Config; use std::io::Write; @@ -25,6 +25,7 @@ pub trait LanguageBackend: Sized { fn write_opaque_item(&mut self, out: &mut SourceWriter, o: &OpaqueItem); fn write_type_def(&mut self, out: &mut SourceWriter, t: &Typedef); fn write_static(&mut self, out: &mut SourceWriter, s: &Static); + fn write_gobject(&mut self, out: &mut SourceWriter, t: &GObject); fn write_function( &mut self, @@ -122,6 +123,7 @@ pub trait LanguageBackend: Sized { self.write_primitive_constants(out, b); self.write_items(out, b); self.write_non_primitive_constants(out, b); + self.write_gobjects(out, b); self.write_globals(out, b); self.write_functions(out, b); self.close_namespaces(out); @@ -129,6 +131,14 @@ pub trait LanguageBackend: Sized { self.write_trailer(out, b); } + fn write_gobjects(&mut self, out: &mut SourceWriter, b: &Bindings) { + for gobject in &b.gobjects { + out.new_line_if_not_start(); + gobject.write(&b.config, self, out, None); + out.new_line(); + } + } + fn write_primitive_constants(&mut self, out: &mut SourceWriter, b: &Bindings) { for constant in &b.constants { if constant.uses_only_primitive_types() { @@ -172,6 +182,7 @@ pub trait LanguageBackend: Sized { ItemContainer::Union(ref x) => self.write_union(out, x), ItemContainer::OpaqueItem(ref x) => self.write_opaque_item(out, x), ItemContainer::Typedef(ref x) => self.write_type_def(out, x), + ItemContainer::GObject(ref x) => self.write_gobject(out, x), } out.new_line(); } diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs index 671f5d9b..7b9564d0 100644 --- a/src/bindgen/library.rs +++ b/src/bindgen/library.rs @@ -10,7 +10,7 @@ use crate::bindgen::config::{Config, Language, SortKey}; use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; use crate::bindgen::dependencies::Dependencies; use crate::bindgen::error::Error; -use crate::bindgen::ir::{Constant, Enum, Function, Item, ItemContainer, ItemMap}; +use crate::bindgen::ir::{Constant, Enum, Function, GObject, GType, Item, ItemContainer, ItemMap}; use crate::bindgen::ir::{OpaqueItem, Path, Static, Struct, Typedef, Union}; use crate::bindgen::monomorph::Monomorphs; use crate::bindgen::ItemType; @@ -28,6 +28,7 @@ pub struct Library { functions: Vec, source_files: Vec, package_version: String, + gobjects: ItemMap, } impl Library { @@ -44,6 +45,7 @@ impl Library { functions: Vec, source_files: Vec, package_version: String, + gobjects: ItemMap, ) -> Library { Library { config, @@ -57,12 +59,14 @@ impl Library { functions, source_files, package_version, + gobjects, } } pub fn generate(mut self) -> Result { self.transfer_annotations(); self.simplify_standard_types(); + self.gobject_config(); match self.config.function.sort_by.unwrap_or(self.config.sort_by) { SortKey::Name => self.functions.sort_by(|x, y| x.path.cmp(&y.path)), @@ -90,6 +94,9 @@ impl Library { self.constants.for_all_items(|constant| { constant.add_dependencies(&self, &mut dependencies); }); + self.gobjects.for_all_items(|gobject| { + gobject.add_dependencies(&self, &mut dependencies); + }); for name in &self.config.export.include { let path = Path::new(name.clone()); if let Some(items) = self.get_items(&path) { @@ -145,6 +152,7 @@ impl Library { self.source_files, false, self.package_version, + self.gobjects.to_vec(), )) } @@ -357,11 +365,58 @@ impl Library { self.globals .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + self.gobjects + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + for item in &mut self.functions { item.resolve_declaration_types(&resolver); } } + fn gobject_config(&mut self) { + let mut needs_gobject = false; + + for x in self.gobjects.to_vec() { + if !matches!(x.gtype, GType::Boxed) { + self.typedefs.filter(|o| o.name() == x.export_name()); + self.opaque_items.filter(|o| o.name() == x.export_name()); + } + match x.gtype { + GType::Object { class, .. } => { + self.config + .export + .rename + .insert(x.path.name().into(), x.name.clone()); + self.config.export.rename.insert( + class.unwrap().get_root_path().unwrap().to_string(), + format!("{}Class", x.name), + ); + } + GType::Interface { .. } => { + self.config + .export + .rename + .insert(x.path.name().into(), format!("{}Interface", x.name)); + self.config.export.rename.insert( + x.path.name().strip_suffix("Interface").unwrap().to_string(), + x.name, + ); + } + GType::Boxed | GType::Enum { .. } | GType::Error { .. } => { + self.config + .export + .rename + .insert(x.path.name().into(), x.name.clone()); + } + } + needs_gobject = true; + } + + if needs_gobject { + self.config.sys_includes.push("glib-object.h".into()) + } + } + fn simplify_standard_types(&mut self) { let config = &self.config; diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs index 26c25e42..4181733b 100644 --- a/src/bindgen/parser.rs +++ b/src/bindgen/parser.rs @@ -15,8 +15,8 @@ use crate::bindgen::cargo::{Cargo, PackageRef}; use crate::bindgen::config::{Config, ParseConfig}; use crate::bindgen::error::Error; use crate::bindgen::ir::{ - AnnotationSet, AnnotationValue, Cfg, Constant, Documentation, Enum, Function, GenericParam, - GenericParams, ItemMap, OpaqueItem, Path, Static, Struct, Type, Typedef, Union, + AnnotationSet, AnnotationValue, Cfg, Constant, Documentation, Enum, Function, GObject, GType, + GenericParam, GenericParams, ItemMap, OpaqueItem, Path, Static, Struct, Type, Typedef, Union, }; use crate::bindgen::utilities::{SynAbiHelpers, SynAttributeHelpers, SynItemHelpers}; @@ -420,6 +420,7 @@ pub struct Parse { pub functions: Vec, pub source_files: Vec, pub package_version: String, + pub gobjects: ItemMap, } impl Parse { @@ -435,10 +436,11 @@ impl Parse { functions: Vec::new(), source_files: Vec::new(), package_version: String::new(), + gobjects: ItemMap::default(), } } - pub fn add_std_types(&mut self) { + pub fn add_std_types(&mut self, _config: &Config) { let mut add_opaque = |path: &str, generic_params: Vec<&str>| { let path = Path::new(path); let generic_params: Vec<_> = generic_params @@ -471,6 +473,12 @@ impl Parse { add_opaque("VecDeque", vec!["T"]); add_opaque("ManuallyDrop", vec!["T"]); add_opaque("MaybeUninit", vec!["T"]); + + // if config.gobject { + // add_opaque("GType", vec![]); + // add_opaque("GObject", vec![""]); + // add_opaque("GObjectClass", vec![""]); + // } } pub fn extend_with(&mut self, other: &Parse) { @@ -481,6 +489,7 @@ impl Parse { self.unions.extend_with(&other.unions); self.opaque_items.extend_with(&other.opaque_items); self.typedefs.extend_with(&other.typedefs); + self.gobjects.extend_with(&other.gobjects); self.functions.extend_from_slice(&other.functions); self.source_files.extend_from_slice(&other.source_files); self.package_version.clone_from(&other.package_version); @@ -522,12 +531,18 @@ impl Parse { } syn::Item::Struct(ref item) => { self.load_syn_struct(config, crate_name, mod_cfg, item); + if config.gobject { + self.load_syn_gboxed(mod_cfg, item); + } } syn::Item::Union(ref item) => { self.load_syn_union(config, crate_name, mod_cfg, item); } syn::Item::Enum(ref item) => { self.load_syn_enum(config, crate_name, mod_cfg, item); + if config.gobject { + self.load_syn_genum(mod_cfg, item); + } } syn::Item::Type(ref item) => { self.load_syn_ty(crate_name, mod_cfg, item); @@ -540,7 +555,6 @@ impl Parse { if has_assoc_const { impls_with_assoc_consts.push(item_impl); } - if let syn::Type::Path(ref path) = *item_impl.self_ty { if let Some(type_name) = path.path.get_ident() { for method in item_impl.items.iter().filter_map(|item| match item { @@ -558,6 +572,18 @@ impl Parse { method, ) } + if config.gobject { + if let Some((_, trait_path, _)) = &item_impl.trait_ { + self.load_syn_gobject( + config, + crate_name, + mod_cfg, + trait_path, + Path::new(type_name.to_string()), + item_impl, + ); + } + } } } } @@ -993,6 +1019,171 @@ impl Parse { } } + fn load_syn_genum(&mut self, mod_cfg: Option<&Cfg>, item: &syn::ItemEnum) { + for attr in item.attrs.iter() { + if let syn::Meta::List(ref list) = attr.meta { + if list.path.is_ident("enum_type") || list.path.is_ident("flags") { + match GObject::load_enum(mod_cfg, item, list) { + Ok(gi) => { + info!("Take {}.", item.ident); + self.gobjects.try_insert(gi); + } + Err(msg) => { + error!("Cannot use GEnum/GFlags {} ({}).", item.ident, msg); + } + } + } + if list.path.is_ident("error_domain") { + match GObject::load_error_domain(mod_cfg, item, list) { + Ok(gi) => { + info!("Take {}.", item.ident); + self.gobjects.try_insert(gi); + } + Err(msg) => { + error!("Cannot use GErrorDomain {} ({}).", item.ident, msg); + } + } + } + } + } + } + + fn load_syn_gboxed(&mut self, mod_cfg: Option<&Cfg>, item: &syn::ItemStruct) { + for attr in item.attrs.iter() { + if let syn::Meta::List(ref list) = attr.meta { + if list.path.is_ident("boxed_type") { + match GObject::load_boxed(mod_cfg, item, list) { + Ok(gi) => { + info!("Take {}.", item.ident); + self.gobjects.try_insert(gi); + } + Err(msg) => { + error!("Cannot use GBoxed {} ({}).", item.ident, msg); + } + } + } + } + } + } + + fn load_syn_gobject( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + trait_path: &syn::Path, + self_type: Path, + item: &syn::ItemImpl, + ) { + if trait_path.is_ident("ObjectSubclass") { + self.load_syn_gobject_subclass(config, crate_name, mod_cfg, self_type, item); + } else if trait_path.is_ident("ObjectInterface") { + self.load_syn_gobject_interface(mod_cfg, self_type, item); + } + } + + fn load_syn_gobject_interface( + &mut self, + mod_cfg: Option<&Cfg>, + self_type: Path, + item: &syn::ItemImpl, + ) { + match GObject::load_interface(&self_type, mod_cfg, item) { + Ok(gi) => { + info!("Take {}.", self_type); + self.gobjects.try_insert(gi); + } + Err(msg) => { + error!("Cannot use GInterface {} ({}).", self_type, msg); + } + } + } + + fn load_syn_gobject_subclass( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + self_type: Path, + item: &syn::ItemImpl, + ) { + match GObject::load_object(&self_type, mod_cfg, item) { + Ok(mut gobject) => { + let (class, instance, parent_type) = match &mut gobject.gtype { + GType::Object { + class, + instance, + parent_type, + } => (class, instance, parent_type), + _ => panic!(), + }; + if instance.is_none() { + let ident = + syn::Ident::new(&gobject.path.name(), proc_macro2::Span::call_site()); + let parent_type = parent_type.get_root_path().unwrap().to_string(); + let parent = if parent_type == "Object" { + quote!(glib::gobject_ffi::GObject) + } else { + let parent = syn::Ident::new(&parent_type, proc_macro2::Span::call_site()); + parse_quote! { #parent } + }; + let struct_ = parse_quote! { + #[repr(C)] + pub struct #ident { + pub parent: #parent, + } + }; + self.load_syn_struct(config, crate_name, mod_cfg, &struct_); + *instance = Some( + Type::load(&syn::Type::Path(syn::TypePath { + path: syn::Path::from(ident), + qself: None, + })) + .unwrap() + .unwrap(), + ); + } + if class.is_none() { + let class_ident = syn::Ident::new( + &format!("{}Class", gobject.path), + proc_macro2::Span::call_site(), + ); + let parent_type = parent_type.get_root_path().unwrap().to_string(); + let parent_class = if parent_type == "Object" { + quote!(glib::gobject_ffi::GObjectClass) + } else { + let parent_class = syn::Ident::new( + &format!("{}Class", parent_type), + proc_macro2::Span::call_site(), + ); + parse_quote! { #parent_class } + }; + let struct_ = parse_quote! { + #[repr(C)] + pub struct #class_ident { + pub parent_class: #parent_class, + } + }; + self.load_syn_struct(config, crate_name, mod_cfg, &struct_); + *class = Some( + Type::load(&syn::Type::Path(syn::TypePath { + path: syn::Path::from(class_ident), + qself: None, + })) + .unwrap() + .unwrap(), + ); + } + + info!("Take {}.", self_type); + self.gobjects.try_insert(gobject); + } + Err(msg) => { + error!("Cannot use GObject {} ({}).", self_type, msg); + } + } + } + fn load_builtin_macro( &mut self, config: &Config, diff --git a/src/gmain.rs b/src/gmain.rs new file mode 100644 index 00000000..90f0fd90 --- /dev/null +++ b/src/gmain.rs @@ -0,0 +1,189 @@ +/* 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::env; +use std::io; +use std::path::{Path, PathBuf}; + +extern crate clap; +#[macro_use] +extern crate log; +extern crate proc_macro2; +#[macro_use] +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate quote; +#[macro_use] +extern crate syn; +extern crate toml; + +use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; + +mod bindgen; +mod logging; + +use crate::bindgen::{Bindings, Builder, Cargo, Error}; + +fn load_bindings(input: &Path, matches: &ArgMatches) -> Result { + // We have to load a whole crate, so we use cargo to gather metadata + let lib = Cargo::load( + input, + matches.get_one::("lockfile").map(|s| s.as_path()), + matches.get_one::("crate").map(|s| s.as_str()), + true, + matches.get_flag("clean"), + matches.get_flag("only-target-dependencies"), + matches.get_one::("metadata").map(|p| p.as_path()), + )?; + + Builder::new().with_gobject(true).with_cargo(lib).generate() +} + +fn main() { + let matches = Command::new("gbindgen") + .version(bindgen::VERSION) + .about("Generate GObject C bindings for a glib/gtk-rs library") + .arg( + Arg::new("v") + .short('v') + .action(ArgAction::Count) + .help("Enable verbose logging"), + ) + .arg( + Arg::new("verify") + .long("verify") + .action(ArgAction::SetTrue) + .help("Generate bindings and compare it to the existing bindings file and error if they are different"), + ) + .arg( + Arg::new("only-target-dependencies") + .long("only-target-dependencies") + .action(ArgAction::SetTrue) + .help("Only fetch dependencies needed by the target platform. \ + The target platform defaults to the host platform; set TARGET to override.") + ) + .arg( + Arg::new("clean") + .long("clean") + .action(ArgAction::SetTrue) + .help( + "Whether to use a new temporary directory for expanding macros. \ + Affects performance, but might be required in certain build processes.") + .required(false) + ) + .arg( + Arg::new("INPUT") + .help( + "A crate directory or source file to generate bindings for. \ + In general this is the folder where the Cargo.toml file of \ + source Rust library resides.") + .required(false) + .value_parser(value_parser!(PathBuf)) + .index(1), + ) + .arg( + Arg::new("crate") + .long("crate") + .value_name("CRATE_NAME") + .help( + "If generating bindings for a crate, \ + the specific crate to generate bindings for", + ) + .required(false), + ) + .arg( + Arg::new("out") + .short('o') + .long("output") + .value_name("PATH") + .help("The file to output the bindings to") + .value_parser(value_parser!(PathBuf)) + .required(false), + ) + .arg( + Arg::new("lockfile") + .long("lockfile") + .value_name("PATH") + .help( + "Specify the path to the Cargo.lock file explicitly. If this \ + is not specified, the Cargo.lock file is searched for in the \ + same folder as the Cargo.toml file. This option is useful for \ + projects that use workspaces.") + .value_parser(value_parser!(PathBuf)) + .required(false), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .value_name("PATH") + .help( + "Specify the path to the output of a `cargo metadata` \ + command that allows to get dependency information. \ + This is useful because cargo metadata may be the longest \ + part of cbindgen runtime, and you may want to share it \ + across cbindgen invocations. By default cbindgen will run \ + `cargo metadata --all-features --format-version 1 \ + --manifest-path " + ) + .value_parser(value_parser!(PathBuf)) + .required(false), + ) + .arg( + Arg::new("quiet") + .short('q') + .long("quiet") + .action(ArgAction::SetTrue) + .help("Report errors only (overrides verbosity options).") + .required(false), + ) + .get_matches(); + + // Initialize logging + if matches.get_flag("quiet") { + logging::ErrorLogger::init().unwrap(); + } else { + match matches.get_count("v") { + 0 => logging::WarnLogger::init().unwrap(), + 1 => logging::InfoLogger::init().unwrap(), + _ => logging::TraceLogger::init().unwrap(), + } + } + + // Find the input directory + let input: PathBuf = matches + .get_one("INPUT") + .cloned() + .unwrap_or_else(|| env::current_dir().unwrap()); + + let bindings = match load_bindings(&input, &matches) { + Ok(bindings) => bindings, + Err(msg) => { + error!("{}", msg); + error!("Couldn't generate bindings for {}.", input.display()); + std::process::exit(1); + } + }; + + // Write the bindings file + match matches.get_one::("out") { + Some(file) => { + let changed = bindings.write_to_file(file); + + if matches.get_flag("verify") && changed { + error!("Bindings changed: {}", file.display()); + std::process::exit(2); + } + if let Some(depfile) = matches.get_one("depfile") { + bindings.generate_depfile(file, depfile); + } + if let Some(symfile) = matches.get_one::("symfile") { + bindings.generate_symfile(symfile); + } + } + _ => { + bindings.write(io::stdout()); + } + } +} From a47f9e7b4d9cd65916d649d125e3aecda68493fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Mon, 24 Apr 2023 17:21:13 +0400 Subject: [PATCH 2/2] Handle optional ParentType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marc-André Lureau --- src/bindgen/ir/gobject.rs | 12 ++++++++---- src/bindgen/parser.rs | 7 +++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/bindgen/ir/gobject.rs b/src/bindgen/ir/gobject.rs index 26d06a7e..26d9e2e7 100644 --- a/src/bindgen/ir/gobject.rs +++ b/src/bindgen/ir/gobject.rs @@ -23,7 +23,7 @@ pub enum GType { Object { instance: Option, class: Option, - parent_type: Type, + parent_type: Option, }, Interface { type_: Type, @@ -233,7 +233,7 @@ impl GObject { let gtype = GType::Object { instance, class, - parent_type: parent_type.unwrap(), + parent_type: parent_type, }; Ok(Self::new( @@ -277,7 +277,9 @@ impl Item for GObject { instance, class, } => { - parent_type.add_dependencies(library, out); + if let Some(parent_type) = parent_type { + parent_type.add_dependencies(library, out); + } if let Some(instance) = instance { instance.add_dependencies(library, out); } @@ -324,7 +326,9 @@ impl Item for GObject { instance, class, } => { - parent_type.resolve_declaration_types(resolver); + if let Some(parent_type) = parent_type { + parent_type.resolve_declaration_types(resolver); + } if let Some(instance) = instance { instance.resolve_declaration_types(resolver); } diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs index 4181733b..7b0047ca 100644 --- a/src/bindgen/parser.rs +++ b/src/bindgen/parser.rs @@ -1117,10 +1117,14 @@ impl Parse { } => (class, instance, parent_type), _ => panic!(), }; + let parent_type = parent_type + .as_ref() + .and_then(|p| p.get_root_path()) + .map(|p| p.to_string()) + .unwrap_or("Object".to_string()); if instance.is_none() { let ident = syn::Ident::new(&gobject.path.name(), proc_macro2::Span::call_site()); - let parent_type = parent_type.get_root_path().unwrap().to_string(); let parent = if parent_type == "Object" { quote!(glib::gobject_ffi::GObject) } else { @@ -1148,7 +1152,6 @@ impl Parse { &format!("{}Class", gobject.path), proc_macro2::Span::call_site(), ); - let parent_type = parent_type.get_root_path().unwrap().to_string(); let parent_class = if parent_type == "Object" { quote!(glib::gobject_ffi::GObjectClass) } else {