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 crate::context::Context;
29use crate::generator::builtins::generate_builtin_class_files;
30use crate::generator::classes::generate_class_files;
31use crate::generator::extension_interface::generate_sys_interface_file;
32use crate::generator::native_structures::generate_native_structures_files;
33use crate::generator::utility_functions::generate_utilities_file;
34use crate::generator::{
35 generate_core_central_file, generate_core_mod_file, generate_sys_builtin_lifecycle_file,
36 generate_sys_builtin_methods_file, generate_sys_central_file, generate_sys_classes_file,
37 generate_sys_module_file, generate_sys_utilities_file,
38};
39use crate::models::domain::{ApiView, ExtensionApi};
40use crate::models::json::{load_extension_api, JsonExtensionApi};
41
42use proc_macro2::TokenStream;
43use std::path::{Path, PathBuf};
44
45pub type SubmitFn = dyn FnMut(PathBuf, TokenStream);
46
47#[cfg(not(feature = "codegen-full"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-full"))))]
48pub const IS_CODEGEN_FULL: bool = false;
49
50#[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
52pub const IS_CODEGEN_FULL: bool = true;
53
54fn write_file(path: &Path, contents: String) {
55 let dir = path.parent().unwrap();
56 let _ = std::fs::create_dir_all(dir);
57
58 std::fs::write(path, contents)
59 .unwrap_or_else(|e| panic!("failed to write code file to {};\n\t{}", path.display(), e));
60}
61
62#[cfg(not(feature = "codegen-rustfmt"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-rustfmt"))))]
63fn submit_fn(path: PathBuf, tokens: TokenStream) {
64 write_file(&path, formatter::format_tokens(tokens));
65}
66
67#[cfg(feature = "codegen-rustfmt")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-rustfmt")))]
68mod rustfmt {
69 use super::*;
70 use std::process::Command;
71 use std::sync::Mutex;
72
73 pub fn submit_fn(path: PathBuf, tokens: TokenStream) {
74 write_file(&path, tokens.to_string());
75 FILES_TO_RUSTFMT.lock().unwrap().push(path);
76 }
77
78 pub fn rustfmt_files() {
79 let out_files = FILES_TO_RUSTFMT.lock().unwrap();
80 println!("Format {} generated files...", out_files.len());
81
82 for files in out_files.chunks(20) {
83 let mut command = Command::new("rustfmt");
84 command.arg("--edition");
85 command.arg("2021");
86
87 for file in files {
88 command.arg(file);
89 }
90
91 let status = command.status().expect("failed to invoke rustfmt");
92 if !status.success() {
93 panic!("rustfmt failed on {:?}", command);
94 }
95 }
96
97 println!("Rustfmt completed successfully");
98 }
99
100 static FILES_TO_RUSTFMT: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
101}
102
103#[cfg(feature = "codegen-rustfmt")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-rustfmt")))]
104pub(crate) use rustfmt::*;
105
106pub fn generate_sys_files(
107 sys_gen_path: &Path,
108 h_path: &Path,
109 watch: &mut godot_bindings::StopWatch,
110) {
111 let json_api = load_extension_api(watch);
112
113 let mut ctx = Context::build_from_api(&json_api);
114 watch.record("build_context");
115
116 let api = ExtensionApi::from_json(&json_api, &mut ctx);
117 watch.record("map_domain_models");
118
119 generate_sys_central_file(&api, sys_gen_path, &mut submit_fn);
124 watch.record("generate_central_file");
125
126 generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn);
127 watch.record("generate_builtin_methods_file");
128
129 generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn);
130 watch.record("generate_builtin_lifecycle_file");
131
132 generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn);
133 generate_sys_utilities_file(&api, sys_gen_path, &mut submit_fn);
136 watch.record("generate_utilities_file");
137
138 let is_godot_4_0 = api.godot_version.major == 4 && api.godot_version.minor == 0;
139 generate_sys_interface_file(h_path, sys_gen_path, is_godot_4_0, &mut submit_fn);
140 watch.record("generate_interface_file");
141
142 generate_sys_module_file(sys_gen_path, &mut submit_fn);
143 watch.record("generate_module_file");
144
145 #[cfg(feature = "codegen-rustfmt")]
146 {
147 rustfmt_files();
148 watch.record("rustfmt");
149 }
150}
151
152pub fn generate_core_files(core_gen_path: &Path) {
153 let mut watch = godot_bindings::StopWatch::start();
154
155 generate_core_mod_file(core_gen_path, &mut submit_fn);
156
157 let json_api = load_extension_api(&mut watch);
158 let mut ctx = Context::build_from_api(&json_api);
159 watch.record("build_context");
160
161 let api = ExtensionApi::from_json(&json_api, &mut ctx);
162 let view = ApiView::new(&api);
163 watch.record("map_domain_models");
164
165 generate_core_central_file(&api, &mut ctx, core_gen_path, &mut submit_fn);
170 watch.record("generate_central_file");
171
172 generate_utilities_file(&api, core_gen_path, &mut submit_fn);
173 watch.record("generate_utilities_file");
174
175 generate_class_files(
178 &api,
179 &mut ctx,
180 &view,
181 &core_gen_path.join("classes"),
182 &mut submit_fn,
183 );
184 watch.record("generate_class_files");
185
186 generate_builtin_class_files(
187 &api,
188 &mut ctx,
189 &core_gen_path.join("builtin_classes"),
190 &mut submit_fn,
191 );
192 watch.record("generate_builtin_class_files");
193
194 generate_native_structures_files(
195 &api,
196 &mut ctx,
197 &core_gen_path.join("native"),
198 &mut submit_fn,
199 );
200 watch.record("generate_native_structures_files");
201
202 #[cfg(feature = "codegen-rustfmt")]
203 {
204 rustfmt_files();
205 watch.record("rustfmt");
206 }
207
208 watch.write_stats_to(&core_gen_path.join("codegen-stats.txt"));
209}