Skip to main content

hicc_build/
lib.rs

1use hicc_autogen::{Cpp, ExportClasses, ExportLib, ImportClass, ImportLib};
2use std::env;
3use std::fs;
4use std::io::{self, Write};
5use std::ops::{Deref, DerefMut};
6use std::path::{Path, PathBuf};
7use syn::spanned::Spanned;
8
9pub struct Build {
10    build: cc::Build,
11}
12
13impl Default for Build {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl Build {
20    pub fn new() -> Self {
21        let mut this = Self {
22            build: cc::Build::new(),
23        };
24        this.init();
25        this
26    }
27
28    /// 基于输入的`rust`文件自动生成`c++`适配代码, 并添加到待编译的`cpp`文件列表中.
29    ///
30    /// 注: 生成的文件在环境变量`$OUT_DIR`指定的目录下.
31    pub fn rust_file<P: AsRef<Path>>(&mut self, src: P) -> &mut Self {
32        let src = src.as_ref().as_os_str().to_string_lossy().into_owned();
33        let codes = self
34            .generate_cpp_codes(&src)
35            .unwrap_or_else(|e| self.map_err(e, &src));
36        self.write_cpp_file(codes, &src)
37            .unwrap_or_else(|e| panic!("write_cpp_file error: {e:?}"));
38        self
39    }
40
41    /// 基于输入的`rust`文件自动生成`c++`适配代码,并写入指定的头文件.
42    ///
43    /// 头文件并不会加入到待编译的`cpp`源文件列表. 使用时需要在其他文件中显式包含生成的头文件.
44    pub fn cpp_header<P1: AsRef<Path>, P2: AsRef<Path>>(&mut self, src: P1, hdr: P2) -> &mut Self {
45        let src = src.as_ref().as_os_str().to_string_lossy().into_owned();
46        let hdr = hdr.as_ref().as_os_str().to_string_lossy().into_owned();
47        let codes = self
48            .generate_cpp_codes(&src)
49            .unwrap_or_else(|e| self.map_err(e, &src));
50        self.write_cpp_header(&src, codes, &hdr)
51            .unwrap_or_else(|e| panic!("write_header error: {e:?}"));
52        self
53    }
54
55    fn generate_cpp_codes(&self, src: &str) -> syn::Result<String> {
56        let mut codes = String::new();
57        codes.push_str("#include <hicc/hicc.hpp>\n");
58        codes.push_str(&format!("#line 0 R\"({src})\"\n"));
59
60        let content =
61            fs::read_to_string(src).unwrap_or_else(|_| panic!("failed to read rust file `{src}`"));
62        let file: syn::File = syn::parse_str(&content)?;
63        for item in file.items.iter() {
64            let syn::Item::Macro(mac) = item else {
65                continue;
66            };
67            match hicc_macro(&mac.mac.path) {
68                HiccMacro::Class => {
69                    let c = syn::parse2::<ImportClass>(mac.mac.tokens.clone())?;
70                    codes.push_str(&ExportClasses::new(c).export()?)
71                }
72                HiccMacro::Lib => {
73                    let lib = syn::parse2::<ImportLib>(mac.mac.tokens.clone())?;
74                    let lib = ExportLib::try_from(lib)?;
75                    codes.push_str(&lib.export()?);
76                }
77                HiccMacro::Cpp => {
78                    let cpp = syn::parse2::<Cpp>(mac.mac.tokens.clone())?;
79                    codes.push_str(&cpp.export());
80                }
81                HiccMacro::Error => {
82                    return Err(syn::Error::new(mac.span(), "undefined macro!"));
83                }
84                _ => {}
85            }
86        }
87        Ok(codes)
88    }
89
90    fn map_err(&self, e: syn::Error, file: &str) -> ! {
91        let pos = e.span().start();
92        panic!("+++ {file} {}:{}\n{e:?}\n--- End", pos.line, pos.column);
93    }
94
95    fn write_cpp_header(&self, src: &str, codes: String, hdr: &str) -> Result<(), cc::Error> {
96        let hdr_macro = src
97            .replace(|c: char| !c.is_ascii_alphanumeric(), "_")
98            .to_ascii_uppercase();
99
100        if let Some(parent) = <str as AsRef<Path>>::as_ref(hdr).parent() {
101            let _ = fs::create_dir_all(parent);
102        }
103        let mut file = fs::File::create(hdr).map_err(cc::Error::from)?;
104        writeln!(file, "#ifndef {hdr_macro}").map_err(cc::Error::from)?;
105        writeln!(file, "#define {hdr_macro}\n").map_err(cc::Error::from)?;
106        writeln!(file, "{codes}").map_err(cc::Error::from)?;
107        writeln!(file, "#endif").map_err(cc::Error::from)?;
108        Ok(())
109    }
110
111    fn write_cpp_file(&mut self, codes: String, src: &str) -> Result<(), cc::Error> {
112        let Ok(out_dir) = env::var("OUT_DIR") else {
113            return Err(io::Error::new(io::ErrorKind::NotFound, "not found $OUT_DIR").into());
114        };
115        let mut dst = src.replace(['/', '\\'], "-");
116        dst.push_str(".cpp");
117        let path = Path::new(&out_dir).join(dst);
118        let mut file = fs::File::create(path.clone()).map_err(cc::Error::from)?;
119        writeln!(file, "{codes}").map_err(cc::Error::from)?;
120        self.file(path.as_path());
121        Ok(())
122    }
123
124    fn init(&mut self) {
125        if let Some(include) = env::var_os("DEP_HICC_INCLUDE") {
126            self.build.include(include);
127        }
128        if let Some(include) = env::var_os("DEP_HICC_STD_INCLUDE") {
129            self.build.include(include);
130        }
131        self.build.include(".");
132        self.build.cpp(true);
133    }
134}
135
136impl Deref for Build {
137    type Target = cc::Build;
138    fn deref(&self) -> &Self::Target {
139        &self.build
140    }
141}
142
143impl DerefMut for Build {
144    fn deref_mut(&mut self) -> &mut Self::Target {
145        &mut self.build
146    }
147}
148
149enum HiccMacro {
150    Class,
151    Lib,
152    Cpp,
153    Ignore,
154    Error,
155}
156
157fn hicc_macro(path: &syn::Path) -> HiccMacro {
158    match (
159        path.segments.get(0),
160        path.segments.get(1),
161        path.segments.get(2),
162    ) {
163        (Some(p1), Some(p2), None) if p1.ident == "hicc" && p2.ident == "import_class" => {
164            HiccMacro::Class
165        }
166        (Some(p1), None, None) if p1.ident == "import_class" => HiccMacro::Class,
167        (Some(p1), Some(p2), None) if p1.ident == "hicc" && p2.ident == "import_lib" => {
168            HiccMacro::Lib
169        }
170        (Some(p1), None, None) if p1.ident == "import_lib" => HiccMacro::Lib,
171        (Some(p1), Some(p2), None) if p1.ident == "hicc" && p2.ident == "cpp" => HiccMacro::Cpp,
172        (Some(p1), None, None) if p1.ident == "cpp" => HiccMacro::Cpp,
173        (Some(p1), _, _) if p1.ident == "hicc" => HiccMacro::Error,
174        _ => HiccMacro::Ignore,
175    }
176}
177
178/// 规范化路径,在Windows平台上去除扩展长度路径语法(\\?\前缀)
179/// 对于其他平台,原样返回路径
180/// 参考文档: <https://doc.rust-lang.org/std/fs/fn.canonicalize.html#platform-specific-behavior>
181pub fn normalize_windows_path(path: &Path) -> PathBuf {
182    if cfg!(target_os = "windows") {
183        path.display()
184            .to_string()
185            .strip_prefix(r"\\?\")
186            .map(|s| Path::new(s).to_path_buf())
187            .unwrap_or_else(|| path.to_path_buf())
188    } else {
189        path.to_path_buf()
190    }
191}