1#![cfg_attr(published_docs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
16
17mod context;
18mod conv;
19mod formatter;
20mod generator;
21mod models;
22mod special_cases;
23mod util;
24
25#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
26mod tests;
27
28use std::path::{Path, PathBuf};
29
30use proc_macro2::TokenStream;
31
32use crate::context::Context;
33use crate::generator::builtins::generate_builtin_class_files;
34use crate::generator::classes::generate_class_files;
35use crate::generator::native_structures::generate_native_structures_files;
36use crate::generator::utility_functions::generate_utilities_file;
37use crate::generator::{
38 generate_core_central_file, generate_core_mod_file, generate_sys_builtin_lifecycle_file,
39 generate_sys_builtin_methods_file, generate_sys_central_file, generate_sys_classes_file,
40 generate_sys_gdextension_interface_file, generate_sys_module_file, generate_sys_utilities_file,
41 virtual_definitions,
42};
43use crate::models::api_json::{JsonExtensionApi, load_extension_api};
44use crate::models::domain::{ApiView, ExtensionApi};
45use crate::models::header_json::HeaderJson;
46
47pub type SubmitFn = dyn FnMut(PathBuf, TokenStream);
48
49#[cfg(not(feature = "codegen-full"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-full"))))]
50pub const IS_CODEGEN_FULL: bool = false;
51
52#[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
54pub const IS_CODEGEN_FULL: bool = true;
55
56fn write_file(path: &Path, contents: String) {
57 let dir = path.parent().unwrap();
58 let _ = std::fs::create_dir_all(dir);
59
60 std::fs::write(path, contents)
61 .unwrap_or_else(|e| panic!("failed to write code file to {};\n\t{}", path.display(), e));
62}
63
64#[cfg(not(feature = "codegen-rustfmt"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-rustfmt"))))]
65fn submit_fn(path: PathBuf, tokens: TokenStream) {
66 write_file(&path, formatter::format_tokens(tokens));
67}
68
69#[cfg(feature = "codegen-rustfmt")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-rustfmt")))]
70mod rustfmt {
71 use std::process::Command;
72 use std::sync::Mutex;
73
74 use super::*;
75
76 pub fn submit_fn(path: PathBuf, tokens: TokenStream) {
77 write_file(&path, tokens.to_string());
78 FILES_TO_RUSTFMT.lock().unwrap().push(path);
79 }
80
81 pub fn rustfmt_files() {
82 let out_files = FILES_TO_RUSTFMT.lock().unwrap();
83 println!("Format {} generated files...", out_files.len());
84
85 for files in out_files.chunks(20) {
86 let mut command = Command::new("rustfmt");
87 command.arg("--edition");
88 command.arg("2024");
89
90 for file in files {
91 command.arg(file);
92 }
93
94 let status = command.status().expect("failed to invoke rustfmt");
95 if !status.success() {
96 panic!("rustfmt failed on {command:?}");
97 }
98 }
99
100 println!("Rustfmt completed successfully");
101 }
102
103 static FILES_TO_RUSTFMT: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
104}
105
106#[cfg(feature = "codegen-rustfmt")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-rustfmt")))]
107pub(crate) use rustfmt::*;
108
109pub fn generate_sys_files(sys_gen_path: &Path, watch: &mut godot_bindings::StopWatch) {
110 let json_api = load_extension_api(watch);
111
112 let mut ctx = Context::build_from_api(&json_api);
113 watch.record("build_context");
114
115 let api = ExtensionApi::from_json(json_api, &mut ctx);
116 watch.record("map_domain_models");
117
118 generate_sys_central_file(&api, sys_gen_path, &mut submit_fn);
119 watch.record("generate_central_file");
120
121 generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn);
122 watch.record("generate_builtin_methods_file");
123
124 generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn);
125 watch.record("generate_builtin_lifecycle_file");
126
127 generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn);
128 generate_sys_utilities_file(&api, sys_gen_path, &mut submit_fn);
131 watch.record("generate_utilities_file");
132
133 let header_json = load_header_json(watch);
134 watch.record("load_header_json");
135 generate_sys_gdextension_interface_file(&header_json, sys_gen_path, &mut submit_fn);
136 watch.record("generate_gdextension_interface_file");
137
138 generate_sys_module_file(sys_gen_path, &mut submit_fn);
139 watch.record("generate_module_file");
140
141 #[cfg(feature = "codegen-rustfmt")]
142 {
143 rustfmt_files();
144 watch.record("rustfmt");
145 }
146}
147
148fn load_header_json(watch: &mut godot_bindings::StopWatch) -> HeaderJson {
149 let json_str = godot_bindings::load_gdextension_interface_json(watch);
150
151 nanoserde::DeJson::deserialize_json(json_str.as_ref())
152 .unwrap_or_else(|e| panic!("failed to deserialize gdextension_interface.json;\n\t{e}"))
153}
154
155pub fn generate_core_files(core_gen_path: &Path) {
156 let mut watch = godot_bindings::StopWatch::start();
157
158 generate_core_mod_file(core_gen_path, &mut submit_fn);
159
160 let json_api = load_extension_api(&mut watch);
161 let mut ctx = Context::build_from_api(&json_api);
162 watch.record("build_context");
163
164 let api = ExtensionApi::from_json(json_api, &mut ctx);
165 let view = ApiView::new(&api);
166 watch.record("map_domain_models");
167
168 generate_class_files(
171 &api,
172 &mut ctx,
173 &view,
174 &core_gen_path.join("classes"),
175 &mut submit_fn,
176 );
177 watch.record("generate_class_files");
178
179 generate_builtin_class_files(
180 &api,
181 &mut ctx,
182 &view,
183 &core_gen_path.join("builtin_classes"),
184 &mut submit_fn,
185 );
186 watch.record("generate_builtin_class_files");
187
188 generate_native_structures_files(
189 &api,
190 &mut ctx,
191 &core_gen_path.join("native"),
192 &mut submit_fn,
193 );
194 watch.record("generate_native_structures_files");
195
196 generate_core_central_file(&api, &mut ctx, core_gen_path, &mut submit_fn);
200 watch.record("generate_central_file");
201
202 generate_utilities_file(&api, &ctx, &view, core_gen_path, &mut submit_fn);
203 watch.record("generate_utilities_file");
204
205 let code = virtual_definitions::make_virtual_definitions_file(&api, &mut ctx);
209 submit_fn(core_gen_path.join("virtuals.rs"), code);
210 watch.record("generate_virtual_definitions");
211
212 #[cfg(feature = "codegen-rustfmt")]
213 {
214 rustfmt_files();
215 watch.record("rustfmt");
216 }
217
218 watch.write_stats_to(&core_gen_path.join("codegen-stats.txt"));
219}