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 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 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
178pub 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}