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() && f.ret().is_some())
89            || g2r_any!(|f| !f.cgo_call());
90    if use_shm {
91        importc.push_str(RawRsFile::go_shm_include());
92    }
93    if g2r_traits.iter().any(|t| t.has_ret()) {
94        importc.push_str(RawRsFile::go_internal_drop());
95    }
96    g2r_traits.iter().for_each(|t| {
97        importc.push_str(&t.to_importc());
98    });
99
100    let import_shm = or_empty!(
101        use_shm,
102        "mem_ring \"github.com/ihciah/rust2go/mem-ring\"\n\"github.com/panjf2000/ants/v2\"\n"
103    );
104    let import_runtime = or_empty!(use_runtime, "\"runtime\"\n");
105    let import_cgocall = or_empty!(use_cgocall, "\"github.com/ihciah/rust2go/cgocall\"\n");
106    let import_asmcall = or_empty!(use_asmcall, "\"github.com/ihciah/rust2go/asmcall\"\n");
107    let import_118 = or_empty!(args.go118, "\"reflect\"\n");
108
109    let mut go_content = format!(
110        "package main\n\n/*\n{importc}*/\nimport \"C\"\nimport (\n\"unsafe\"\n{import_runtime}{import_118}{import_shm}\n{import_cgocall}{import_asmcall})\n"
111    );
112    let levels = raw_file.convert_structs_levels().unwrap();
113    r2g_traits.iter().for_each(|t| {
114        go_content.push_str(&t.generate_go_interface());
115        go_content.push_str(&t.generate_go_exports(&levels));
116    });
117    go_content.push_str(
118        &raw_file
119            .convert_structs_to_go(&levels, args.go118)
120            .expect("Unable to generate go structs"),
121    );
122    if use_shm {
123        go_content.push_str(RawRsFile::go_shm_ring_init());
124    }
125    g2r_traits.iter().for_each(|t| {
126        go_content.push_str(&t.to_go(&levels));
127    });
128    if !args.without_main {
129        go_content.push_str("func main() {}\n");
130    }
131
132    std::fs::write(&args.dst, go_content).expect("Unable to write file");
133
134    if !args.no_fmt {
135        std::process::Command::new("go")
136            .arg("fmt")
137            .arg(&args.dst)
138            .status()
139            .unwrap();
140    }
141}