rust_swig/
lib.rs

1//! `rust_swig` is a Rust Simplified Wrapper and Interface Generator used
2//! to connect other programming languages to Rust.
3//! It is designed to be used from
4//! [cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html).
5//! The idea of this softwared based on [swig](http://www.swig.org).
6//! More details can be found at
7//! [README](https://github.com/Dushistov/rust_swig/blob/master/README.md)
8
9#![recursion_limit = "128"]
10
11macro_rules! parse_type {
12    ($($tt:tt)*) => {{
13        let ty: syn::Type = syn::parse_quote! { $($tt)* };
14        ty
15    }}
16}
17
18macro_rules! parse_spanned {
19    ($span:ident, $($tt:tt)*) => {{
20	let tt = quote::quote_spanned! { $span=> $($tt)* };
21        syn::parse2(tt)
22    }}
23}
24
25macro_rules! parse_type_spanned_checked {
26    ($span:ident, $($tt:tt)*) => {{
27	let ty: syn::Type = parse_spanned!($span, $($tt)*)
28	    .unwrap_or_else(|err| {
29		panic!("Can not parse type {}: {}", stringify!($($tt)*), err);
30	    });
31	ty
32    }}
33}
34
35mod code_parse;
36mod cpp;
37mod error;
38pub mod file_cache;
39mod java_jni;
40mod namegen;
41mod python;
42mod source_registry;
43mod str_replace;
44mod typemap;
45mod types;
46
47use std::{
48    env, io,
49    io::Write,
50    mem,
51    path::{Path, PathBuf},
52    process::{Command, Stdio},
53    str,
54    str::FromStr,
55    sync::Arc,
56};
57
58use log::debug;
59use proc_macro2::TokenStream;
60use strum::EnumIter;
61use syn::spanned::Spanned;
62
63use crate::{
64    error::{panic_on_parse_error, DiagnosticError, Result},
65    source_registry::{SourceId, SourceRegistry},
66    typemap::{ast::DisplayToTokens, TypeMap},
67    types::ItemToExpand,
68};
69
70pub(crate) static WRITE_TO_MEM_FAILED_MSG: &str = "Write to memory buffer failed, no free mem?";
71pub(crate) static SMART_PTR_COPY_TRAIT: &str = "SmartPtrCopy";
72
73/// Calculate target pointer width from environment variable
74/// that `cargo` inserts
75pub fn target_pointer_width_from_env() -> Option<usize> {
76    env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
77        .ok()
78        .map(|p_width| {
79            <usize>::from_str(&p_width)
80                .expect("Can not convert CARGO_CFG_TARGET_POINTER_WIDTH to usize")
81        })
82}
83
84/// `LanguageConfig` contains configuration for specific programming language
85pub enum LanguageConfig {
86    JavaConfig(JavaConfig),
87    CppConfig(CppConfig),
88    PythonConfig(PythonConfig),
89}
90
91/// Configuration for Java binding generation
92#[derive(Debug)]
93pub struct JavaConfig {
94    output_dir: PathBuf,
95    package_name: String,
96    null_annotation_package: Option<String>,
97    optional_package: String,
98    reachability_fence: JavaReachabilityFence,
99}
100
101impl JavaConfig {
102    /// Create `JavaConfig`
103    /// # Arguments
104    /// * `output_dir` - directory where place generated java files
105    /// * `package_name` - package name for generated java files
106    pub fn new(output_dir: PathBuf, package_name: String) -> JavaConfig {
107        JavaConfig {
108            output_dir,
109            package_name,
110            null_annotation_package: None,
111            optional_package: "java.util".to_string(),
112            reachability_fence: JavaReachabilityFence::GenerateFence(8),
113        }
114    }
115    /// Use @NonNull for types where appropriate
116    /// # Arguments
117    /// * `import_annotation` - import statement for @NonNull,
118    ///                         for example android.support.annotation.NonNull
119    #[deprecated(note = "Use use_null_annotation_from_package instead")]
120    pub fn use_null_annotation(mut self, import_annotation: String) -> JavaConfig {
121        let suffix = ".NonNull";
122        if !import_annotation.ends_with(suffix) {
123            panic!(
124                "import_annotation({}) should ends with {}",
125                import_annotation, suffix
126            );
127        }
128        let package = &import_annotation[0..import_annotation.len() - suffix.len()];
129        self.null_annotation_package = Some(package.into());
130        self
131    }
132    /// Use @NonNull/@Nullable for types where appropriate
133    /// # Arguments
134    /// * `null_annotation_package` - from which package import annotations like NonNull,
135    ///                         for example for Android Studio
136    ///                         you should pass android.support.annotation
137    pub fn use_null_annotation_from_package(
138        mut self,
139        null_annotation_package: String,
140    ) -> JavaConfig {
141        self.null_annotation_package = Some(null_annotation_package);
142        self
143    }
144    /// If you use JDK without java.util.Optional*, then you can provide
145    /// name of custom package with Optional. Default value is "java.util"
146    pub fn use_optional_package(mut self, optional_package: String) -> JavaConfig {
147        self.optional_package = optional_package;
148        self
149    }
150    /// Choose reachability fence variant, `JavaReachabilityFence::Std` provide
151    /// ability to generate better code, but not available untill Java 9 and
152    /// Android API level 28
153    pub fn use_reachability_fence(
154        mut self,
155        reachability_fence: JavaReachabilityFence,
156    ) -> JavaConfig {
157        self.reachability_fence = reachability_fence;
158        self
159    }
160}
161
162/// What reachability fence to use
163#[derive(Debug, Clone, Copy)]
164pub enum JavaReachabilityFence {
165    /// java.lang.ref.Reference.reachabilityFence​
166    Std,
167    /// If Reference.reachabilityFence​ is not available,
168    /// generate JNI code to emulate it
169    /// Argument is maximum number of parameters, that you would
170    /// "fence". By default is big enough number, but may be
171    /// you lint tool will be "against" too big number (we generate Java method
172    /// with such number of arguments)
173    GenerateFence(usize),
174}
175
176/// Configuration for C++ binding generation
177pub struct CppConfig {
178    output_dir: PathBuf,
179    namespace_name: String,
180    cpp_optional: CppOptional,
181    cpp_variant: CppVariant,
182    cpp_str_view: CppStrView,
183    /// Create separate *_impl.hpp files with methods implementations.
184    /// Can be necessary for the project with circular dependencies between classes.
185    separate_impl_headers: bool,
186}
187
188/// To which `C++` type map `std::option::Option`
189#[derive(Clone, Copy, EnumIter)]
190pub enum CppOptional {
191    /// `std::optional` from C++17 standard
192    Std17,
193    /// `boost::optional`
194    Boost,
195}
196
197impl From<CppOptional> for &'static str {
198    fn from(x: CppOptional) -> Self {
199        match x {
200            CppOptional::Std17 => "CppOptional::Std17",
201            CppOptional::Boost => "CppOptional::Boost",
202        }
203    }
204}
205
206/// To which `C++` type map `std::result::Result`
207#[derive(Clone, Copy, EnumIter)]
208pub enum CppVariant {
209    /// `std::variant` from C++17 standard
210    Std17,
211    /// `boost::variant`
212    Boost,
213}
214
215impl From<CppVariant> for &'static str {
216    fn from(x: CppVariant) -> Self {
217        match x {
218            CppVariant::Std17 => "CppVariant::Std17",
219            CppVariant::Boost => "CppVariant::Boost",
220        }
221    }
222}
223
224/// To whcih `C++` type map `&str`
225#[derive(Clone, Copy, EnumIter)]
226pub enum CppStrView {
227    /// `std::string_view` from C++17 standard
228    Std17,
229    /// `boost::string_view`
230    Boost,
231}
232
233impl From<CppStrView> for &'static str {
234    fn from(x: CppStrView) -> Self {
235        match x {
236            CppStrView::Std17 => "CppStrView::Std17",
237            CppStrView::Boost => "CppStrView::Boost",
238        }
239    }
240}
241
242impl CppConfig {
243    /// Create `CppConfig`
244    /// # Arguments
245    /// * `output_dir` - directory where place generated c++ files
246    /// * `namespace_name` - namespace name for generated c++ classes
247    pub fn new(output_dir: PathBuf, namespace_name: String) -> CppConfig {
248        CppConfig {
249            output_dir,
250            namespace_name,
251            cpp_optional: CppOptional::Std17,
252            cpp_variant: CppVariant::Std17,
253            cpp_str_view: CppStrView::Std17,
254            separate_impl_headers: false,
255        }
256    }
257    pub fn cpp_optional(self, cpp_optional: CppOptional) -> CppConfig {
258        CppConfig {
259            cpp_optional,
260            ..self
261        }
262    }
263    pub fn cpp_variant(self, cpp_variant: CppVariant) -> CppConfig {
264        CppConfig {
265            cpp_variant,
266            ..self
267        }
268    }
269    pub fn cpp_str_view(self, cpp_str_view: CppStrView) -> CppConfig {
270        CppConfig {
271            cpp_str_view,
272            ..self
273        }
274    }
275    /// Use boost for that fit: Result -> boost::variant,
276    /// Option -> boost::optional, &str -> boost::string_view
277    pub fn use_boost(self) -> CppConfig {
278        CppConfig {
279            cpp_variant: CppVariant::Boost,
280            cpp_optional: CppOptional::Boost,
281            cpp_str_view: CppStrView::Boost,
282            ..self
283        }
284    }
285    /// Create separate *_impl.hpp files with methods' implementations.
286    /// Can be necessary for the project with circular dependencies between classes.
287    pub fn separate_impl_headers(self, separate_impl_headers: bool) -> CppConfig {
288        CppConfig {
289            separate_impl_headers,
290            ..self
291        }
292    }
293}
294
295/// Configuration for Java binding generation
296pub struct PythonConfig {
297    module_name: String,
298}
299
300impl PythonConfig {
301    /// Create `PythonConfig`
302    /// # Arguments
303    pub fn new(module_name: String) -> PythonConfig {
304        PythonConfig { module_name }
305    }
306}
307
308/// `Generator` is a main point of `rust_swig`.
309/// It expands rust macroses and generates not rust code.
310/// It designed to use inside `build.rs`.
311pub struct Generator {
312    init_done: bool,
313    config: LanguageConfig,
314    conv_map: TypeMap,
315    conv_map_source: Vec<SourceId>,
316    foreign_lang_helpers: Vec<SourceCode>,
317    pointer_target_width: usize,
318    src_reg: SourceRegistry,
319    rustfmt_bindings: bool,
320    remove_not_generated_files: bool,
321}
322
323struct SourceCode {
324    id_of_code: String,
325    code: String,
326}
327
328static FOREIGNER_CLASS: &str = "foreigner_class";
329static FOREIGN_CLASS: &str = "foreign_class";
330static FOREIGN_ENUM: &str = "foreign_enum";
331static FOREIGN_INTERFACE: &str = "foreign_interface";
332static FOREIGN_CALLBACK: &str = "foreign_callback";
333static FOREIGNER_CODE: &str = "foreigner_code";
334static FOREIGN_CODE: &str = "foreign_code";
335static FOREIGN_TYPEMAP: &str = "foreign_typemap";
336
337impl Generator {
338    pub fn new(config: LanguageConfig) -> Generator {
339        let pointer_target_width = target_pointer_width_from_env();
340        let mut conv_map_source = Vec::new();
341        let mut foreign_lang_helpers = Vec::new();
342        let mut src_reg = SourceRegistry::default();
343        match config {
344            LanguageConfig::JavaConfig(ref java_cfg) => {
345                conv_map_source.push(
346                    src_reg.register(SourceCode {
347                        id_of_code: "jni-include.rs".into(),
348                        code: include_str!("java_jni/jni-include.rs")
349                            .replace(
350                                "java.util.Optional",
351                                &format!("{}.Optional", java_cfg.optional_package),
352                            )
353                            .replace(
354                                "java/util/Optional",
355                                &format!(
356                                    "{}/Optional",
357                                    java_cfg.optional_package.replace('.', "/")
358                                ),
359                            ),
360                    }),
361                );
362            }
363            LanguageConfig::CppConfig(..) => {
364                conv_map_source.push(src_reg.register(SourceCode {
365                    id_of_code: "cpp-include.rs".into(),
366                    code: include_str!("cpp/cpp-include.rs").into(),
367                }));
368                foreign_lang_helpers.push(SourceCode {
369                    id_of_code: "rust_vec_impl.hpp".into(),
370                    code: include_str!("cpp/rust_vec_impl.hpp").into(),
371                });
372                foreign_lang_helpers.push(SourceCode {
373                    id_of_code: "rust_foreign_vec_impl.hpp".into(),
374                    code: include_str!("cpp/rust_foreign_vec_impl.hpp").into(),
375                });
376                foreign_lang_helpers.push(SourceCode {
377                    id_of_code: "rust_foreign_slice_iter.hpp".into(),
378                    code: include_str!("cpp/rust_foreign_slice_iter.hpp").into(),
379                });
380                foreign_lang_helpers.push(SourceCode {
381                    id_of_code: "rust_foreign_slice_impl.hpp".into(),
382                    code: include_str!("cpp/rust_foreign_slice_impl.hpp").into(),
383                });
384                foreign_lang_helpers.push(SourceCode {
385                    id_of_code: "rust_slice_tmpl.hpp".into(),
386                    code: include_str!("cpp/rust_slice_tmpl.hpp").into(),
387                });
388            }
389            LanguageConfig::PythonConfig(..) => {
390                conv_map_source.push(src_reg.register(SourceCode {
391                    id_of_code: "python-include.rs".into(),
392                    code: include_str!("python/python-include.rs").into(),
393                }));
394            }
395        }
396        Generator {
397            init_done: false,
398            config,
399            conv_map: TypeMap::default(),
400            conv_map_source,
401            foreign_lang_helpers,
402            pointer_target_width: pointer_target_width.unwrap_or(0),
403            src_reg,
404            rustfmt_bindings: false,
405            remove_not_generated_files: false,
406        }
407    }
408
409    /// By default we get pointer_target_width via cargo (more exactly CARGO_CFG_TARGET_POINTER_WIDTH),
410    /// but you can change default value via this method
411    pub fn with_pointer_target_width(mut self, pointer_target_width: usize) -> Self {
412        self.pointer_target_width = pointer_target_width;
413        self
414    }
415
416    /// Set whether rustfmt should format the generated bindings.
417    pub fn rustfmt_bindings(mut self, doit: bool) -> Self {
418        self.rustfmt_bindings = doit;
419        self
420    }
421
422    /// If true removes not generated by rust_swig files from output directory,
423    /// suitable for removing obsolete files.
424    /// Warning! May remove your files if turn on, so by default false
425    pub fn remove_not_generated_files_from_output_directory(mut self, doit: bool) -> Self {
426        self.remove_not_generated_files = doit;
427        self
428    }
429
430    /// Add new foreign langauge type <-> Rust mapping
431    pub fn merge_type_map(mut self, id_of_code: &str, code: &str) -> Generator {
432        self.conv_map_source.push(self.src_reg.register(SourceCode {
433            id_of_code: id_of_code.into(),
434            code: code.into(),
435        }));
436        self
437    }
438
439    /// process `src` and save result of macro expansion to `dst`
440    ///
441    /// # Panics
442    /// Panics on error
443    pub fn expand<S, D>(mut self, crate_name: &str, src: S, dst: D)
444    where
445        S: AsRef<Path>,
446        D: AsRef<Path>,
447    {
448        let src_cnt = std::fs::read_to_string(src.as_ref()).unwrap_or_else(|err| {
449            panic!(
450                "Error during read for file {}: {}",
451                src.as_ref().display(),
452                err
453            )
454        });
455
456        let src_id = self.src_reg.register(SourceCode {
457            id_of_code: format!("{}: {}", crate_name, src.as_ref().display()),
458            code: src_cnt,
459        });
460
461        if let Err(err) = self.expand_str(src_id, dst) {
462            panic_on_parse_error(&self.src_reg, &err);
463        }
464    }
465
466    /// process `src` and save result of macro expansion to `dst`
467    ///
468    /// # Panics
469    /// Panics on I/O errors
470    fn expand_str<D>(&mut self, src_id: SourceId, dst: D) -> Result<()>
471    where
472        D: AsRef<Path>,
473    {
474        if self.pointer_target_width == 0 {
475            panic!(
476                r#"pointer target width unknown,
477 set env CARGO_CFG_TARGET_POINTER_WIDTH environment variable,
478 or use `with_pointer_target_width` function
479"#
480            );
481        }
482        let items = self.init_types_map(self.pointer_target_width)?;
483
484        let syn_file = syn::parse_file(self.src_reg.src(src_id))
485            .map_err(|err| DiagnosticError::from_syn_err(src_id, err))?;
486
487        let mut file =
488            file_cache::FileWriteCache::new(dst.as_ref(), &mut file_cache::NoNeedFsOpsRegistration);
489
490        for item in items {
491            write!(&mut file, "{}", DisplayToTokens(&item)).expect(WRITE_TO_MEM_FAILED_MSG);
492        }
493
494        // n / 2 - just guess
495        let mut items_to_expand = Vec::with_capacity(syn_file.items.len() / 2);
496
497        for item in syn_file.items {
498            if let syn::Item::Macro(mut item_macro) = item {
499                let is_our_macro = [
500                    FOREIGNER_CLASS,
501                    FOREIGN_CLASS,
502                    FOREIGN_ENUM,
503                    FOREIGN_INTERFACE,
504                    FOREIGN_CALLBACK,
505                    FOREIGN_TYPEMAP,
506                ]
507                .iter()
508                .any(|x| item_macro.mac.path.is_ident(x));
509                if !is_our_macro {
510                    writeln!(&mut file, "{}", DisplayToTokens(&item_macro))
511                        .expect("mem I/O failed");
512                    continue;
513                }
514                debug!("Found {}", DisplayToTokens(&item_macro.mac.path));
515                if item_macro.mac.tokens.is_empty() {
516                    return Err(DiagnosticError::new(
517                        src_id,
518                        item_macro.span(),
519                        format!(
520                            "missing tokens in call of macro '{}'",
521                            DisplayToTokens(&item_macro.mac.path)
522                        ),
523                    ));
524                }
525                let mut tts = TokenStream::new();
526                mem::swap(&mut tts, &mut item_macro.mac.tokens);
527                if item_macro.mac.path.is_ident(FOREIGNER_CLASS)
528                    || item_macro.mac.path.is_ident(FOREIGN_CLASS)
529                {
530                    let fclass = code_parse::parse_foreigner_class(src_id, &self.config, tts)?;
531                    debug!("expand_foreigner_class: self_desc {:?}", fclass.self_desc);
532                    self.conv_map.register_foreigner_class(&fclass);
533                    items_to_expand.push(ItemToExpand::Class(Box::new(fclass)));
534                } else if item_macro.mac.path.is_ident(FOREIGN_ENUM) {
535                    let fenum = code_parse::parse_foreign_enum(src_id, tts)?;
536                    items_to_expand.push(ItemToExpand::Enum(fenum));
537                } else if item_macro.mac.path.is_ident(FOREIGN_INTERFACE)
538                    || item_macro.mac.path.is_ident(FOREIGN_CALLBACK)
539                {
540                    let finterface = code_parse::parse_foreign_interface(src_id, tts)?;
541                    items_to_expand.push(ItemToExpand::Interface(finterface));
542                } else if item_macro.mac.path.is_ident(FOREIGN_TYPEMAP) {
543                    self.conv_map.parse_foreign_typemap_macro(src_id, tts)?;
544                } else {
545                    unreachable!();
546                }
547            } else {
548                writeln!(&mut file, "{}", DisplayToTokens(&item)).expect("mem I/O failed");
549            }
550        }
551        let generator = Generator::language_generator(&self.config);
552        let code = generator.expand_items(
553            &mut self.conv_map,
554            self.pointer_target_width,
555            &self.foreign_lang_helpers,
556            items_to_expand,
557            self.remove_not_generated_files,
558        )?;
559        for elem in code {
560            writeln!(&mut file, "{}", elem).expect(WRITE_TO_MEM_FAILED_MSG);
561        }
562
563        let source_bytes = file.take_content();
564        let source_bytes = generator.post_proccess_code(
565            &mut self.conv_map,
566            self.pointer_target_width,
567            source_bytes,
568        )?;
569        file.replace_content(source_bytes);
570
571        if self.rustfmt_bindings {
572            let source_bytes = file.take_content();
573            let new_cnt = rustfmt_cnt(source_bytes).unwrap_or_else(|err| {
574                panic!("Error during running of rustfmt: {}", err);
575            });
576            file.replace_content(new_cnt);
577        }
578
579        file.update_file_if_necessary().unwrap_or_else(|err| {
580            panic!(
581                "Error during write to file {}: {}",
582                dst.as_ref().display(),
583                err
584            );
585        });
586        Ok(())
587    }
588
589    fn init_types_map(&mut self, target_pointer_width: usize) -> Result<Vec<syn::Item>> {
590        if self.init_done {
591            return Ok(vec![]);
592        }
593        self.init_done = true;
594        for code_id in &self.conv_map_source {
595            let code = self.src_reg.src(*code_id);
596            self.conv_map.merge(*code_id, code, target_pointer_width)?;
597        }
598
599        if self.conv_map.is_empty() {
600            return Err(DiagnosticError::new_without_src_info(
601                "After merge all \"types maps\" have no convertion code",
602            ));
603        }
604
605        Ok(self.conv_map.take_utils_code())
606    }
607
608    fn language_generator(cfg: &LanguageConfig) -> &dyn LanguageGenerator {
609        match cfg {
610            LanguageConfig::JavaConfig(ref java_cfg) => java_cfg,
611            LanguageConfig::CppConfig(ref cpp_cfg) => cpp_cfg,
612            LanguageConfig::PythonConfig(ref python_cfg) => python_cfg,
613        }
614    }
615}
616
617trait LanguageGenerator {
618    fn expand_items(
619        &self,
620        conv_map: &mut TypeMap,
621        pointer_target_width: usize,
622        code: &[SourceCode],
623        items: Vec<ItemToExpand>,
624        remove_not_generated_files: bool,
625    ) -> Result<Vec<TokenStream>>;
626
627    fn post_proccess_code(
628        &self,
629        _conv_map: &mut TypeMap,
630        _pointer_target_width: usize,
631        generated_code: Vec<u8>,
632    ) -> Result<Vec<u8>> {
633        Ok(generated_code)
634    }
635}
636
637#[doc(hidden)]
638pub fn rustfmt_cnt(source: Vec<u8>) -> io::Result<Vec<u8>> {
639    let rustfmt = which::which("rustfmt")
640        .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?;
641
642    let mut cmd = Command::new(&*rustfmt);
643
644    cmd.stdin(Stdio::piped())
645        .stdout(Stdio::piped())
646        .stderr(Stdio::null());
647
648    let mut child = cmd.spawn()?;
649    let mut child_stdin = child.stdin.take().unwrap();
650    let mut child_stdout = child.stdout.take().unwrap();
651    let src_len = source.len();
652    let src = Arc::new(source);
653    // Write to stdin in a new thread, so that we can read from stdout on this
654    // thread. This keeps the child from blocking on writing to its stdout which
655    // might block us from writing to its stdin.
656    let stdin_handle = ::std::thread::spawn(move || {
657        let _ = child_stdin.write_all(src.as_slice());
658        src
659    });
660
661    let mut output = Vec::with_capacity(src_len);
662    io::copy(&mut child_stdout, &mut output)?;
663    let status = child.wait()?;
664    let src = stdin_handle.join().expect(
665        "The thread writing to rustfmt's stdin doesn't do \
666         anything that could panic",
667    );
668    let src =
669        Arc::try_unwrap(src).expect("Internal error: rusftfmt_cnt should only one Arc refernce");
670    match status.code() {
671        Some(0) => Ok(output),
672        Some(2) => Err(io::Error::new(
673            io::ErrorKind::Other,
674            "Rustfmt parsing errors.".to_string(),
675        )),
676        Some(3) => {
677            println!("warning=Rustfmt could not format some lines.");
678            Ok(src)
679        }
680        _ => {
681            println!("warning=Internal rustfmt error");
682            Ok(src)
683        }
684    }
685}