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