Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ git2 = { version = "0.6", default-features = false }

[profile.release]
codegen-units = 4

[features]
default = []
use_unions = []
55 changes: 41 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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:

Expand All @@ -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"]
Expand All @@ -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]]
Expand Down Expand Up @@ -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]]
Expand Down Expand Up @@ -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:

Expand All @@ -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).
1 change: 1 addition & 0 deletions src/analysis/ffi_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..) => {
Expand Down
28 changes: 17 additions & 11 deletions src/codegen/sys/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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(())
Expand Down
119 changes: 79 additions & 40 deletions src/codegen/sys/lib_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 6 additions & 1 deletion src/codegen/sys/statics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;",
Expand Down
33 changes: 33 additions & 0 deletions src/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Loading