1pub mod schema;
4pub mod naming;
5pub mod config;
6pub mod context;
7pub mod type_map;
8pub mod defaults;
9pub mod filter;
10pub mod rust_gen;
11pub mod cpp_gen;
12
13use std::path::{Path, PathBuf};
14
15use crate::config::UikaConfig;
16use crate::schema::{ClassesFile, EnumsFile, StructsFile};
17
18pub fn run_generate(config_path: &Path) {
20 let config_str = std::fs::read_to_string(config_path)
22 .unwrap_or_else(|e| panic!("Failed to read {}: {e}", config_path.display()));
23 let uika_config: UikaConfig = toml::from_str(&config_str)
24 .unwrap_or_else(|e| panic!("Failed to parse {}: {e}", config_path.display()));
25 let codegen = &uika_config.codegen;
26
27 let uht_input = PathBuf::from(&codegen.paths.uht_input);
29 let classes_path = uht_input.join("uika_classes.json");
30 let structs_path = uht_input.join("uika_structs.json");
31 let enums_path = uht_input.join("uika_enums.json");
32 let rust_out = PathBuf::from(&codegen.paths.rust_out);
33 let cpp_out = PathBuf::from(&codegen.paths.cpp_out);
34
35 eprintln!("uika-codegen: loading JSON...");
36
37 let classes_json: ClassesFile = {
39 let data = std::fs::read_to_string(&classes_path)
40 .unwrap_or_else(|e| panic!("Failed to read {}: {e}", classes_path.display()));
41 serde_json::from_str(&data)
42 .unwrap_or_else(|e| panic!("Failed to parse {}: {e}", classes_path.display()))
43 };
44
45 let structs_json: StructsFile = {
46 let data = std::fs::read_to_string(&structs_path)
47 .unwrap_or_else(|e| panic!("Failed to read {}: {e}", structs_path.display()));
48 serde_json::from_str(&data)
49 .unwrap_or_else(|e| panic!("Failed to parse {}: {e}", structs_path.display()))
50 };
51
52 let enums_json: EnumsFile = {
53 let data = std::fs::read_to_string(&enums_path)
54 .unwrap_or_else(|e| panic!("Failed to read {}: {e}", enums_path.display()));
55 serde_json::from_str(&data)
56 .unwrap_or_else(|e| panic!("Failed to parse {}: {e}", enums_path.display()))
57 };
58
59 eprintln!(
60 " Loaded {} classes, {} structs, {} enums",
61 classes_json.classes.len(),
62 structs_json.structs.len(),
63 enums_json.enums.len()
64 );
65
66 let mut ctx = context::CodegenContext::new(
68 classes_json.classes,
69 structs_json.structs,
70 enums_json.enums,
71 codegen,
72 );
73
74 eprintln!(
75 " Enabled modules: {:?}",
76 ctx.enabled_modules.iter().collect::<Vec<_>>()
77 );
78 for (module, classes) in &ctx.module_classes {
79 eprintln!(" {}: {} classes", module, classes.len());
80 }
81 for (module, structs) in &ctx.module_structs {
82 eprintln!(" {}: {} structs", module, structs.len());
83 }
84 for (module, enums) in &ctx.module_enums {
85 eprintln!(" {}: {} enums", module, enums.len());
86 }
87
88 eprintln!("uika-codegen: filtering...");
90 filter::apply_filters(&mut ctx, &codegen.blocklist);
91
92 eprintln!("uika-codegen: building function table...");
94 build_func_table(&mut ctx);
95 eprintln!(" {} functions in func_table", ctx.func_table.len());
96
97 eprintln!("uika-codegen: generating Rust code...");
99 rust_gen::generate(&ctx, &rust_out);
100
101 eprintln!("uika-codegen: generating C++ code...");
103 cpp_gen::generate(&ctx, &cpp_out);
104
105 generate_module_deps(codegen, &cpp_out);
107
108 eprintln!("uika-codegen: verifying output...");
110 verify_output(&ctx, &rust_out, &cpp_out);
111
112 eprintln!("uika-codegen: done!");
113}
114
115fn generate_module_deps(config: &crate::config::CodegenConfig, cpp_out: &Path) {
117 use std::collections::BTreeSet;
118
119 let enabled_features: std::collections::HashSet<&str> =
120 config.features.iter().map(|s| s.as_str()).collect();
121
122 let mut ue_modules: BTreeSet<&str> = BTreeSet::new();
125 ue_modules.insert("Core");
126 for (pkg, mapping) in &config.modules {
127 if enabled_features.contains(mapping.feature.as_str()) {
128 ue_modules.insert(pkg.as_str());
129 }
130 }
131
132 let content = ue_modules
133 .iter()
134 .map(|m| *m)
135 .collect::<Vec<_>>()
136 .join("\n");
137
138 let path = cpp_out.join("module_deps.txt");
139 std::fs::write(&path, &content)
140 .unwrap_or_else(|e| panic!("Failed to write {}: {e}", path.display()));
141
142 eprintln!(" module_deps.txt: {:?}", ue_modules.iter().collect::<Vec<_>>());
143}
144
145fn verify_output(ctx: &context::CodegenContext, rust_out: &Path, cpp_out: &Path) {
147 let mut errors: Vec<String> = Vec::new();
148
149 for (i, entry) in ctx.func_table.iter().enumerate() {
151 if entry.func_id != i as u32 {
152 errors.push(format!(
153 "FuncId gap: expected {} for {}.{}, got {}",
154 i, entry.class_name, entry.func_name, entry.func_id
155 ));
156 break; }
158 }
159
160 let rust_required = ["lib.rs", "func_ids.rs"];
162 for name in &rust_required {
163 let path = rust_out.join(name);
164 match std::fs::metadata(&path) {
165 Ok(m) if m.len() == 0 => errors.push(format!("Rust output empty: {}", path.display())),
166 Err(_) => errors.push(format!("Rust output missing: {}", path.display())),
167 _ => {}
168 }
169 }
170
171 for module_name in ctx.enabled_modules.iter() {
173 let mod_path = rust_out.join(module_name).join("mod.rs");
174 if !mod_path.exists() {
175 errors.push(format!("Module mod.rs missing: {}", mod_path.display()));
176 }
177 }
178
179 let cpp_required = ["UikaFuncIds.h", "UikaFillFuncTable.cpp"];
181 for name in &cpp_required {
182 let path = cpp_out.join(name);
183 match std::fs::metadata(&path) {
184 Ok(m) if m.len() == 0 => errors.push(format!("C++ output empty: {}", path.display())),
185 Err(_) => errors.push(format!("C++ output missing: {}", path.display())),
186 _ => {}
187 }
188 }
189
190 let func_count = ctx.func_table.len();
192 let module_count = ctx.enabled_modules.len();
193 let class_count: usize = ctx.module_classes.values().map(|v| v.len()).sum();
194 let struct_count: usize = ctx.module_structs.values().map(|v| v.len()).sum();
195 let enum_count: usize = ctx.module_enums.values().map(|v| v.len()).sum();
196
197 if errors.is_empty() {
198 eprintln!(
199 " OK: {} modules, {} classes, {} structs, {} enums, {} functions",
200 module_count, class_count, struct_count, enum_count, func_count
201 );
202 } else {
203 eprintln!(" Verification FAILED:");
204 for e in &errors {
205 eprintln!(" - {e}");
206 }
207 std::process::exit(1);
208 }
209}
210
211fn build_func_table(ctx: &mut context::CodegenContext) {
213 let mut entries = Vec::new();
214
215 for (module_name, classes) in &ctx.module_classes {
216 for class in classes {
217 if class.super_class.as_deref() == Some("Interface") {
219 continue;
220 }
221 for func in &class.funcs {
222 let all_supported = func.params.iter().all(|p| {
224 type_map::map_property_type(
225 &p.prop_type,
226 p.class_name.as_deref(),
227 p.struct_name.as_deref(),
228 p.enum_name.as_deref(),
229 p.enum_underlying_type.as_deref(),
230 p.meta_class_name.as_deref(),
231 p.interface_name.as_deref(),
232 )
233 .supported
234 });
235 if !all_supported {
236 continue;
237 }
238 entries.push(context::FuncEntry {
239 func_id: 0, module_name: module_name.clone(),
241 class_name: class.name.clone(),
242 func_name: func.name.clone(),
243 rust_func_name: naming::to_snake_case(&func.name),
244 func: func.clone(),
245 cpp_class_name: class.cpp_name.clone(),
246 header: class.header.clone(),
247 });
248 }
249 }
250 }
251
252 entries.sort_by(|a, b| {
254 a.module_name
255 .cmp(&b.module_name)
256 .then_with(|| a.class_name.cmp(&b.class_name))
257 .then_with(|| a.func_name.cmp(&b.func_name))
258 });
259
260 for (i, entry) in entries.iter_mut().enumerate() {
262 entry.func_id = i as u32;
263 }
264
265 ctx.func_table = entries;
266}