1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//
// Wildland Project
//
// Copyright © 2022 Golem Foundation,
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as published by
// the Free Software Foundation.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::io::Write;

use anyhow::Context;
use cfg_expr::targets::get_builtin_target_by_triple;
use rusty_bind_parser::binding_module::CargoFeature;
use rusty_bind_parser::BindingModule;
use syn::{File, Item};

/// The function parses a Rust source file and searches for a module decorated
/// with `#[binding_wrapper]` attribute. Once the module is provided it parses
/// it and generates gluecode for: SWIG interface, C++ and Swift.
///
pub fn parse_ffi_module(input_rust_file_path: &str, output_dir: &str) -> anyhow::Result<()> {
    let file = std::fs::read_to_string(input_rust_file_path)
        .with_context(|| format!("Could not read a file `{input_rust_file_path}`"))?;

    if !std::path::Path::new(output_dir).exists() {
        std::fs::create_dir(output_dir)
            .with_context(|| format!("Could not create a directory `{output_dir}`"))?;
    }

    let context = build_context().context("Failed to build context")?;

    // Don't return an error in case of any problems with parsing -
    // user will be informed about them in the further process of the compilation.
    //
    if let Ok(file) = syn::parse_str(&file) as Result<File, _> {
        for item in file.items {
            if let Item::Mod(module) = item {
                let parsed = BindingModule::translate_module(module, &context)?;
                let rust_code = parsed.get_tokens().to_string();
                let generated_code = parsed.generate_binding_files();

                let mut output_interface = std::fs::File::create(format!(
                    "{output_dir}/interface.rs"
                ))
                .with_context(|| format!("Could not create a file `{output_dir}/interface.rs`"))?;
                output_interface
                    .write_all(rust_code.as_bytes())
                    .with_context(|| format!("Could not write file `{output_dir}/interface.rs`"))?;

                let mut output_interface = std::fs::File::create(format!("{output_dir}/ffi_cxx.h"))
                    .with_context(|| format!("Could not create a file `{output_dir}/ffi_cxx.h`"))?;
                output_interface
                    .write_all(generated_code.cpp_header.as_bytes())
                    .with_context(|| format!("Could not write file `{output_dir}/ffi_cxx.h`"))?;

                let mut output_interface =
                    std::fs::File::create(format!("{output_dir}/ffi_swift.swift")).with_context(
                        || format!("Could not create a file `{output_dir}/ffi_swift.swift`"),
                    )?;
                output_interface
                    .write_all(generated_code.swift_header.as_bytes())
                    .with_context(|| {
                        format!("Could not write file `{output_dir}/ffi_swift.swift`")
                    })?;

                let mut output_interface = std::fs::File::create(format!(
                    "{output_dir}/ffi_swift.h"
                ))
                .with_context(|| format!("Could not create a file `{output_dir}/ffi_swift.h`"))?;
                output_interface
                    .write_all(generated_code.cpp_externs.as_bytes())
                    .with_context(|| format!("Could not write file `{output_dir}/ffi_swift.h`"))?;

                let mut output_interface = std::fs::File::create(format!(
                    "{output_dir}/ffi_swig.i"
                ))
                .with_context(|| format!("Could not create a file `{output_dir}/ffi_swig.i`"))?;
                output_interface
                    .write_all(generated_code.swig_interface.as_bytes())
                    .with_context(|| {
                        format!("Could not write to file `{output_dir}/ffi_swig.i`")
                    })?;
            }
        }
    }
    Ok(())
}

fn build_context() -> anyhow::Result<rusty_bind_parser::BuildContext> {
    let target = std::env::var("TARGET").context("TARGET env variable is not present")?;
    let current_target_info =
        get_builtin_target_by_triple(&target).context("Fail to parse TARGET env")?;

    let features = std::env::vars()
        .map(|kv| kv.0)
        .filter(|key| key.starts_with("CARGO_FEATURE_"))
        .map(|key| CargoFeature::new(&key["CARGO_FEATURE_".len()..]))
        .collect();

    let target_features = std::env::var("CARGO_CFG_TARGET_FEATURE")
        .unwrap_or_default()
        .split(',')
        .map(Into::into)
        .collect();

    Ok(rusty_bind_parser::BuildContext {
        current_target_info,
        features,
        target_features,
    })
}