Permalink
Browse files

Merge pull request #402 from Luke-Nukem/master

Full enablement of Unions using nightly Rust
  • Loading branch information...
EPashkin committed Jul 7, 2017
2 parents 9e3f4cc + 78d6f9b commit 2192ae7a423664422b5ed1f43c7cdeb69168579d
Showing with 376 additions and 104 deletions.
  1. +4 −0 Cargo.toml
  2. +41 −14 README.md
  3. +1 −0 src/analysis/ffi_type.rs
  4. +17 −11 src/codegen/sys/functions.rs
  5. +79 −40 src/codegen/sys/lib_.rs
  6. +6 −1 src/codegen/sys/statics.rs
  7. +33 −0 src/library.rs
  8. +195 −38 src/parser.rs
View
@@ -29,3 +29,7 @@ git2 = { version = "0.6", default-features = false }
[profile.release]
codegen-units = 4
[features]
default = []
use_unions = []
View
@@ -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).
View
@@ -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(..) => {
@@ -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(())
View
@@ -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<String>) -> 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<Strin
// TODO: Special case for padding unions like used in GStreamer, see e.g.
// the padding in GstControlBinding
if is_union && !truncated {
if let Some(union_) = env.library.type_(field.typ).maybe_ref_as::<Union>() {
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::<Union>() {
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<Strin
c_type = Ok("[u64; 2]".to_owned());
}
lines.push(format!("\tpub {}: {},", name, c_type.into_string()));
} else if is_gweakref {
} else if is_gweakref && !cfg!(feature = "use_unions") {
// union containing a single pointer
lines.push("\tpub priv_: gpointer,".to_owned());
} else {
@@ -3,8 +3,13 @@ use std::io::{Result, Write};
use super::super::general::write_vec;
pub fn begin(w: &mut Write) -> 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;",
View
@@ -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<Field>) -> TypeId {
let field_tids: Vec<TypeId> = 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<TypeId> = 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<TypeId> = 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 {
Oops, something went wrong.

0 comments on commit 2192ae7

Please sign in to comment.