diff --git a/Cargo.toml b/Cargo.toml index ab305a4dc..1ba616ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,7 @@ git2 = { version = "0.6", default-features = false } [profile.release] codegen-units = 4 + +[features] +default = [] +use_unions = [] diff --git a/README.md b/README.md index bdb1df62e..30ea7be06 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,26 @@ # GIR -The `GIR` is used to generate both sys level and Rust-user API level. +The `GIR` is used to generate both the sys level crate and a safe API crate to use the sys level (FFI) crate. ## Generate FFI (sys level) -You first need to get the Gir file corresponding to the project you want to bind. You can get them from [here](https://github.com/gtk-rs/gir-files) or directly on [ubuntu website](http://packages.ubuntu.com/) (for example: http://packages.ubuntu.com/zesty/amd64/libgtk-3-dev). +Using `gir` requires both a `*.toml` and a `*.gir` for generation of the bindings. -Then you need to write a TOML file (let's call it YourSysGirFile.toml) that you'll pass to gir (you can take a look to [gtk-rs/sys/gir-gtk.toml](https://github.com/gtk-rs/sys/blob/master/conf/gir-gtk.toml) to see an example). +The `*.gir` you need will correspond to the project you want to generate bindings for. You can get them from [here](https://github.com/gtk-rs/gir-files) or directly on [ubuntu website](http://packages.ubuntu.com/) (for example: http://packages.ubuntu.com/zesty/amd64/libgtk-3-dev). -### TOML file +The `*.toml` is what is used to pass various settings and options to gir for use when generating the bindings - you will likely need to write one to suit your needs, for an example you can take a look to [gtk-rs/sys/gir-gtk.toml](https://github.com/gtk-rs/sys/blob/master/conf/gir-gtk.toml). -In FFI mode, Gir generates as much as it can. So in this mode, the TOML file is mostly used to ignore some objects. To do so, you need to add its fullname to an `ignore` array. Example: +## `gir` Modes + +There are two main modes of generation for `gir`; *FFI* and *API*. + +The *FFI* mode is what creates the low-level FFI bindings from the supplied `*.gir` file - these are essentially direct calls in to the related C library and are typically unsafe. The resulting crate is typically appended with `-sys`. + +The *API* mode generates another crate for a layer on top of these unsafe (*sys*) bindings which makes them safe for use in general Rust. + +### The FFI mode TOML config + +In FFI (`-m sys`) mode, Gir generates as much as it can. So in this mode, the TOML file is mostly used to ignore some objects. To do so, you need to add its fullname to an `ignore` array. Example: ```toml ignore = ["Gtk.Widget", "Gtk.Window"] @@ -29,7 +39,7 @@ status = "generate" is_windows_utf8 = true ``` -### Generation +### Generation in FFI mode When you're ready, let's generate the FFI part: @@ -39,13 +49,11 @@ cargo run --release -- -c YourSysGirFile.toml -d ../gir-files -m sys -o the-outp The generated files will be placed in `the-output-directory-sys`. You now have the sys part of your binding! -## Generate the Rust-user API level - -You'll now have to write another GIR file (take a look to [gtk/Gir.toml](https://github.com/gtk-rs/gtk/blob/master/Gir.toml) for an example). +## The API mode TOML config -### TOML file +This mode requires you to write another TOML file. [gtk/Gir.toml](https://github.com/gtk-rs/gtk/blob/master/Gir.toml) is a good example. -At the opposite of the FFI mode, this one only generates the specified objects. You can either add the object's fullname to the `generate` array or add it to the `manual` array (but in this case, it won't be generated, just used in other functions/methods instead of generating an "ignored" argument). Example: +This mode generates only the specified objects. You can either add the object's fullname to the `generate` array or add it to the `manual` array (but in this case, it won't be generated, just used in other functions/methods instead of generating an "ignored" argument). Example: ```toml generate = ["Gtk.Widget", "Gtk.Window"] @@ -54,7 +62,7 @@ manual = ["Gtk.Button"] So in here, both `GtkWidget` and `GtkWindow` will be fully generated and functions/methods using `GtkButton` will be uncommented. To generate code for all global functions, add `Gtk.*` to the `generate` array. -Sometimes Gir understands the object definition incorrectly or the `.gir` file contains incomplete or wrong definition, to fix it, you can use the full object configuration: +Sometimes Gir understands the object definition incorrectly or the `.gir` file contains an incomplete or wrong definition, to fix it, you can use the full object configuration: ```toml [[object]] @@ -124,7 +132,7 @@ cfg_condition = "mycond" ignore = true ``` -Since there is no child properties in `.gir` files, it needs to be added for classes manually: +Since there are no child properties in `.gir` files, it needs to be added for classes manually: ```toml [[object]] @@ -195,7 +203,7 @@ mutating the object exists). `send+sync` is valid if the type can be sent to different threads and all API allows simultaneous calls from different threads due to internal locking via e.g. a mutex. -### Generation +### Generation in API mode To generate the Rust-user API level, The command is very similar to the previous one. It's better to not put this output in the same directory as where the FFI files are. Just run: @@ -204,3 +212,22 @@ cargo run --release -- -c YourGirFile.toml -d ../gir-files -o the-output-directo ``` Now it should be done. Just go to the output directory (so `the-output-directory/auto` in our case) and try to build using `cargo build`. Don't forget to update your dependencies in both projects: nothing much to do in the FFI/sys one but the Rust-user API level will need to have a dependency over the FFI/sys one. + +## Nightly Rust Only Features + +### Unions + +By default union generation is disabled except for some special cases due to unions not yet being a stable feature. However if you are using *nightly* rust, then you can enable union generation using `cargo run --release --features "use_unions"`. + +Keep in mind that to access union members, you are required to use `unsafe` blocks, for example; + +``` +union myUnion { + test : u32 +} + +let testUnion = myUnion { test : 42 }; +unsafe { println!("{}", myUnion.test }; +``` + +This is required as the rust compiler can not guarantee the safety of the union, or that the member being addressed exsits. The union RFC is [here](https://github.com/tbu-/rust-rfcs/blob/master/text/1444-union.md) and the tracking issue is [here](https://github.com/rust-lang/rust/issues/32836). diff --git a/src/analysis/ffi_type.rs b/src/analysis/ffi_type.rs index 4eb317bf5..6bf84a893 100644 --- a/src/analysis/ffi_type.rs +++ b/src/analysis/ffi_type.rs @@ -93,6 +93,7 @@ fn ffi_inner(env: &Env, tid: TypeId, inner: &str) -> Result { }; Ok(inner.into()) } + Type::Union(..) | Type::Record(..) | Type::Alias(..) | Type::Function(..) => { diff --git a/src/codegen/sys/functions.rs b/src/codegen/sys/functions.rs index 9f7e01f6e..1ac7a731c 100644 --- a/src/codegen/sys/functions.rs +++ b/src/codegen/sys/functions.rs @@ -7,6 +7,7 @@ use library; use nameutil; use super::ffi_type::*; use traits::*; +use regex::Regex; //used as glib:get-type in GLib-2.0.gir const INTERN: &'static str = "intern"; @@ -22,17 +23,22 @@ pub fn generate_records_funcs( ) -> Result<()> { let intern_str = INTERN.to_string(); for record in records { - let name = format!("{}.{}", env.config.library_name, record.name); - let obj = env.config.objects.get(&name).unwrap_or(&DEFAULT_OBJ); - let glib_get_type = record.glib_get_type.as_ref().unwrap_or(&intern_str); - try!(generate_object_funcs( - w, - env, - obj, - &record.c_type, - glib_get_type, - &record.functions, - )); + // Nested structs tend to generate a duplicate function name, + // this catches the nested struct and ignores function gen + let s_regex = Regex::new(r"^\w+_s\d+$").unwrap(); + if !s_regex.is_match(&record.name) { + let name = format!("{}.{}", env.config.library_name, record.name); + let obj = env.config.objects.get(&name).unwrap_or(&DEFAULT_OBJ); + let glib_get_type = record.glib_get_type.as_ref().unwrap_or(&intern_str); + try!(generate_object_funcs( + w, + env, + obj, + &record.c_type, + glib_get_type, + &record.functions, + )); + } } Ok(()) diff --git a/src/codegen/sys/lib_.rs b/src/codegen/sys/lib_.rs index 045ba5a3a..cd2a2b336 100644 --- a/src/codegen/sys/lib_.rs +++ b/src/codegen/sys/lib_.rs @@ -3,6 +3,7 @@ use std::io::{Result, Write}; use case::CaseExt; use analysis::c_type::rustify_pointers; + use codegen::general::{self, version_condition}; use config::ExternalLibrary; use env::Env; @@ -317,24 +318,56 @@ fn generate_unions(w: &mut Write, env: &Env, items: &[&Union]) -> Result<()> { for item in items { if let Some(ref c_type) = item.c_type { - // TODO: GLib/GObject special cases until we have proper union support in Rust - if env.config.library_name == "GLib" && c_type == "GMutex" { - // Two c_uint or a pointer => 64 bits on all platforms currently - // supported by GLib but the alignment is different on 32 bit - // platforms (32 bit vs. 64 bits on 64 bit platforms) - try!(writeln!( - w, - "#[cfg(target_pointer_width = \"32\")]\n#[repr(C)]\npub struct {0}([u32; 2]);\n\ - #[cfg(target_pointer_width = \"64\")]\n#[repr(C)]\npub struct {0}(*mut c_void);", - c_type - )); - } else { - try!(writeln!(w, "pub type {} = c_void; // union", c_type)); + #[cfg(feature = "use_unions")] + { + let (lines, commented) = generate_fields(env, &item.name, &item.fields); + + let comment = if commented { "//" } else { "" }; + if lines.is_empty() { + try!(writeln!( + w, + "{comment}#[repr(C)]\n{comment}pub union {name}(c_void);\n", + comment = comment, + name = c_type + )); + } else { + try!(writeln!( + w, + "{comment}#[repr(C)]\n{comment}pub union {name} {{", + comment = comment, + name = c_type + )); + + for line in lines { + try!(writeln!(w, "{}{}", comment, line)); + } + try!(writeln!(w, "{}}}\n", comment)); + } + } + #[cfg(not(feature = "use_unions"))] + { + // TODO: GLib/GObject special cases until we have proper union support in Rust + if env.config.library_name == "GLib" && c_type == "GMutex" { + // Two c_uint or a pointer => 64 bits on all platforms currently + // supported by GLib but the alignment is different on 32 bit + // platforms (32 bit vs. 64 bits on 64 bit platforms) + try!(writeln!( + w, + "#[cfg(target_pointer_width = \"32\")]\n#[repr(C)]\npub struct {0}([u32; 2]);\n\ + #[cfg(target_pointer_width = \"64\")]\n#[repr(C)]\npub struct {0}(*mut c_void);", + c_type + )); + } else { + try!(writeln!(w, "pub type {} = c_void; // union", c_type)); + } } } } - if !items.is_empty() { - try!(writeln!(w, "")); + #[cfg(not(feature = "use_unions"))] + { + if !items.is_empty() { + try!(writeln!(w, "")); + } } Ok(()) @@ -439,7 +472,7 @@ fn generate_records(w: &mut Write, env: &Env, records: &[&Record]) -> Result<()> Ok(()) } -// TODO: GLib/GObject special cases until we have proper union support in Rust +// TODO: GLib/GObject special cases unless nightly unions are enabled fn is_union_special_case(c_type: &Option) -> bool { if let Some(c_type) = c_type.as_ref() { c_type.as_str() == "GMutex" @@ -477,36 +510,42 @@ fn generate_fields(env: &Env, struct_name: &str, fields: &[Field]) -> (Vec() { - for union_field in &union_.fields { - if union_field.name.contains("reserved") || - union_field.name.contains("padding") - { - if let Some(ref c_type) = union_field.c_type { - let name = mangle_keywords(&*union_field.name); - let c_type = ffi_type(env, union_field.typ, c_type); - if c_type.is_err() { - commented = true; + #[cfg(not(feature = "use_unions"))] + { + if is_union && !truncated { + if let Some(union_) = env.library.type_(field.typ).maybe_ref_as::() { + for union_field in &union_.fields { + if union_field.name.contains("reserved") || + union_field.name.contains("padding") + { + if let Some(ref c_type) = union_field.c_type { + let name = mangle_keywords(&*union_field.name); + let c_type = ffi_type(env, union_field.typ, c_type); + if c_type.is_err() { + commented = true; + } + lines.push(format!("\tpub {}: {},", name, c_type.into_string())); + continue 'fields; } - lines.push(format!("\tpub {}: {},", name, c_type.into_string())); - continue 'fields; } } } } } - if !is_gweakref && !truncated && !is_ptr && (is_union || is_bits) && - !is_union_special_case(&field.c_type) - { - warn!( - "Field `{}::{}` not expressible in Rust, truncated", - struct_name, - field.name - ); - lines.push("\t_truncated_record_marker: c_void,".to_owned()); - truncated = true; + if !cfg!(feature = "use_unions") || (is_bits && !truncated) { + if !is_gweakref && !truncated && !is_ptr && + (is_union || is_bits) && + !is_union_special_case(&field.c_type) + { + warn!( + "Field `{}::{}` not expressible in Rust, truncated", + struct_name, + field.name + ); + lines.push("\t_truncated_record_marker: c_void,".to_owned()); + truncated = true; + } } if truncated { @@ -537,7 +576,7 @@ fn generate_fields(env: &Env, struct_name: &str, fields: &[Field]) -> (Vec Result<()> { + #[cfg(feature = "use_unions")] + let u = "#![feature(untagged_unions)]"; + #[cfg(not(feature = "use_unions"))] + let u = ""; + let v = vec![ - "", + u, "#![allow(non_camel_case_types, non_upper_case_globals)]", "", "extern crate libc;", diff --git a/src/library.rs b/src/library.rs index 1e1bc7126..b7c02f74d 100644 --- a/src/library.rs +++ b/src/library.rs @@ -537,6 +537,7 @@ impl Type { library.add_type(INTERNAL_NAMESPACE, &format!("fn<#{:?}>", param_tids), typ) } + #[cfg(not(feature = "use_unions"))] pub fn union(library: &mut Library, fields: Vec) -> TypeId { let field_tids: Vec = fields.iter().map(|f| f.typ).collect(); let typ = Type::Union(Union { @@ -545,6 +546,38 @@ impl Type { }); library.add_type(INTERNAL_NAMESPACE, &format!("#{:?}", field_tids), typ) } + + #[cfg(feature = "use_unions")] + pub fn union(library: &mut Library, u: Union, ns_id: u16) -> TypeId { + let fields = u.fields; + let field_tids: Vec = fields.iter().map(|f| f.typ).collect(); + let typ = Type::Union(Union { + name : u.name, + c_type : u.c_type, + fields : fields, + functions : u.functions, + doc : u.doc, + }); + library.add_type(ns_id, &format!("#{:?}", field_tids), typ) + } + + #[cfg(feature = "use_unions")] + pub fn record(library: &mut Library, r: Record, ns_id: u16) -> TypeId { + let fields = r.fields; + let field_tids: Vec = fields.iter().map(|f| f.typ).collect(); + let typ = Type::Record(Record { + name : r.name, + c_type : r.c_type, + glib_get_type : r.glib_get_type, + fields : fields, + functions : r.functions, + version : r.version, + deprecated_version: r.deprecated_version, + doc : r.doc, + doc_deprecated : r.doc_deprecated, + }); + library.add_type(ns_id, &format!("#{:?}", field_tids), typ) + } } macro_rules! impl_maybe_ref { diff --git a/src/parser.rs b/src/parser.rs index d54a55c5c..77a56bab1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -152,7 +152,7 @@ impl Library { try!(self.read_class(parser, ns_id, &attributes)); } "record" => { - try!(self.read_record(parser, ns_id, &attributes)); + try!(self.read_record_start(parser, ns_id, &attributes)); } "union" => { try!(self.read_named_union(parser, ns_id, &attributes)); @@ -265,13 +265,30 @@ impl Library { "virtual-method" => try!(ignore_element(parser)), "doc" => doc = try!(read_text(parser)), "union" => { - let (union_fields, _, doc) = try!(self.read_union(parser, ns_id)); - let typ = Type::union(self, union_fields); - fields.push(Field { - typ: typ, - doc: doc, - ..Field::default() - }); + #[cfg(feature = "use_unions")] + { + use self::Type::*; + if let Union(u) = + try!(self.read_union_unsafe(parser, ns_id, attrs)) { + let u_doc = u.doc.clone(); + let type_id = Type::union(self, u, ns_id); + fields.push(Field { + typ: type_id, + doc: u_doc, + ..Field::default() + }); + }; + } + #[cfg(not(feature = "use_unions"))] + { + let (union_fields, _, doc) = try!(self.read_union(parser, ns_id)); + let typ = Type::union(self, union_fields); + fields.push(Field { + typ: typ, + doc: doc, + ..Field::default() + }); + } } x => bail!(mk_error!(format!("Unexpected element <{}>", x), parser)), } @@ -302,18 +319,32 @@ impl Library { Ok(()) } - fn read_record(&mut self, parser: &mut Reader, ns_id: u16, attrs: &Attributes) -> Result<()> { + fn read_record_start(&mut self, parser: &mut Reader, + ns_id: u16, attrs: &Attributes) -> Result<()> { + + if let Some(typ) = try!(self.read_record(parser, ns_id, attrs)) { + let name = typ.get_name().clone(); + self.add_type(ns_id, &name, typ); + } + Ok(()) + } + + fn read_record(&mut self, parser: &mut Reader, ns_id: u16, attrs: &Attributes) + -> Result> { let mut name = try!( attrs .by_name("name") - .ok_or_else(|| mk_error!("Missing record name", parser)) + .ok_or_else(|| mk_error!("Missing union name", parser)) ); let mut c_type = try!( attrs .by_name("type") .ok_or_else(|| mk_error!("Missing c:type attribute", parser)) ); - let get_type = attrs.by_name("get-type"); + let get_type = match attrs.by_name("get-type") { + Some(s) => Some(s.to_string()), + None => None + }; let version = try!(self.parse_version(parser, ns_id, attrs.by_name("version"))); let deprecated_version = try!(self.parse_version( parser, @@ -324,6 +355,8 @@ impl Library { let mut fns = Vec::new(); let mut doc = None; let mut doc_deprecated = None; + #[cfg(feature = "use_unions")] + let mut union_count = 1; loop { let event = try!(parser.next()); match event { @@ -341,16 +374,60 @@ impl Library { )) } "union" => { - let (union_fields, _, doc) = try!(self.read_union(parser, ns_id)); - let typ = Type::union(self, union_fields); - fields.push(Field { - typ: typ, - doc: doc, - ..Field::default() - }); + #[cfg(feature = "use_unions")] + { + use self::Type::*; + if let Union(mut u) = + try!(self.read_union_unsafe(parser, ns_id, attrs)) { + // A nested union->struct->union typically has no c_type + // so we iterate over fields to find it. These fields are + // within the nested union->struct if found + let mut nested = true; + for f in &mut u.fields { + if f.c_type.is_none() || + c_type == u.c_type.as_ref().unwrap() { + nested = true; + u.name = format!("{}_u{}",c_type,union_count); + u.c_type = Some(format!("{}_u{}",c_type,union_count)); + } + } + let ctype = u.c_type.clone(); + let u_doc = u.doc.clone(); + let type_id = Type::union(self, u, ns_id); + if nested { + fields.push(Field { + name: format!("u{}", union_count), + typ: type_id, + doc: u_doc, + c_type: ctype, + ..Field::default() + }); + } else { + fields.push(Field { + typ: type_id, + doc: u_doc, + c_type: ctype, + ..Field::default() + }); + } + union_count += 1; + }; + } + #[cfg(not(feature = "use_unions"))] + { + let (union_fields, _, doc) = try!(self.read_union(parser, ns_id)); + let typ = Type::union(self, union_fields); + fields.push(Field { + typ: typ, + doc: doc, + ..Field::default() + }); + } } "field" => { - fields.push(try!(self.read_field(parser, ns_id, &attributes))); + if let Ok(f) = self.read_field(parser, ns_id, &attributes) { + fields.push(f); + } } "doc" => doc = try!(read_text(parser)), "doc-deprecated" => doc_deprecated = try!(read_text(parser)), @@ -363,7 +440,7 @@ impl Library { } if attrs.by_name("is-gtype-struct").is_some() { - return Ok(()); + return Ok(None); } if name == "Atom" && self.namespace(ns_id).name == "Gdk" { @@ -380,7 +457,7 @@ impl Library { c_identifier: "GdkAtom".into(), typ: tid, target_c_type: "GdkAtom_*".into(), - doc: doc.clone(), + doc: None, //TODO: temporary }), ); } @@ -396,39 +473,119 @@ impl Library { doc: doc, doc_deprecated: doc_deprecated, }); - self.add_type(ns_id, name, typ); - Ok(()) + + Ok(Some(typ)) } - fn read_named_union( - &mut self, - parser: &mut Reader, - ns_id: u16, - attrs: &Attributes, - ) -> Result<()> { + fn read_named_union(&mut self, parser: &mut Reader, + ns_id: u16, attrs: &Attributes) -> Result<()> { let name = try!( attrs .by_name("name") .ok_or_else(|| mk_error!("Missing union name", parser)) ); let c_type = attrs.by_name("type"); - let (fields, fns, doc) = try!(self.read_union(parser, ns_id)); + #[cfg(feature = "use_unions")] + { + if let Type::Union(u) = try!(self.read_union_unsafe(parser, ns_id, attrs)) { + let typ = Type::Union(Union { + name: name.into(), + c_type: c_type.map(|s| s.into()), + fields: u.fields, + functions: u.functions, + doc: u.doc, + }); + self.add_type(ns_id, name, typ); + } + } + #[cfg(not(feature = "use_unions"))] + { + let (fields, fns, doc) = try!(self.read_union(parser, ns_id)); + let typ = Type::Union(Union { + name: name.into(), + c_type: c_type.map(|s| s.into()), + fields: fields, + functions: fns, + doc: doc, + }); + self.add_type(ns_id, name, typ); + } + Ok(()) + } + + #[cfg(feature = "use_unions")] + fn read_union_unsafe(&mut self, parser: &mut Reader, ns_id: u16, attrs: &Attributes) + -> Result { + let name = try!( + attrs + .by_name("name") + .ok_or_else(|| mk_error!("Missing record name", parser)) + ); + let c_type = attrs.by_name("type").unwrap_or(""); + + let mut fields = Vec::new(); + let mut fns = Vec::new(); + let mut doc = None; + let mut struct_count = 1; + loop { + let event = try!(parser.next()); + match event { + StartElement { name, attributes, .. } => { + match name.local_name.as_ref() { + "field" => { + let f = try!(self.read_field(parser, ns_id, &attributes)); + fields.push(f); + } + kind @ "constructor" | + kind @ "function" | + kind @ "method" => { + try!(self.read_function_to_vec( + parser, + ns_id, + kind, + &attributes, + &mut fns, + )) + } + "record" => { + use self::Type::*; + if let Some(Record(mut r)) = + try!(self.read_record(parser, ns_id, attrs)) { + r.name = format!("{}_s{}",c_type, struct_count); + r.c_type = format!("{}_s{}",c_type, struct_count); + let r_doc = r.doc.clone(); + let type_id = Type::record(self, r, ns_id); + fields.push(Field { + name: format!("s{}", struct_count), + typ: type_id, + doc: r_doc, + ..Field::default() + }); + struct_count += 1; + }; + }, + "doc" => doc = try!(read_text(parser)), + x => bail!(mk_error!(format!("Unexpected element <{}>", x), parser)), + } + } + EndElement { .. } => break, + _ => xml_next!(event, parser), + } + } + let typ = Type::Union(Union { name: name.into(), - c_type: c_type.map(|s| s.into()), + c_type: Some(c_type.into()), fields: fields, functions: fns, doc: doc, }); - self.add_type(ns_id, name, typ); - Ok(()) + Ok(typ) } - fn read_union( - &mut self, - parser: &mut Reader, - ns_id: u16, - ) -> Result<(Vec, Vec, Option)> { + #[cfg(not(feature = "use_unions"))] + fn read_union(&mut self, parser: &mut Reader, ns_id: u16) + -> Result<(Vec, Vec, Option)> { let mut fields = Vec::new(); let mut fns = Vec::new(); let mut doc = None;