├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── swig-derive-test ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── bindings.h ├── expanded.rs ├── src │ └── lib.rs ├── swig.i └── test.py ├── swig-derive ├── Cargo.toml └── src │ └── lib.rs └── swiggen ├── .gitignore ├── Cargo.toml └── src ├── lib.rs ├── main.rs └── ty.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "swig-derive", 5 | "swiggen", 6 | "swig-derive-test", 7 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sam Scott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust FFI Autogen 2 | ================ 3 | 4 | _A noble snippet emswiggens the smallest macro_ 5 | 6 | This is a proof of concept using procedural macros to generate: 7 | - (a) Rust `extern "C"` function definitions 8 | - (b) SWIG wrapper code for language bindings 9 | 10 | 11 | **WARNING**: This code is pretty much stream-of-conciousness code without 12 | any regard for sanity or style. Partially an experiment to see if possible, 13 | and partially just stumbling around procedural macros and syn. 14 | 15 | Using procedural macros, so of course this needs nightly for now. 16 | 17 | ## Showcase 18 | 19 | Write Rust, get python/ruby/java/pick your poison. Try out the [example](#example): 20 | 21 | ```bash 22 | cd swig-derive-test 23 | make 24 | python -c "import swig_derive_test as sdt; t = sdt.Test(42); print(t.get_field())" 25 | ``` 26 | ^^^ `Test` is a Rust object, behaving like a native Python class. 27 | 28 | 29 | ## Requirements 30 | 31 | Needs [`cargo-expand`](https://github.com/dtolnay/cargo-expand/) and [`swig`](http://swig.org/) installed. 32 | 33 | ## Organisation 34 | 35 | [swiggen](swiggen/) contains the main parsing/generation code, and also a binary 36 | for generating the final binders. 37 | 38 | [swiggen-derive](swiggen-derive/) contains the proc macro code, which simply 39 | calls out to swiggen. 40 | 41 | [swiggen-derive-test](swiggen-derive-test/) contains an example of the 42 | functionality. 43 | 44 | 45 | ## Example 46 | 47 | A full example can be found in [swig-derive-test](swig-derive-test/), with 48 | example Makefile etc. Clone this repository, navigate to the test folder 49 | and run `make test` to see it in action. 50 | 51 | Summary: 52 | 53 | - Write Rust code 54 | - Add `#[derive(Swig)]` and #[swiggen]` macros where appropriate 55 | - Build + run swiggen to produce lib, headers and bindings 56 | - Run swig on binding code 57 | - Compile swig output 58 | - Run Rust code in chosen language. 59 | 60 | Starting with: 61 | 62 | ```rust 63 | #![feature(proc_macro)] // <- we need nightly 64 | 65 | #[macro_use] 66 | extern crate swig_derive; 67 | use swig_derive::{swiggen, swiggen_hack}; 68 | 69 | #[derive(Default, Swig)] 70 | #[swig_derive(Default)] 71 | pub struct Test { 72 | pub field: u32 73 | } 74 | 75 | swiggen_hack!{ 76 | impl Test { 77 | #[swiggen(Test)] 78 | pub fn new(field: u32) -> Self { 79 | Self { 80 | field: field, 81 | } 82 | } 83 | 84 | #[swiggen(Test)] 85 | pub fn get_field(&self) -> u32 { 86 | self.field 87 | } 88 | } 89 | } 90 | 91 | #[swiggen] 92 | pub fn different_test() -> Test { 93 | Test::new(42) 94 | } 95 | 96 | #[no_mangle] 97 | pub extern "C" fn manual_extern() -> u32 { 98 | Test::new(13).get_field() 99 | } 100 | 101 | ``` 102 | 103 | Building this with [`crate-type` set to `staticlib` or `cdylib`](https://doc.rust-lang.org/reference/linkage.html) 104 | will produce some files of the form `lib_*.a`, `lib_*.so` containing a number of symbols like 105 | `__SWIG_INJECT_get_field_Test`. 106 | 107 | The [swiggen](swiggen/) crate contains a binary which processes a Rust crate 108 | and outputs (a) a header file, and (b) a SWIG bindings file. 109 | The former produced by calling out to [cbindgen](https://github.com/eqrion/cbindgen). 110 | 111 | Using [SWIG](www.swig.org/) on the swig file, calling the appropriate 112 | build functions (example for Python [here](swig-derive-test/Makefile)), and 113 | we are done: 114 | 115 | ```py 116 | >>> import swig_derive_test as sdt 117 | >>> t = sdt.Test() 118 | >>> t.get_field() 119 | 0 120 | >>> t = sdt.Test(12) 121 | >>> t.get_field() 122 | 12 123 | >>> sdt.different_test().get_field() 124 | 42 125 | >>> sdt.manual_extern() 126 | 13 127 | ``` 128 | 129 | ## Functionality 130 | 131 | Based on the above, what kind of seems to be working so far: 132 | 133 | - `#[swig(Derive)]` on a struct will generate appropriate cpp-style bindings 134 | in SWIG to produce nicely object-oriented code in the target language. 135 | - `#[swig_derive(...)]` attribute to autogen wrappers for derived methods (so far only `Default` is supported) 136 | - `#[swiggen]` on a regular method to get appropriately bound method 137 | - Some support for converting primitive types into extern types (thanks to cbindgen) 138 | - `Self` types can be used on method signatures (the correct struct is taken from the attribute) 139 | - `#[swiggen(Foo)]` generates class methods for `Foo` when used on an "impl" function 140 | - Regular `extern "C"` functions are still exported in the bindings 141 | 142 | Things that don't really work: 143 | 144 | - `swig_hack!` is needed on an impl block so that the extern methods generated 145 | inside the block get pushed outside the block (otherwise they aren't actually exported) 146 | - No idea how well other types/structs will actually work. No real testing. 147 | - Currently just hacked together by making loads of the cbindgen library public 148 | - Probably a million more problems 149 | - The code is not well written at all, everything is very hacky 150 | - Unwraps everywhere. No real error handling. Also all hidden behind macros 151 | so probably incredibly impenetrable errors. 152 | -------------------------------------------------------------------------------- /swig-derive-test/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /swig-derive-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swig-derive-test" 3 | version = "0.1.0" 4 | authors = ["Sam Scott "] 5 | 6 | [dependencies] 7 | swig-derive = { path = "../swig-derive" } 8 | 9 | [lib] 10 | crate-type = ["cdylib", "staticlib"] 11 | -------------------------------------------------------------------------------- /swig-derive-test/Makefile: -------------------------------------------------------------------------------- 1 | PKG_NAME=swig_derive_test 2 | 3 | default: bindings compile 4 | bindings: 5 | cargo +nightly build 6 | cargo +nightly run --manifest-path=../swiggen/Cargo.toml 7 | 8 | compile: 9 | swig -python -Wextra -c++ -o swig_wrap.cpp swig.i 10 | g++ -z noexecstack -std=c++17 -Wno-register -Wall -fPIC -c -g -o swig_wrap.o swig_wrap.cpp -I. $(shell python-config --includes) 11 | g++ -z noexecstack -std=c++17 -Wno-deprecated-register -static-libgcc -shared -o _${PKG_NAME}.so swig_wrap.o ../target/debug/lib${PKG_NAME}.a 12 | 13 | test: bindings compile 14 | python test.py 15 | -------------------------------------------------------------------------------- /swig-derive-test/README.md: -------------------------------------------------------------------------------- 1 | Example test of swig-derive 2 | ========================== 3 | 4 | Check out the Makefile for how this works. 5 | 6 | There are also examples of the output of the [expanded code](expanded.rs), 7 | the [produced bindings](bindings.h) and the [generated swig code](swig.i) -------------------------------------------------------------------------------- /swig-derive-test/bindings.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct Test; 5 | 6 | extern "C" { 7 | 8 | Test *__SWIG_INJECT_default_Test(); 9 | 10 | Test *__SWIG_INJECT_ffi_different_test(); 11 | 12 | void __SWIG_INJECT_free_Test(Test *arg); 13 | 14 | uint32_t manual_extern(); 15 | 16 | } // extern "C" 17 | -------------------------------------------------------------------------------- /swig-derive-test/expanded.rs: -------------------------------------------------------------------------------- 1 | #![feature(prelude_import)] 2 | #![no_std] 3 | #![feature(proc_macro)] 4 | #![feature(proc_macro_lib)] 5 | #[prelude_import] 6 | use std::prelude::v1::*; 7 | #[macro_use] 8 | extern crate std; 9 | 10 | #[macro_use] 11 | extern crate swig_derive; 12 | use swig_derive::{swiggen, swiggen_hack}; 13 | 14 | #[swig_derive(Default)] 15 | pub struct Test { 16 | pub field: u32, 17 | } 18 | #[automatically_derived] 19 | #[allow(unused_qualifications)] 20 | impl ::std::default::Default for Test { 21 | #[inline] 22 | fn default() -> Test { Test{field: ::std::default::Default::default(),} } 23 | } 24 | #[doc = 25 | "__SWIG_CODE\n// Wrapper for Rust class Test\nclass Test {\n public:\n ffi::Test *self;\n Test(ffi::Test *ptr) {\n self = ptr;\n };\n ~Test(){\n ffi::__SWIG_INJECT_free_Test(self);\n self = NULL;\n };\n Test() { self = __SWIG_INJECT_default_Test(); };\n};\n__SWIG_END_CODE\n__SWIG_HDR\n// Wrapper for Rust class Test\nclass Test {\n ffi::Test *self;\n public:\n ~Test();\n Test();\n};\n__SWIG_END_HDR\n"] 26 | #[allow(non_camel_case_types)] 27 | struct __SWIG_INJECT_Test; 28 | #[allow(non_snake_case)] 29 | #[no_mangle] 30 | pub extern "C" fn __SWIG_INJECT_free_Test(arg: *mut Test) { 31 | unsafe { 32 | if !!arg.is_null() { 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | { 43 | ::rt::begin_panic("assertion failed: !arg.is_null()", 44 | &("swig-derive-test/src/lib.rs", 8u32, 45 | 19u32)) 46 | } 47 | }; 48 | &*arg; 49 | } 50 | } 51 | #[allow(non_snake_case)] 52 | #[no_mangle] 53 | pub extern "C" fn __SWIG_INJECT_default_Test() -> *mut Test { 54 | Box::into_raw(Box::new(Test::default())) 55 | } 56 | #[repr(C)] 57 | pub struct Test2 { 58 | pub field: u32, 59 | } 60 | impl Test { 61 | pub fn new(field: u32) -> Self { Self{field: field,} } 62 | #[allow(non_snake_case)] 63 | #[doc = 64 | "__SWIG_CODE\n__SWIG_END_CODE\n__SWIG_HDR\n\n %extend Test {\n Test(uint32_t field) {\n return new swiggen::Test(ffi::__SWIG_INJECT_ffi_new(field));\n }\n };\n__SWIG_END_HDR\n"] 65 | fn __SWIG_INJECT_hidden_ffi_new() { } 66 | #[allow(non_snake_case)] 67 | #[no_mangle] 68 | pub extern "C" fn __SWIG_INJECT_ffi_new(field: u32) -> *mut Test { 69 | #[allow(unused_macros)] 70 | macro_rules! ffi_ref(( $ name : ident ) => ( 71 | let $ name = unsafe { 72 | assert ! ( ! $ name . is_null ( ) ) ; * $ name } 73 | ; ) ; ( @ ref $ name : ident ) => ( 74 | let $ name = unsafe { 75 | assert ! ( ! $ name . is_null ( ) ) ; & * $ name 76 | } ; ) ; ( @ prim $ name : ident ) => { } ;); 77 | #[allow(unused_macros)] 78 | macro_rules! box_ptr(( $ x : expr ) => ( 79 | Box :: into_raw ( Box :: new ( $ x ) ) ) ; ( 80 | @ prim $ x : expr ) => ( $ x ) ;); 81 | let res = Test::new(field); 82 | Box::into_raw(Box::new(res)) 83 | } 84 | pub fn get_field(&self) -> u32 { self.field } 85 | #[allow(non_snake_case)] 86 | #[doc = 87 | "__SWIG_CODE\n__SWIG_END_CODE\n__SWIG_HDR\n\n %extend Test {\n uint32_t get_field() {\n return uint32_t(ffi::__SWIG_INJECT_ffi_get_field($self->self));\n }\n };\n__SWIG_END_HDR\n"] 88 | fn __SWIG_INJECT_hidden_ffi_get_field() { } 89 | #[allow(non_snake_case)] 90 | #[no_mangle] 91 | pub extern "C" fn __SWIG_INJECT_ffi_get_field(wrapped_self: *const Test) 92 | -> u32 { 93 | #[allow(unused_macros)] 94 | macro_rules! ffi_ref(( $ name : ident ) => ( 95 | let $ name = unsafe { 96 | assert ! ( ! $ name . is_null ( ) ) ; * $ name } 97 | ; ) ; ( @ ref $ name : ident ) => ( 98 | let $ name = unsafe { 99 | assert ! ( ! $ name . is_null ( ) ) ; & * $ name 100 | } ; ) ; ( @ prim $ name : ident ) => { } ;); 101 | #[allow(unused_macros)] 102 | macro_rules! box_ptr(( $ x : expr ) => ( 103 | Box :: into_raw ( Box :: new ( $ x ) ) ) ; ( 104 | @ prim $ x : expr ) => ( $ x ) ;); 105 | let wrapped_self = 106 | unsafe { 107 | if !!wrapped_self.is_null() { 108 | { 109 | ::rt::begin_panic("assertion failed: !wrapped_self.is_null()", 110 | &("swig-derive-test/src/lib.rs", 111 | 20u32, 1u32)) 112 | } 113 | }; 114 | &*wrapped_self 115 | }; 116 | let res = Test::get_field(wrapped_self); 117 | res 118 | } 119 | } 120 | #[allow(non_snake_case)] 121 | #[doc = 122 | "__SWIG_CODE\n__SWIG_END_CODE\n__SWIG_HDR\n\n %extend Test {\n Test(uint32_t field) {\n return new swiggen::Test(ffi::__SWIG_INJECT_ffi_new(field));\n }\n };\n__SWIG_END_HDR\n"] 123 | fn __SWIG_INJECT_hidden_ffi_new() { } 124 | #[allow(non_snake_case)] 125 | #[no_mangle] 126 | pub extern "C" fn __SWIG_INJECT_ffi_new(field: u32) -> *mut Test { 127 | #[allow(unused_macros)] 128 | macro_rules! ffi_ref(( $ name : ident ) => ( 129 | let $ name = unsafe { 130 | assert ! ( ! $ name . is_null ( ) ) ; * $ name } ; ) 131 | ; ( @ ref $ name : ident ) => ( 132 | let $ name = unsafe { 133 | assert ! ( ! $ name . is_null ( ) ) ; & * $ name } ; 134 | ) ; ( @ prim $ name : ident ) => { } ;); 135 | #[allow(unused_macros)] 136 | macro_rules! box_ptr(( $ x : expr ) => ( 137 | Box :: into_raw ( Box :: new ( $ x ) ) ) ; ( 138 | @ prim $ x : expr ) => ( $ x ) ;); 139 | let res = Test::new(field); 140 | Box::into_raw(Box::new(res)) 141 | } 142 | #[allow(non_snake_case)] 143 | #[doc = 144 | "__SWIG_CODE\n__SWIG_END_CODE\n__SWIG_HDR\n\n %extend Test {\n uint32_t get_field() {\n return uint32_t(ffi::__SWIG_INJECT_ffi_get_field($self->self));\n }\n };\n__SWIG_END_HDR\n"] 145 | fn __SWIG_INJECT_hidden_ffi_get_field() { } 146 | #[allow(non_snake_case)] 147 | #[no_mangle] 148 | pub extern "C" fn __SWIG_INJECT_ffi_get_field(wrapped_self: *const Test) 149 | -> u32 { 150 | #[allow(unused_macros)] 151 | macro_rules! ffi_ref(( $ name : ident ) => ( 152 | let $ name = unsafe { 153 | assert ! ( ! $ name . is_null ( ) ) ; * $ name } ; ) 154 | ; ( @ ref $ name : ident ) => ( 155 | let $ name = unsafe { 156 | assert ! ( ! $ name . is_null ( ) ) ; & * $ name } ; 157 | ) ; ( @ prim $ name : ident ) => { } ;); 158 | #[allow(unused_macros)] 159 | macro_rules! box_ptr(( $ x : expr ) => ( 160 | Box :: into_raw ( Box :: new ( $ x ) ) ) ; ( 161 | @ prim $ x : expr ) => ( $ x ) ;); 162 | let wrapped_self = 163 | unsafe { 164 | if !!wrapped_self.is_null() { 165 | { 166 | ::rt::begin_panic("assertion failed: !wrapped_self.is_null()", 167 | &("swig-derive-test/src/lib.rs", 20u32, 168 | 1u32)) 169 | } 170 | }; 171 | &*wrapped_self 172 | }; 173 | let res = Test::get_field(wrapped_self); 174 | res 175 | } 176 | pub fn test_function() -> Test { Test::new(5) } 177 | #[allow(non_snake_case)] 178 | #[doc = 179 | "__SWIG_CODE\nTest test_function() {\n return Test(ffi::__SWIG_INJECT_ffi_test_function());\n }__SWIG_END_CODE\n__SWIG_HDR\nTest test_function();__SWIG_END_HDR\n"] 180 | fn __SWIG_INJECT_hidden_ffi_test_function() { } 181 | #[allow(non_snake_case)] 182 | #[no_mangle] 183 | pub extern "C" fn __SWIG_INJECT_ffi_test_function() -> *mut Test { 184 | #[allow(unused_macros)] 185 | macro_rules! ffi_ref(( $ name : ident ) => ( 186 | let $ name = unsafe { 187 | assert ! ( ! $ name . is_null ( ) ) ; * $ name } ; ) 188 | ; ( @ ref $ name : ident ) => ( 189 | let $ name = unsafe { 190 | assert ! ( ! $ name . is_null ( ) ) ; & * $ name } ; 191 | ) ; ( @ prim $ name : ident ) => { } ;); 192 | #[allow(unused_macros)] 193 | macro_rules! box_ptr(( $ x : expr ) => ( 194 | Box :: into_raw ( Box :: new ( $ x ) ) ) ; ( 195 | @ prim $ x : expr ) => ( $ x ) ;); 196 | let res = ::test_function(); 197 | Box::into_raw(Box::new(res)) 198 | } 199 | #[no_mangle] 200 | pub extern "C" fn test_value() -> u32 { Test::new(13).field } 201 | #[no_mangle] 202 | pub extern "C" fn test2_func() -> Test2 { Test2{field: 12,} } 203 | -------------------------------------------------------------------------------- /swig-derive-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro)] 2 | #![feature(proc_macro_gen)] 3 | 4 | #[macro_use] 5 | extern crate swig_derive; 6 | use swig_derive::{swiggen, swiggen_hack}; 7 | 8 | #[derive(Default, Swig)] 9 | #[swig_derive(Default)] 10 | pub struct Test { 11 | pub field: u32 12 | } 13 | 14 | swiggen_hack!{ 15 | impl Test { 16 | #[swiggen(Test)] 17 | pub fn new(field: u32) -> Self { 18 | Self { 19 | field: field, 20 | } 21 | } 22 | 23 | #[swiggen(Test)] 24 | pub fn get_field(&self) -> u32 { 25 | self.field 26 | } 27 | } 28 | } 29 | 30 | #[swiggen] 31 | pub fn different_test() -> Test { 32 | Test::new(42) 33 | } 34 | 35 | #[no_mangle] 36 | pub extern "C" fn manual_extern() -> u32 { 37 | Test::new(13).get_field() 38 | } 39 | -------------------------------------------------------------------------------- /swig-derive-test/swig.i: -------------------------------------------------------------------------------- 1 | %module swig_derive_test 2 | #define PKG_NAME swig_derive_test 3 | %include 4 | %include 5 | %include 6 | 7 | 8 | %{ 9 | namespace ffi { 10 | #include "bindings.h" 11 | } 12 | 13 | using namespace ffi; 14 | 15 | namespace swig_derive_test { 16 | // Wrapper for Rust class Test 17 | class Test { 18 | public: 19 | ffi::Test *self; 20 | Test(ffi::Test *ptr) { 21 | self = ptr; 22 | }; 23 | ~Test(){ 24 | ffi::__SWIG_INJECT_free_Test(self); 25 | self = NULL; 26 | }; 27 | Test() { self = __SWIG_INJECT_default_Test(); }; 28 | }; 29 | Test different_test() { 30 | return Test(ffi::__SWIG_INJECT_ffi_different_test()); 31 | }} 32 | %} 33 | 34 | namespace swig_derive_test { 35 | // Wrapper for Rust class Test 36 | class Test { 37 | ffi::Test *self; 38 | public: 39 | ~Test(); 40 | Test(); 41 | }; 42 | Test different_test(); 43 | } 44 | 45 | %ignore __SWIG_INJECT_; 46 | %include "bindings.h"; 47 | -------------------------------------------------------------------------------- /swig-derive-test/test.py: -------------------------------------------------------------------------------- 1 | import swig_derive_test as sdt 2 | 3 | t = sdt.Test() 4 | assert t.get_field() == 0 5 | 6 | t = sdt.Test(12) 7 | assert t.get_field() == 12 8 | 9 | assert sdt.different_test().get_field() == 42 10 | 11 | assert sdt.manual_extern() == 13 12 | 13 | print("It works!") 14 | -------------------------------------------------------------------------------- /swig-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swig-derive" 3 | version = "0.1.0" 4 | authors = ["Sam Scott "] 5 | 6 | [dependencies] 7 | cbindgen = { git = "https://github.com/samscott89/cbindgen" } 8 | swiggen = { path = "../swiggen" } 9 | syn = { version = "0.14", features = ["full"] } 10 | quote = "0.6" 11 | 12 | [lib] 13 | proc-macro = true 14 | -------------------------------------------------------------------------------- /swig-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "proc-macro"] 2 | #![feature(proc_macro)] 3 | #![feature(proc_macro_lib)] 4 | #![recursion_limit="128"] 5 | 6 | /// Procedural macros to generate `extern "C"` functions and SWIG wrapper code 7 | /// from Rust code. 8 | 9 | extern crate cbindgen; 10 | #[macro_use] 11 | extern crate quote; 12 | extern crate proc_macro; 13 | extern crate syn; 14 | extern crate swiggen; 15 | 16 | use proc_macro::TokenStream; 17 | 18 | /// Generate SWIG wrapper code for a struct, to handle freeing of the memory 19 | /// on destruction. 20 | /// Uses the `#[swig_derive(Foo)]` attribute to also derive these methods 21 | /// in SWIG. (Currently only `Default` is supported). 22 | #[proc_macro_derive(Swig, attributes(swig_derive))] 23 | pub fn swig_it(input: TokenStream) -> TokenStream { 24 | // Parse the string representation 25 | let ast: syn::DeriveInput = syn::parse(input).unwrap(); 26 | swiggen::impl_extern_it(&ast).into() 27 | } 28 | 29 | /// Convert a Rust method into an `extern "C"` definition with SWIG wrapping 30 | /// code. If this is used on a method inside an impl block, an additional 31 | /// parameter needs to be entered like `#[swiggen(Foo)]` to give the context. 32 | /// Currently, the `swiggen_hack` macro needs to also wrap the impl block 33 | /// to make it work 34 | #[proc_macro_attribute] 35 | pub fn swiggen(arg: TokenStream, input: TokenStream) -> TokenStream { 36 | let ast: syn::ItemFn = syn::parse(input).unwrap(); 37 | // Parses the arg `(Foo)` as `Some(Foo)`. 38 | let arg: swiggen::Args = syn::parse(arg).unwrap(); 39 | let base_name: Option = arg.0; 40 | 41 | let new_meth = swiggen::impl_extern_fn(&base_name, &ast); 42 | // When there is a base name, we rely on the `swiggen_hack` 43 | // to put the tokens in the right place later. 44 | let tokens = if base_name.is_some() { 45 | quote!{ 46 | #ast 47 | } 48 | } else { 49 | quote!{ 50 | #ast 51 | 52 | #new_meth 53 | } 54 | }; 55 | tokens.into() 56 | } 57 | 58 | /// Proc macro to be used on an impl block so that any `#[swiggen]` function 59 | /// can generate the extern code outside of the impl block. 60 | #[proc_macro] 61 | pub fn swiggen_hack(input: TokenStream) -> TokenStream { 62 | let ast: syn::ItemImpl = syn::parse(input).unwrap(); 63 | swiggen::split_out_externs(&ast).into() 64 | } 65 | 66 | #[proc_macro] 67 | pub fn swiggen_prelude(_input: TokenStream) -> TokenStream { 68 | // let ast: syn::ItemImpl = syn::parse(input).unwrap(); 69 | // swiggen::split_out_externs(&ast).into() 70 | let tokens = quote! { 71 | extern crate libc; 72 | use libc::*; 73 | use std::ffi::{CString, CStr}; 74 | 75 | #[no_mangle] 76 | pub extern fn free_string(s: *mut c_char) { 77 | unsafe { 78 | if s.is_null() { return } 79 | CString::from_raw(s) 80 | }; 81 | } 82 | 83 | #[allow(unused_macros)] 84 | macro_rules! ffi_ref { 85 | ($name:ident) => ( 86 | let $name = unsafe { 87 | assert!(!$name.is_null()); 88 | *$name 89 | }; 90 | ); 91 | (@ref $name:ident) => ( 92 | let $name = unsafe { 93 | assert!(!$name.is_null()); 94 | &*$name 95 | }; 96 | ); 97 | (@str $name:ident) => ( 98 | let $name = unsafe { 99 | assert!(!$name.is_null()); 100 | CStr::from_ptr($name).to_str().unwrap() 101 | }; 102 | ); 103 | (@prim $name:ident) => {}; 104 | } 105 | #[allow(unused_macros)] 106 | macro_rules! box_ptr { 107 | ($x:expr) => ( 108 | Box::into_raw(Box::new($x)) 109 | ); 110 | (@prim $x:expr) => ( 111 | $x 112 | ); 113 | (@str $x:expr) => ( 114 | CString::new($x).unwrap().into_raw() 115 | ); 116 | 117 | } 118 | }; 119 | 120 | tokens.into() 121 | } 122 | -------------------------------------------------------------------------------- /swiggen/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /swiggen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swiggen" 3 | version = "0.1.0" 4 | authors = ["Sam Scott "] 5 | 6 | [dependencies] 7 | failure = "0.1" 8 | proc-macro2 = "0.4" 9 | serde = "1.0.66" 10 | tempdir = "0.3.7" 11 | toml = "0.4" 12 | log = "0.4.2" 13 | env_logger = "0.5.10" 14 | 15 | [dependencies.syn] 16 | version = "0.14" 17 | default-features = false 18 | features = ["clone-impls", "derive", "parsing", "printing", "full", "extra-traits"] 19 | 20 | [dependencies.cbindgen] 21 | # version = "0.6" 22 | git = "https://github.com/samscott89/cbindgen" 23 | 24 | [dependencies.quote] 25 | default-features = false 26 | version = "0.6" 27 | 28 | [[bin]] 29 | name = "swiggen" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /swiggen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_doc_comment)] 2 | 3 | /// # swiggen 4 | /// 5 | /// The `swiggen` library is used to generate `extern "C"` definitions and 6 | /// SWIG wrapper code from Rust functions. 7 | /// 8 | /// This basically does two things: generates the `extern "C"` methods by 9 | /// applying typemaps from cbindgen, or some fairly crude heuristics - 10 | /// such as converting an opaque `Foo` into a `*mut Foo`, and running 11 | /// `Box::into_raw(Box::new(foo))` to convert it into a pointer. 12 | /// 13 | /// These exported functions all have mangled names like `__SWIG_INJECT_new_Foo`. 14 | /// The code also generates SWIG wrapper code which wraps these functions sp 15 | /// that `Foo` behaves like a native object with methods like `Foo.new`. 16 | /// The SWIG code is injected into the expanded Rust source code through doc 17 | /// comments on various structs/functions. 18 | 19 | 20 | extern crate cbindgen; 21 | #[macro_use] 22 | extern crate log; 23 | extern crate proc_macro2; 24 | #[macro_use] 25 | extern crate quote; 26 | #[macro_use] 27 | extern crate syn; 28 | 29 | use proc_macro2::{Span, TokenStream}; 30 | use quote::ToTokens; 31 | use quote::TokenStreamExt; 32 | 33 | use std::fmt; 34 | 35 | use std::fs::File; 36 | use std::io::Write; 37 | use std::str; 38 | 39 | use cbindgen::ir::ty; 40 | use cbindgen::utilities::SynAbiHelpers; 41 | use cbindgen::writer::{Source, SourceWriter}; 42 | 43 | /// Tags used to indicate swig binding code injected into the Rust source. 44 | enum SwigTag { 45 | CodeStart, 46 | CodeEnd, 47 | HdrStart, 48 | HdrEnd, 49 | SwigInject, 50 | } 51 | 52 | impl fmt::Display for SwigTag { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | let tag = self.to_str(); 55 | write!(f, "{}", tag) 56 | } 57 | } 58 | 59 | impl SwigTag { 60 | fn to_str(&self) -> &'static str { 61 | match self { 62 | SwigTag::CodeStart => "__SWIG_CODE\n", 63 | SwigTag::CodeEnd => "__SWIG_END_CODE\n", 64 | SwigTag::HdrStart => "__SWIG_HDR\n", 65 | SwigTag::HdrEnd => "__SWIG_END_HDR\n", 66 | SwigTag::SwigInject=> "__SWIG_INJECT_", 67 | } 68 | } 69 | 70 | #[inline] 71 | fn len(&self) -> usize { 72 | match self { 73 | SwigTag::CodeStart => "__SWIG_CODE\n", 74 | SwigTag::CodeEnd => "__SWIG_END_CODE\n", 75 | SwigTag::HdrStart => "__SWIG_HDR\n", 76 | SwigTag::HdrEnd => "__SWIG_END_HDR\n", 77 | SwigTag::SwigInject=> "__SWIG_INJECT_", 78 | }.len() 79 | } 80 | } 81 | 82 | pub trait ToSwig { 83 | fn to_swig(&self) -> String; 84 | } 85 | 86 | /// A type implementing `AsExtern` can be converted into an type compatible with 87 | /// `extern "C"` functions. 88 | pub trait AsExtern { 89 | fn as_extern(&self) -> TokenStream; 90 | } 91 | 92 | impl AsExtern for syn::DeriveInput { 93 | fn as_extern(&self) -> TokenStream { 94 | let name = &self.ident; 95 | let free_name = swig_free(&name); 96 | // For an stuct we want to derive Swig for, we add a `free_Foo` 97 | // method so we can free it from SWIG code. 98 | let mut tokens = quote! { 99 | #[allow(non_snake_case)] 100 | #[no_mangle] 101 | pub extern "C" fn #free_name(arg: *mut #name) { 102 | unsafe { 103 | assert!(!arg.is_null()); 104 | &*arg; 105 | } 106 | } 107 | }; 108 | let default_name = swig_fn(&name, "default"); 109 | 110 | // TOOD: Add more derive capabilities 111 | // Extracting the derived methods from `#[swig_derive(...)]`. 112 | // We need to automatically add the SWIG code since we cant somehow 113 | // add the `#[swiggen(Foo)]` attribute to the derived methods. 114 | let derivs = get_derives(&self.attrs); 115 | let new_toks = derivs.iter().filter_map(|w| { 116 | match w.as_str() { 117 | "Default" => { 118 | Some(quote! { 119 | #[allow(non_snake_case)] 120 | #[no_mangle] 121 | pub extern "C" fn #default_name() -> *mut #name { 122 | Box::into_raw(Box::new(#name::default())) 123 | } 124 | }) 125 | }, 126 | _ => None 127 | } 128 | }); 129 | tokens.append_all(new_toks); 130 | tokens 131 | } 132 | } 133 | 134 | /// A method definition inside an impl block has an additional 135 | /// `base` variable corresponding to the name of the type. 136 | struct InternalFn<'a> { 137 | base: &'a Option, 138 | fn_def: &'a syn::ItemFn, 139 | } 140 | 141 | /// Convenience method to use cbindgen to convert types into C-compat types. 142 | /// e.g. "input: u32" -> `cbindgen_write((input, u32))` might output `uint32 input`. 143 | fn cbindgen_write(s: &S) -> String { 144 | let mut buf = Vec::new(); 145 | { 146 | let cfg = cbindgen::Config::default(); 147 | let mut sw = SourceWriter::new(&mut buf, &cfg); 148 | s.write(&cfg, &mut sw); 149 | } 150 | String::from_utf8(buf).unwrap().replace("str", "char") 151 | } 152 | 153 | /// Hacky method to take a `&self` or `self` function argument and produce 154 | /// something compatible with `extern "C"` method. Since we can't use `self`, 155 | /// we coerce this to a pointer, and call the arg `wrapped_self`. 156 | fn convert_self_type(arg: &syn::FnArg, base: &Option) -> syn::FnArg { 157 | let base = base.clone().expect("Cannot convert `self` arg without provided base name. 158 | Try: `#[swiggen(Foo)]` in macro"); 159 | let mut arg = arg.clone().into_token_stream().to_string(); 160 | arg = if arg.starts_with('&') { 161 | arg.replace("&", "*const ") 162 | } else { 163 | "*mut ".to_string() + &arg 164 | }; 165 | arg = format!("wrapped_self: {}", arg.replace("self", &base.to_string())); 166 | syn::parse_str(&arg).unwrap() 167 | } 168 | 169 | /// For inputs, if the type is a primitive (as defined by cbindgen), we don't 170 | /// do anything. Otherwise, assume we will take it in as a pointer. 171 | fn convert_arg_type(syn::ArgCaptured { ref pat, ref ty, .. }: &syn::ArgCaptured) -> syn::FnArg { 172 | if ty.clone().into_token_stream().to_string().ends_with("str") { 173 | parse_quote!(#pat: *const c_char) 174 | } else { 175 | if needs_ref(ty) { 176 | parse_quote!(#pat: *const #ty) 177 | } else { 178 | parse_quote!(#pat: #ty) 179 | } 180 | } 181 | } 182 | 183 | /// Similar to above, make sure that we return primitives when 184 | /// recognised 185 | fn convert_ret_type(rty: &syn::ReturnType, base: &Option) -> syn::ReturnType { 186 | match rty { 187 | syn::ReturnType::Default => syn::ReturnType::Default, 188 | syn::ReturnType::Type(_, ty) => { 189 | if needs_ref(ty) { 190 | if ty.clone().into_token_stream().to_string() == "Self" { 191 | let base = base.clone().expect("Cannot convert `Self` return type without provided base name. 192 | Try: `#[swiggen(Foo)]` in macro"); 193 | parse_quote!(-> *mut #base) 194 | } else if ty.clone().into_token_stream().to_string() == "String" { 195 | parse_quote!(-> *mut c_char) 196 | } else { 197 | parse_quote!(-> *mut #ty) 198 | } 199 | } else { 200 | parse_quote!(-> #ty) 201 | } 202 | } 203 | } 204 | } 205 | 206 | /// For paths, assume we can convert to an opaque pointer. 207 | fn needs_ref(ty: &syn::Type) -> bool { 208 | match ty::Type::load(ty) { 209 | Ok(Some(ty::Type::Primitive(_))) => false, 210 | Ok(Some(ty::Type::Path(_)))=> true, 211 | _ => false, 212 | } 213 | } 214 | 215 | impl<'a> AsExtern for InternalFn<'a> { 216 | fn as_extern(&self) -> TokenStream { 217 | // Messy blob of code to convert function name, arguments, types, 218 | // return type and generate appropriate code. 219 | // Should be extracted out into smaller functions. 220 | let name = &self.fn_def.ident; 221 | let ext_name = swig_fn(&name, "ffi"); 222 | let mut args = Vec::::new(); 223 | let mut caller = Vec::::new(); 224 | let mut caller_ref = Vec::::new(); 225 | self.fn_def.decl.inputs.iter().for_each(|ref arg| { 226 | match arg { 227 | syn::FnArg::SelfRef(_) | syn::FnArg::SelfValue(_) => { 228 | // For self methods, we do some extra work to wrap the 229 | // function so that `impl Foo { fn bar(&self); }` 230 | // becomes `Foo_bar(wrapped_self: *const Foo)`. 231 | let wrapped_self = convert_self_type(&arg, self.base); 232 | args.push(wrapped_self.into_token_stream()); 233 | 234 | let ws = syn::Ident::new("wrapped_self", Span::call_site()); 235 | caller.push(ws.clone()); 236 | caller_ref.push(quote!{@ref #ws}); 237 | } 238 | syn::FnArg::Captured(ref ac) => { 239 | let id = match &ac.pat { 240 | syn::Pat::Ident(pi) => { 241 | &pi.ident 242 | }, 243 | _ => unimplemented!(), 244 | }; 245 | args.push(convert_arg_type(ac).into_token_stream()); 246 | caller.push(id.clone()); 247 | 248 | // this later calls the appropriate macro function as to 249 | // whether we need to do some pointer/box stuff 250 | if ac.ty.clone().into_token_stream().to_string().ends_with("str") { 251 | caller_ref.push(quote!{@str #id}); 252 | } else if let syn::Type::Reference(_) = ac.ty { 253 | caller_ref.push(quote!{@ref #id}); 254 | } else { 255 | caller_ref.push(quote!{@prim #id}); 256 | } 257 | }, 258 | _ => () 259 | } 260 | }); 261 | let base = self.base; 262 | let out = convert_ret_type(&self.fn_def.decl.output, self.base); 263 | // Similar to the above, this later calls the appropriate macro function 264 | // as to whether we need to do some pointer/box stuff 265 | let res_ref = if let syn::ReturnType::Type(_, ref ty) = self.fn_def.decl.output { 266 | if ty.clone().into_token_stream().to_string() == "String" { 267 | quote!{@str res} 268 | } else if needs_ref(&ty) { 269 | quote!{res} 270 | } else { 271 | quote!{@prim res} 272 | } 273 | } else { 274 | quote!{@prim res} 275 | }; 276 | 277 | /// Generate the function. We also inject some macro 278 | /// definitions to help with converting pointers into types and types 279 | /// into pointers. 280 | let tokens = quote! { 281 | #[allow(non_snake_case)] 282 | #[no_mangle] 283 | pub extern "C" fn #ext_name(#(#args),*) #out { 284 | #(ffi_ref!(#caller_ref);)* 285 | let res = #base::#name(#(#caller),*); 286 | box_ptr!(#res_ref) 287 | } 288 | }; 289 | tokens 290 | } 291 | } 292 | 293 | 294 | /// Helper function to define the exported/mangled names. 295 | fn swig_fn(name: &syn::Ident, fn_name: &str) -> syn::Ident { 296 | syn::Ident::new(&format!("{}{}_{}", SwigTag::SwigInject, fn_name, name), Span::call_site()) 297 | } 298 | 299 | fn swig_free(name: &syn::Ident) -> syn::Ident { 300 | swig_fn(name, "free") 301 | } 302 | 303 | impl ToSwig for syn::DeriveInput { 304 | fn to_swig(&self) -> String { 305 | /// Generate the SWIG wrapper code as a string. 306 | /// Basically, a class for the Rust struct `Foo` is just a wrapper 307 | /// class called `Foo` which contains a pointer to the actual Rust 308 | /// object. 309 | 310 | // prefix with tag 311 | let mut swigged = SwigTag::CodeStart.to_string(); 312 | let mut swigged_h = SwigTag::HdrStart.to_string(); 313 | 314 | let name = &self.ident; 315 | match &self.data { 316 | syn::Data::Struct(ref _ds) => { 317 | // simple wrapper definition to wrap opaque pointer. 318 | // methods get added elsewhere 319 | swigged.push_str(&format!("\ 320 | // Wrapper for Rust class {name} 321 | class {name} {{ 322 | public: 323 | ffi::{name} *self; 324 | {name}(ffi::{name} *ptr) {{ 325 | self = ptr; 326 | }}; 327 | ~{name}(){{ 328 | ffi::{free_name}(self); 329 | self = NULL; 330 | }}; 331 | ", name=name, free_name=swig_free(&name)) 332 | ); 333 | 334 | swigged_h.push_str(&format!("\ 335 | // Wrapper for Rust class {name} 336 | class {name} {{ 337 | ffi::{name} *self; 338 | public: 339 | ~{name}(); 340 | ", name=name) 341 | ); 342 | // pull out any derive implementations we want to wrap 343 | // TODO: do this in a less ad-hoc way 344 | get_derives(&self.attrs).iter().for_each(|w| { 345 | match w.as_str() { 346 | "Default" => { 347 | swigged.push_str(&format!( 348 | "{name}() {{ self = {def_name}(); }};\n", 349 | name=name, def_name=swig_fn(&name, "default") 350 | )); 351 | swigged_h.push_str(&format!("{}();\n",name)); 352 | }, 353 | _ => (), 354 | } 355 | 356 | }); 357 | swigged.push_str("};\n"); 358 | swigged_h.push_str("};\n"); 359 | }, 360 | _ => unimplemented!(), 361 | } 362 | swigged.push_str(&SwigTag::CodeEnd.to_str()); 363 | swigged_h.push_str(&SwigTag::HdrEnd.to_str()); 364 | swigged.push_str(&swigged_h); 365 | swigged 366 | } 367 | } 368 | 369 | impl<'a> ToSwig for InternalFn<'a> { 370 | fn to_swig(&self) -> String { 371 | // Generate SWIG wrapper for methods. 372 | // Main complication is making sure that namespaces are correct since 373 | // we are basically overwriting names. 374 | // Also a bit of magic to take an impl method, and add it back into 375 | // being a class method. 376 | 377 | // prefix with tag 378 | let mut swigged = SwigTag::CodeStart.to_string(); 379 | let mut swigged_h = SwigTag::HdrStart.to_string(); 380 | 381 | let name = &self.fn_def.ident; 382 | let cb_fn = cbindgen::ir::Function::load(name.to_string(), 383 | &self.fn_def.decl, 384 | true, 385 | &[], 386 | &None).unwrap(); 387 | 388 | let mut args = String::new(); 389 | let mut caller = String::new(); 390 | 391 | // Convert function arguments 392 | cb_fn.args.iter().for_each(|arg| { 393 | if args.len() > 0 { 394 | args += ", "; 395 | } 396 | if caller.len() > 0 { 397 | caller += ", "; 398 | } 399 | if arg.0 == "self" { 400 | caller += "$self->self"; 401 | } else { 402 | args += &cbindgen_write(arg); 403 | caller += &arg.0; 404 | } 405 | }); 406 | 407 | 408 | // Convert return type 409 | let mut out = cbindgen_write(&cb_fn.ret); 410 | if out == "Self" { 411 | out = self.base.clone().expect("Cannot convert `Self` return type without provided base name. 412 | Try: `#[swiggen(Foo)]` in macro").to_string(); 413 | } else if out == "String" { 414 | out = "char *".to_string() 415 | } 416 | let mut ret_out = out.clone(); 417 | 418 | 419 | // Convert function name. 420 | let name = if name.to_string() == "new" { 421 | // Custom format for new functions 422 | ret_out = "".to_string(); 423 | out = "new PKG_NAME::".to_string() + &out; 424 | self.base.clone().expect("Cannot convert `Self` return type without provided base name. 425 | Try: `#[swiggen(Foo)]` in macro").to_string() 426 | } else { 427 | name.to_string() 428 | }; 429 | 430 | // Get the mangled name exported by Rust 431 | let ext_name = swig_fn(&self.fn_def.ident, "ffi"); 432 | 433 | // The following code generates the function definitions and the header 434 | // Code needed for SWIG to generate bindings. 435 | 436 | if self.base.is_none() { 437 | swigged.push_str(&format!("\ 438 | {ret_out} {name}({args}) {{ 439 | return ({out})(ffi::{ext_name}({caller})); 440 | }}" 441 | , name=name, ext_name=ext_name, out=out, ret_out=ret_out, args=args, caller=caller)); 442 | } 443 | if let Some(base) = self.base { 444 | // Note the %extend is used by SWIG to make this a class method for 445 | // `base`. 446 | swigged_h.push_str(&format!(" 447 | %extend {base_name} {{ 448 | {ret_out} {name}({args}) {{ 449 | return ({out})(ffi::{ext_name}({caller})); 450 | }} 451 | }};\n" 452 | ,name=name, ext_name=ext_name, base_name=base, ret_out=ret_out, out=out, args=args, caller=caller)); 453 | } else { 454 | swigged_h.push_str(&format!("\ 455 | {out} {name}({args});" 456 | , name=name, out=out, args=args)); 457 | } 458 | 459 | swigged.push_str(&SwigTag::CodeEnd.to_str()); 460 | swigged_h.push_str(&SwigTag::HdrEnd.to_str()); 461 | swigged.push_str(&swigged_h); 462 | swigged 463 | } 464 | } 465 | 466 | 467 | /// Generate extern and SWIG code for a `#[derive(Swig)]` annotated item. 468 | pub fn impl_extern_it(ast: &syn::DeriveInput) -> TokenStream { 469 | let comment = ast.to_swig(); 470 | let comment = format!("#[doc=\"{}\"] #[allow(non_camel_case_types)] struct {}{};", comment, SwigTag::SwigInject, ast.ident); 471 | let doc_comment: syn::ItemStruct = syn::parse_str(&comment).expect("failed to generate SWIG code correctly"); 472 | let mut tokens: TokenStream = doc_comment.into_token_stream(); 473 | tokens.append_all(ast.as_extern().into_iter()); 474 | tokens 475 | } 476 | 477 | /// Generate extern and SWIG code for a `#[swiggen]` annotated method. 478 | pub fn impl_extern_fn(base_name: &Option, ast: &syn::ItemFn) -> TokenStream { 479 | let ifn = InternalFn { 480 | base: base_name, 481 | fn_def: ast, 482 | }; 483 | 484 | let tok = ifn.as_extern(); 485 | let comment = ifn.to_swig(); 486 | let hidden = swig_fn(&ast.ident, "hidden_ffi"); 487 | quote! { 488 | #[allow(non_snake_case)] 489 | #[doc=#comment] 490 | fn #hidden(){} 491 | 492 | #tok 493 | } 494 | } 495 | 496 | /// Write the swig code (injected via doc comments) into `swig.i`. 497 | /// This parses expanded Rust code, and writes the SWIG code to a file. 498 | pub fn gen_swig(pkg_name: &str, src: &str) { 499 | let mut tmp_file = File::create("swig.i").unwrap(); 500 | 501 | tmp_file.write_all(format!("\ 502 | %module {name} 503 | #define PKG_NAME {name} 504 | %include 505 | %include 506 | %include 507 | 508 | %typemap(newfree) char * \"free_string($1);\"; 509 | 510 | 511 | %{{ 512 | namespace ffi {{ 513 | #include \"bindings.h\" 514 | }} 515 | 516 | using namespace ffi; 517 | 518 | namespace {name} {{ 519 | ", name=pkg_name).as_bytes()).unwrap(); 520 | 521 | let syntax = syn::parse_file(&src).expect("Unable to parse file"); 522 | trace!("Syntax: {:#?}", syntax); 523 | let mut hdr = String::new(); 524 | 525 | // SWIG code is inside doc comments: 526 | // #[doc = ""] 527 | // struct __SWIG_INJECT_Foo; 528 | // 529 | // So we extract this out. 530 | 531 | syntax.items.iter().flat_map(|i| { 532 | // Extract out all of the attributes which are attached to structs/functions 533 | // starting with "__SWIG_INJECT" 534 | match i { 535 | syn::Item::Impl(ii) => { 536 | ii.items.iter().fold(Vec::new(), |mut acc, ref ii| { 537 | match ii { 538 | syn::ImplItem::Method(iim) => { 539 | debug!("{:#?}", iim); 540 | if iim.sig.ident.to_string().starts_with(SwigTag::SwigInject.to_str()) { 541 | acc.extend_from_slice(&iim.attrs[..]); 542 | } 543 | acc 544 | }, 545 | _ => Vec::new(), 546 | } 547 | }) 548 | }, 549 | syn::Item::Struct(syn::ItemStruct { attrs, ident, .. }) | 550 | syn::Item::Fn(syn::ItemFn { attrs, ident, ..}) => { 551 | if ident.to_string().starts_with(SwigTag::SwigInject.to_str()) { 552 | debug!("{:#?}", attrs); 553 | attrs.clone() 554 | } else { 555 | Vec::new() 556 | } 557 | }, 558 | _ => Vec::new() 559 | } 560 | }).for_each(|ref attr| { 561 | match attr.interpret_meta() { 562 | Some(syn::Meta::NameValue(ref mnv)) if &mnv.ident.to_string() == "doc" => { 563 | // Extract out the doc comment for these attributes 564 | if let syn::Lit::Str(ref ls) = mnv.lit { 565 | let mut swig_class = ls.value().replace("\\n", "\n"); 566 | let prefix_offset = swig_class.find(SwigTag::CodeStart.to_str()).expect("no code prefix") + SwigTag::CodeStart.len(); 567 | let suffix_offset = swig_class.find(SwigTag::CodeEnd.to_str()).expect("no code suffix"); 568 | let final_class = &swig_class[prefix_offset..suffix_offset]; 569 | 570 | let prefix_offset = swig_class.find(SwigTag::HdrStart.to_str()).expect("no header prefix") + SwigTag::HdrStart.len(); 571 | let suffix_offset = swig_class.find(SwigTag::HdrEnd.to_str()).expect("no header suffix"); 572 | let final_hdr = &swig_class[prefix_offset..suffix_offset]; 573 | 574 | tmp_file.write_all(&final_class.replace("\\n", "\n").as_bytes()).unwrap(); 575 | hdr += &final_hdr.replace("\\n", "\n"); 576 | debug!("{}", final_hdr); 577 | debug!("{}", final_class); 578 | 579 | } 580 | }, 581 | _ => () 582 | } 583 | }); 584 | 585 | tmp_file.write_all(format!("\ 586 | }} 587 | %}} 588 | 589 | namespace {name} {{ 590 | {header} 591 | }} 592 | 593 | %ignore {inject}; 594 | %include \"bindings.h\"; 595 | ", name=pkg_name, header=hdr, inject=SwigTag::SwigInject).as_bytes()).unwrap(); 596 | } 597 | 598 | 599 | /// Extract out any `derive(Foo)` attributes. 600 | fn get_derives(attrs: &[syn::Attribute]) -> Vec { 601 | attrs.iter().filter_map(|a| a.interpret_meta()) 602 | .filter_map(|a| { 603 | if let syn::Meta::List(ml) = a { 604 | Some(ml) 605 | } else { 606 | None 607 | } 608 | }).filter(|ml| ml.ident.to_string() == "swig_derive") 609 | .flat_map(|ml| ml.nested) 610 | .filter_map(|nm| { 611 | if let syn::NestedMeta::Meta(m) = nm { 612 | if let syn::Meta::Word(w) = m { 613 | Some(w.to_string()) 614 | } else { 615 | None 616 | } 617 | } else { 618 | None 619 | } 620 | }).collect() 621 | } 622 | 623 | /// Parse a Rust file to extract any extern "C" functions or 624 | /// `#[swiggen]`-annotated methods and move these out of the impl block. 625 | pub fn split_out_externs(ast: &syn::ItemImpl) -> TokenStream { 626 | let mut tokens = TokenStream::new(); 627 | tokens.append_all(ast.items.iter().filter_map(|item| { 628 | match item { 629 | syn::ImplItem::Method(iim) => { 630 | if iim.sig.abi.is_c(){ 631 | Some(item.into_token_stream()) 632 | } else { 633 | let mut ret = None; 634 | for attr in iim.attrs.iter().filter_map(|a| a.interpret_meta()) { 635 | match attr { 636 | syn::Meta::List(ml) => if ml.ident == syn::Ident::new("swiggen", Span::call_site()) { 637 | if let Some(v) = ml.nested.first().map(|p| p.into_value()) { 638 | match v { 639 | syn::NestedMeta::Meta(m) => { 640 | let base_name = Some(m.name()); 641 | ret = Some(impl_extern_fn(&base_name, &iim_to_itemfn(iim.clone()))); 642 | }, 643 | _ => {} 644 | } 645 | } 646 | }, 647 | _ => {} 648 | } 649 | } 650 | ret 651 | } 652 | }, 653 | _ => None, 654 | } 655 | })); 656 | 657 | quote!{ 658 | #ast 659 | 660 | #tokens 661 | } 662 | } 663 | 664 | #[derive(Debug)] 665 | pub struct Args(pub Option); 666 | 667 | // Extract an `Option` from `(T)` or `""`. 668 | impl syn::synom::Synom for Args { 669 | named!(parse -> Self, map!(option!(map!( 670 | parens!(syn!(syn::Ident)), 671 | |(_parens, id)| id 672 | )), |o| Args(o))); 673 | } 674 | 675 | fn iim_to_itemfn(iim: syn::ImplItemMethod) -> syn::ItemFn { 676 | syn::ItemFn { 677 | attrs: iim.attrs, 678 | vis: iim.vis, 679 | constness: iim.sig.constness, 680 | unsafety: iim.sig.unsafety, 681 | abi: iim.sig.abi, 682 | ident: iim.sig.ident, 683 | decl: Box::new(iim.sig.decl), 684 | block: Box::new(iim.block), 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /swiggen/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate cbindgen; 2 | extern crate env_logger; 3 | #[macro_use] 4 | extern crate log; 5 | extern crate failure; 6 | #[macro_use] 7 | extern crate serde; 8 | extern crate swiggen; 9 | extern crate tempdir; 10 | extern crate toml; 11 | 12 | use tempdir::TempDir; 13 | use std::env; 14 | use std::process::Command; 15 | use cbindgen::{Builder, Config, Language}; 16 | use std::fs::File; 17 | use std::path::Path; 18 | use std::io::{Read, Write}; 19 | use std::str; 20 | 21 | use failure::Error; 22 | 23 | // let metadata = cargo_metadata::metadata(Some(Path::new("./Cargo.toml"))).unwrap(); 24 | #[derive(Clone, Deserialize, Debug)] 25 | pub struct Manifest { 26 | pub package: Package, 27 | } 28 | 29 | #[derive(Clone, Deserialize, Debug)] 30 | pub struct Package { 31 | pub name: String, 32 | } 33 | 34 | /// Parse the Cargo.toml for a given path 35 | pub fn manifest(manifest_path: &Path) -> Result { 36 | let mut s = String::new(); 37 | let mut f = File::open(manifest_path)?; 38 | f.read_to_string(&mut s)?; 39 | 40 | toml::from_str::(&s).map_err(|x| x.into()) 41 | } 42 | pub fn main() { 43 | env_logger::init(); 44 | let manifest = manifest(&Path::new("./Cargo.toml")).unwrap(); 45 | 46 | trace!("{:#?}", manifest); 47 | let package_name = &manifest.package.name.replace("-", "_"); 48 | 49 | let tmp_dir = TempDir::new("cargo-exp").unwrap(); 50 | let file_path = tmp_dir.path().join("expanded.rs"); 51 | let mut tmp_file = File::create(&file_path).unwrap(); 52 | 53 | let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo +nightly")); 54 | 55 | let mut cmd = Command::new(cargo); 56 | cmd.arg("expand"); 57 | cmd.arg("--features=bindings"); 58 | let output = cmd.output().unwrap(); 59 | trace!("Output: {:#?}", output); 60 | tmp_file.write_all(&output.stdout).unwrap(); 61 | 62 | gen_bindings(&file_path); 63 | swiggen::gen_swig(package_name, str::from_utf8(&output.stdout).unwrap()); 64 | } 65 | 66 | fn gen_bindings(path: &Path) { 67 | let config = Config { language: Language::Cxx, .. Config::default() }; 68 | 69 | Builder::new() 70 | .with_src(path) 71 | .with_config(config) 72 | .generate() 73 | .expect("Unable to generate bindings") 74 | .write_to_file("bindings.h"); 75 | } 76 | -------------------------------------------------------------------------------- /swiggen/src/ty.rs: -------------------------------------------------------------------------------- 1 | 2 | struct Mapping { 3 | is_self: bool, 4 | 5 | } 6 | 7 | match arg { 8 | syn::FnArg::SelfRef(_) | syn::FnArg::SelfValue(_) => { 9 | // For self methods, we do some extra work to wrap the 10 | // function so that `impl Foo { fn bar(&self); }` 11 | // becomes `Foo_bar(wrapped_self: *const Foo)`. 12 | let wrapped_self = convert_self_type(&arg, self.base); 13 | args.push(wrapped_self.into_token_stream()); 14 | 15 | let ws = syn::Ident::new("wrapped_self", Span::call_site()); 16 | caller.push(ws.clone()); 17 | caller_ref.push(quote!{@ref #ws}); 18 | } 19 | syn::FnArg::Captured(ref ac) => { 20 | let id = match &ac.pat { 21 | syn::Pat::Ident(pi) => { 22 | &pi.ident 23 | }, 24 | _ => unimplemented!(), 25 | }; 26 | args.push(convert_arg_type(ac).into_token_stream()); 27 | caller.push(id.clone()); 28 | 29 | // this later calls the appropriate macro function as to 30 | // whether we need to do some pointer/box stuff 31 | if ac.ty.clone().into_token_stream().to_string().ends_with("str") { 32 | caller_ref.push(quote!{@str #id}); 33 | } else if let syn::Type::Reference(_) = ac.ty { 34 | caller_ref.push(quote!{@ref #id}); 35 | } else { 36 | caller_ref.push(quote!{@prim #id}); 37 | } 38 | }, 39 | _ => () 40 | } --------------------------------------------------------------------------------