diff --git a/grust/generators/sys_cargo.py b/grust/generators/sys_cargo.py new file mode 100644 index 0000000..2c56a6a --- /dev/null +++ b/grust/generators/sys_cargo.py @@ -0,0 +1,62 @@ +# coding: UTF-8 +# grust-gen - Rust binding generator for GObject introspection +# +# Copyright (C) 2016 Jonas Ã…dahl +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +import os +from ..mapping import sys_crate_name +from ..output import FileOutput + +class SysCargoWriter(object): + """Generator for -sys Cargo build files.""" + + def __init__(self, mapper, transformer, tmpl_lookup, path): + self._mapper = mapper + self._transformer = transformer + self._cargo_template = tmpl_lookup.get_template('cargo/cargo.tmpl') + self._build_rs_template = tmpl_lookup.get_template('cargo/build.rs.tmpl') + self._cargo_file = os.path.join(path, 'Cargo.toml') + self._build_rs_file = os.path.join(path, 'build.rs') + + def _write_cargo(self, output): + pkgname = sys_crate_name(self._transformer.namespace) + version = self._transformer.namespace.version + result = self._cargo_template.render_unicode(mapper=self._mapper, + pkgname=pkgname, + version=version) + output.write(result) + + def _write_build_rs(self, output): + pkgconfig_packages = self._transformer.namespace.exported_packages + result = self._build_rs_template.render_unicode(packages=pkgconfig_packages) + output.write(result) + + def write(self): + cargo_output = FileOutput(self._cargo_file, encoding='utf-8') + with cargo_output as output: + try: + self._write_cargo(output) + except Exception: + raise SystemExit(1) + + build_rs_output = FileOutput(self._build_rs_file, encoding='utf-8') + with build_rs_output as output: + try: + self._write_build_rs(output) + except Exception: + raise SystemExit(1) diff --git a/grust/generators/sys_crate.py b/grust/generators/sys_crate.py index 9ddb8e1..667ca41 100644 --- a/grust/generators/sys_crate.py +++ b/grust/generators/sys_crate.py @@ -29,7 +29,7 @@ def __init__(self, template, options, gir_filename=None): - self._mapper = RawMapper(transformer) + self._mapper = RawMapper(transformer, options) self._template = template self._options = options if gir_filename: @@ -55,3 +55,6 @@ def _prepare_walk(self, node, chain): context=node) return False return True + + def get_mapper(self): + return self._mapper diff --git a/grust/genmain.py b/grust/genmain.py index 744baa8..1ecbea7 100644 --- a/grust/genmain.py +++ b/grust/genmain.py @@ -29,8 +29,11 @@ from .giscanner import message from .giscanner import utils from .generators.sys_crate import SysCrateWriter +from .generators.sys_cargo import SysCargoWriter from .output import FileOutput, DirectOutput from . import __version__ as version +from .mapping import sys_crate_name +from .mapping import RawMapper def output_file(name): if name == '-': @@ -53,6 +56,14 @@ def _create_arg_parser(): help='add directory to include search path') parser.add_argument('-t', '--template', help='name of the custom template file') + parser.add_argument('-c', '--cargo', dest='gen_cargo', action='store_true', + help='generate a Cargo build description') + parser.add_argument('--exclude', action='append', + dest='excluded_crates', metavar='DEP', + help='exclude mapping functions relating to this crate') + parser.add_argument('--include', action='append', + dest='included_crates', metavar='DEP', + help='include mapping functions relating to this crate') return parser def generator_main(): @@ -112,4 +123,16 @@ def generator_main(): if error_count > 0: raise SystemExit(2) + if opts.gen_cargo: + if opts.template is not None: + sys.exit('can only generate cargo build description without custom template') + if not isinstance(output, FileOutput): + sys.exit('can only generate cargo build description with file output') + cargo_path = os.path.dirname(output.filename()) + cargo_gen = SysCargoWriter(mapper=gen.get_mapper(), + transformer=transformer, + tmpl_lookup=tmpl_lookup, + path=cargo_path) + + cargo_gen.write() return 0 diff --git a/grust/mapping.py b/grust/mapping.py index 9172d41..9ed1213 100644 --- a/grust/mapping.py +++ b/grust/mapping.py @@ -476,6 +476,7 @@ def _unwrap_call_signature_ctype(type_container): if ctype is None: raise MappingError('parameter {}: C type attribute is missing'.format(type_container.argname)) if (isinstance(type_container, ast.Parameter) + and not isinstance(type_container.type, ast.Array) and type_container.direction in (ast.PARAM_DIRECTION_OUT, ast.PARAM_DIRECTION_INOUT) and not type_container.caller_allocates): @@ -514,11 +515,21 @@ class RawMapper(object): names resolved in the Rust code generated using the mapping methods. """ - def __init__(self, transformer): + def __init__(self, transformer, options): self.transformer = transformer self.crate = self._create_crate(transformer.namespace) self._extern_crates = {} # namespace name -> Crate self._crate_libc = None + self._excluded_crates = options.excluded_crates + self._included_crates = options.included_crates + + def is_excluded(self, crate): + if not self._included_crates and not self._excluded_crates: + return False + if not self._included_crates: + return crate.local_name in self._excluded_crates + else: + return not crate.local_name in self._included_crates def _create_crate(self, namespace): # This is a method, to allow per-namespace configuration @@ -872,6 +883,66 @@ def map_field_type(self, field): .format(field.name, typenode)) return self._map_type(field.type, nullable=True) + def _struct_field_type_is_valid(self, field_type): + if field_type is None: + return False + if isinstance(field_type, ast.Union): + return False + if isinstance(field_type, ast.Array): + return self._struct_field_type_is_valid(field_type.element_type) + if isinstance(field_type, ast.Record): + return self.struct_is_valid(field_type) + if isinstance(field_type, ast.Type) and field_type.target_giname: + if not self.node_is_mappable(field_type): + return False + if field_type.ctype is None or not field_type.ctype.endswith('*'): + crate,name = self._lookup_giname(field_type.target_giname) + return self._struct_field_type_is_valid(crate.namespace.names[name]) + return True + + def struct_is_valid(self, node): + if isinstance(node, ast.Union): + return False + for field in node.fields: + if field.bits is not None: + return False + if not self._struct_field_type_is_valid(field.type): + return False + return True + + def node_is_mappable(self, node): + if isinstance(node, ast.Type) and node == ast.TYPE_VALIST: + return False + if isinstance(node, ast.Type) and node.target_giname: + crate,name = self._lookup_giname(node.target_giname) + if self.is_excluded(crate): + return False + if isinstance(node, ast.TypeContainer): + return self.node_is_mappable(node.type) + if isinstance(node, ast.Alias): + return self.node_is_mappable(node.target) + if isinstance(node, ast.Callable): + # Throwing maps to GError, which is in the glib crate + if self.crate.local_name != 'glib' and node.throws: + glib_crate = self._extern_crates.get('GLib') + if self.is_excluded(glib_crate): + return False + if any([not self.node_is_mappable(param) for param in node.parameters]): + return False + return self.node_is_mappable(node.retval) + if isinstance(node, ast.List) and self.crate.local_name != 'glib': + # Lists are always GList or GSList, which are in the glib crate + glib_crate = self._extern_crates.get('GLib') + if self.is_excluded(glib_crate): + return False + return self.node_is_mappable(node.element_type) + if isinstance(node, ast.Array): + return self.node_is_mappable(node.element_type) + if isinstance(node, ast.Type) and node.target_giname: + crate,name = self._lookup_giname(node.target_giname) + return self.node_is_mappable(crate.namespace.names[name]) + return True + def map_parameter_type(self, parameter): """Return the Rust FFI type syntax for a function parameter. diff --git a/grust/output.py b/grust/output.py index 22fbdab..2cf3889 100644 --- a/grust/output.py +++ b/grust/output.py @@ -51,6 +51,9 @@ def __init__(self, filename, mode='w', encoding=None, newline=None): 'newline': newline } + def filename(self): + return self._filename + def __enter__(self): dirname, basename = os.path.split(self._filename) if sys.version_info.major >= 3: diff --git a/grust/templates/cargo/build.rs.tmpl b/grust/templates/cargo/build.rs.tmpl new file mode 100644 index 0000000..fb1851b --- /dev/null +++ b/grust/templates/cargo/build.rs.tmpl @@ -0,0 +1,13 @@ +extern crate pkg_config; + +const PACKAGES: &'static [ &'static str ] = &[ +% for package in packages: + "${package}", +% endfor +]; + +fn main() { + for package in PACKAGES { + pkg_config::probe_library(package).unwrap(); + } +} diff --git a/grust/templates/cargo/cargo.tmpl b/grust/templates/cargo/cargo.tmpl new file mode 100644 index 0000000..d5490af --- /dev/null +++ b/grust/templates/cargo/cargo.tmpl @@ -0,0 +1,26 @@ +[package] +name = "${pkgname}" +version = "0.0.1" +build = "build.rs" + +[lib] +path = "lib.rs" + +[build-dependencies] +pkg-config = "0.3.7" + +[dependencies.libc] +git = "https://github.com/rust-lang/libc.git" + +[dependencies.grust] +git = "https://github.com/gi-rust/grust.git" + +[dependencies.gtypes] +git = "https://github.com/gi-rust/gtypes.git" + +% for xc in mapper.extern_crates(): +% if xc.name is not 'libc' and not mapper.is_excluded(xc): +[dependencies.${xc.name}] +path = "../${xc.name}" +% endif +% endfor diff --git a/grust/templates/sys/crate.tmpl b/grust/templates/sys/crate.tmpl index 3dbb979..3ed4de1 100644 --- a/grust/templates/sys/crate.tmpl +++ b/grust/templates/sys/crate.tmpl @@ -57,6 +57,7 @@ def indenter(amount): ast.Constant: constant, ast.Record: struct, ast.Class: struct, + ast.Union: struct, ast.Interface: interface, ast.Enum: enum, ast.Bitfield: flags @@ -93,8 +94,7 @@ def indenter(amount): continue if node.name in self.attr.ignore_names: continue - if any(param.type == ast.TYPE_VALIST for param in node.parameters): - # Functions with a va_list parameter are usable only in C + if not mapper.node_is_mappable(node): continue functions.append(node) @@ -117,7 +117,7 @@ extern crate gtypes; % for xc in mapper.extern_crates(): % if xc.name == xc.local_name: extern crate ${xc.name}; -% else: +% elif not mapper.is_excluded(xc): extern crate ${xc.name} as ${xc.local_name}; % endif % endfor @@ -180,12 +180,16 @@ extern { raise ConsistencyError( 'C type name {} conflicts with a fundamental type' .format(node.ctype)) + if not mapper.node_is_mappable(node): + return '' %> pub type ${node.ctype} = ${mapper.map_aliased_type(node)}; \ ## <%def name="callback(node)"> +% if mapper.node_is_mappable(node): pub type ${node.ctype} = ${mapper.map_callback(node)}; +% endif \ ## <%def name="constant(node)"> @@ -203,11 +207,15 @@ pub struct ${node.ctype}(gpointer); pub enum ${node.ctype} { } % else: #[repr(C)] +% if not mapper.struct_is_valid(node): +pub struct ${node.ctype}; +% else: pub struct ${node.ctype} { -% for field in node.fields: +% for field in node.fields: ${'' if field.private else 'pub '}${sanitize_ident(field.name)}: ${mapper.map_field_type(field)}, -% endfor +% endfor } +% endif % endif \ ## @@ -307,11 +315,13 @@ extern { type=mapper.map_parameter_type(param)) for param in parameters] %>\ +% if mapper.node_is_mappable(node): pub fn ${node.symbol}(${', '.join(param_list)})\ -% if node.retval.type != ast.TYPE_NONE: +% if node.retval.type != ast.TYPE_NONE: -> ${mapper.map_return_type(node.retval)}\ -% endif +% endif ; +% endif \ ## <%def name="cfg_attr(cfg)">\