rust2go_cli/
lib.rs

1// Copyright 2024 ihciah. All Rights Reserved.
2
3use std::io::Cursor;
4
5use clap::Parser;
6use itertools::Itertools as _;
7use rust2go_common::common::RawRsFile;
8
9#[derive(Parser, Debug, Default, Clone)]
10#[command(author, version, about, long_about = None)]
11pub struct Args {
12    /// Path of source rust file
13    #[arg(short, long)]
14    pub src: String,
15
16    /// Path of destination go file
17    #[arg(short, long)]
18    pub dst: String,
19
20    /// With or without go main function
21    #[arg(long, default_value = "false")]
22    pub without_main: bool,
23
24    /// Go 1.18 compatible
25    #[arg(long, default_value = "false")]
26    pub go118: bool,
27
28    /// Disable auto format go file
29    #[arg(long, default_value = "false")]
30    pub no_fmt: bool,
31}
32
33pub fn generate(args: &Args) {
34    // Read and parse rs file.
35    let file_content = std::fs::read_to_string(&args.src).expect("Unable to read file");
36    let raw_file = RawRsFile::new(file_content);
37
38    // Convert to Ref structs and write to output file.
39    let (name_mapping, ref_content) = raw_file
40        .convert_structs_to_ref()
41        .expect("Unable to convert to ref");
42    std::fs::write(&args.dst, ref_content.to_string()).expect("Unable to write file");
43
44    // Convert output file with cbindgen.
45    let mut cbuilder = cbindgen::Builder::new()
46        .with_language(cbindgen::Language::C)
47        .with_src(&args.dst)
48        .with_header("// Generated by rust2go. Please DO NOT edit this C part manually.");
49    for name in name_mapping.values().map(|n| n.to_string()).sorted() {
50        cbuilder = cbuilder.include_item(name);
51    }
52    let mut output = Vec::<u8>::new();
53    cbuilder
54        .generate()
55        .expect("Unable to generate bindings")
56        .write(Cursor::new(&mut output));
57
58    // Convert headers into golang.
59    let mut importc = String::from_utf8(output).expect("Unable to convert to string");
60
61    let r2g_traits = raw_file.convert_r2g_trait().unwrap();
62    let g2r_traits = raw_file.convert_g2r_trait().unwrap();
63    macro_rules! r2g_any {
64        ($f: expr) => {
65            r2g_traits.iter().any(|t| t.fns().iter().any($f))
66        };
67    }
68    macro_rules! g2r_any {
69        ($f: expr) => {
70            g2r_traits.iter().any(|t| t.fns().iter().any($f))
71        };
72    }
73    macro_rules! or_empty {
74        ($flag: expr, $content: expr) => {
75            if $flag {
76                $content
77            } else {
78                ""
79            }
80        };
81    }
82    let use_shm = r2g_any!(|f| f.mem_call_id().is_some());
83    let use_runtime =
84        r2g_any!(|f| f.mem_call_id().is_none()) || g2r_traits.iter().any(|t| !t.fns().is_empty());
85    let use_cgocall =
86        r2g_any!(|f| f.mem_call_id().is_none() && f.cgo_callback()) || g2r_any!(|f| f.cgo_call());
87    let use_asmcall =
88        r2g_any!(|f| f.mem_call_id().is_none() && !f.cgo_callback()) || g2r_any!(|f| !f.cgo_call());
89    if use_shm {
90        importc.push_str(RawRsFile::go_shm_include());
91    }
92    if g2r_traits.iter().any(|t| t.has_ret()) {
93        importc.push_str(RawRsFile::go_internal_drop());
94    }
95    g2r_traits.iter().for_each(|t| {
96        importc.push_str(&t.to_importc());
97    });
98
99    let import_shm = or_empty!(
100        use_shm,
101        "mem_ring \"github.com/ihciah/rust2go/mem-ring\"\n\"github.com/panjf2000/ants/v2\"\n"
102    );
103    let import_runtime = or_empty!(use_runtime, "\"runtime\"\n");
104    let import_cgocall = or_empty!(use_cgocall, "\"github.com/ihciah/rust2go/cgocall\"\n");
105    let import_asmcall = or_empty!(use_asmcall, "\"github.com/ihciah/rust2go/asmcall\"\n");
106    let import_118 = or_empty!(args.go118, "\"reflect\"\n");
107
108    let mut go_content = format!(
109        "package main\n\n/*\n{importc}*/\nimport \"C\"\nimport (\n\"unsafe\"\n{import_runtime}{import_118}{import_shm}\n{import_cgocall}{import_asmcall})\n"
110    );
111    let levels = raw_file.convert_structs_levels().unwrap();
112    r2g_traits.iter().for_each(|t| {
113        go_content.push_str(&t.generate_go_interface());
114        go_content.push_str(&t.generate_go_exports(&levels));
115    });
116    go_content.push_str(
117        &raw_file
118            .convert_structs_to_go(&levels, args.go118)
119            .expect("Unable to generate go structs"),
120    );
121    if use_shm {
122        go_content.push_str(RawRsFile::go_shm_ring_init());
123    }
124    g2r_traits.iter().for_each(|t| {
125        go_content.push_str(&t.to_go(&levels));
126    });
127    if !args.without_main {
128        go_content.push_str("func main() {}\n");
129    }
130
131    std::fs::write(&args.dst, go_content).expect("Unable to write file");
132
133    if !args.no_fmt {
134        std::process::Command::new("go")
135            .arg("fmt")
136            .arg(&args.dst)
137            .status()
138            .unwrap();
139    }
140}