godot_codegen/
lib.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8//! # Internal crate of [**godot-rust**](https://godot-rust.github.io)
9//!
10//! Do not depend on this crate directly, instead use the `godot` crate.
11//! No SemVer or other guarantees are provided.
12
13// Codegen has no FFI and thus no reason to use unsafe code.
14#![forbid(unsafe_code)]
15
16mod context;
17mod conv;
18mod formatter;
19mod generator;
20mod models;
21mod special_cases;
22mod util;
23
24#[cfg(test)]
25mod tests;
26
27use crate::context::Context;
28use crate::generator::builtins::generate_builtin_class_files;
29use crate::generator::classes::generate_class_files;
30use crate::generator::extension_interface::generate_sys_interface_file;
31use crate::generator::native_structures::generate_native_structures_files;
32use crate::generator::utility_functions::generate_utilities_file;
33use crate::generator::{
34    generate_core_central_file, generate_core_mod_file, generate_sys_builtin_lifecycle_file,
35    generate_sys_builtin_methods_file, generate_sys_central_file, generate_sys_classes_file,
36    generate_sys_module_file, generate_sys_utilities_file,
37};
38use crate::models::domain::{ApiView, ExtensionApi};
39use crate::models::json::{load_extension_api, JsonExtensionApi};
40
41use proc_macro2::TokenStream;
42use std::path::{Path, PathBuf};
43
44pub type SubmitFn = dyn FnMut(PathBuf, TokenStream);
45
46fn write_file(path: &Path, contents: String) {
47    let dir = path.parent().unwrap();
48    let _ = std::fs::create_dir_all(dir);
49
50    std::fs::write(path, contents)
51        .unwrap_or_else(|e| panic!("failed to write code file to {};\n\t{}", path.display(), e));
52}
53
54#[cfg(not(feature = "codegen-rustfmt"))]
55fn submit_fn(path: PathBuf, tokens: TokenStream) {
56    write_file(&path, formatter::format_tokens(tokens));
57}
58
59#[cfg(feature = "codegen-rustfmt")]
60mod rustfmt {
61    use super::*;
62    use std::process::Command;
63    use std::sync::Mutex;
64
65    pub fn submit_fn(path: PathBuf, tokens: TokenStream) {
66        write_file(&path, tokens.to_string());
67        FILES_TO_RUSTFMT.lock().unwrap().push(path);
68    }
69
70    pub fn rustfmt_files() {
71        let out_files = FILES_TO_RUSTFMT.lock().unwrap();
72        println!("Format {} generated files...", out_files.len());
73
74        for files in out_files.chunks(20) {
75            let mut command = Command::new("rustfmt");
76            command.arg("--edition");
77            command.arg("2021");
78
79            for file in files {
80                command.arg(file);
81            }
82
83            let status = command.status().expect("failed to invoke rustfmt");
84            if !status.success() {
85                panic!("rustfmt failed on {:?}", command);
86            }
87        }
88
89        println!("Rustfmt completed successfully");
90    }
91
92    static FILES_TO_RUSTFMT: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
93}
94
95#[cfg(feature = "codegen-rustfmt")]
96pub(crate) use rustfmt::*;
97
98pub fn generate_sys_files(
99    sys_gen_path: &Path,
100    h_path: &Path,
101    watch: &mut godot_bindings::StopWatch,
102) {
103    let json_api = load_extension_api(watch);
104
105    let mut ctx = Context::build_from_api(&json_api);
106    watch.record("build_context");
107
108    let api = ExtensionApi::from_json(&json_api, &mut ctx);
109    watch.record("map_domain_models");
110
111    // TODO if ctx is no longer needed for below functions:
112    // Deallocate all the JSON models; no longer needed for codegen.
113    // drop(json_api);
114
115    generate_sys_central_file(&api, sys_gen_path, &mut submit_fn);
116    watch.record("generate_central_file");
117
118    generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn);
119    watch.record("generate_builtin_methods_file");
120
121    generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn);
122    watch.record("generate_builtin_lifecycle_file");
123
124    generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn);
125    // watch records inside the function.
126
127    generate_sys_utilities_file(&api, sys_gen_path, &mut submit_fn);
128    watch.record("generate_utilities_file");
129
130    let is_godot_4_0 = api.godot_version.major == 4 && api.godot_version.minor == 0;
131    generate_sys_interface_file(h_path, sys_gen_path, is_godot_4_0, &mut submit_fn);
132    watch.record("generate_interface_file");
133
134    generate_sys_module_file(sys_gen_path, &mut submit_fn);
135    watch.record("generate_module_file");
136
137    #[cfg(feature = "codegen-rustfmt")]
138    {
139        rustfmt_files();
140        watch.record("rustfmt");
141    }
142}
143
144pub fn generate_core_files(core_gen_path: &Path) {
145    let mut watch = godot_bindings::StopWatch::start();
146
147    generate_core_mod_file(core_gen_path, &mut submit_fn);
148
149    let json_api = load_extension_api(&mut watch);
150    let mut ctx = Context::build_from_api(&json_api);
151    watch.record("build_context");
152
153    let api = ExtensionApi::from_json(&json_api, &mut ctx);
154    let view = ApiView::new(&api);
155    watch.record("map_domain_models");
156
157    // TODO if ctx is no longer needed for below functions:
158    // Deallocate all the JSON models; no longer needed for codegen.
159    // drop(json_api);
160
161    generate_core_central_file(&api, &mut ctx, core_gen_path, &mut submit_fn);
162    watch.record("generate_central_file");
163
164    generate_utilities_file(&api, core_gen_path, &mut submit_fn);
165    watch.record("generate_utilities_file");
166
167    // Class files -- currently output in godot-core; could maybe be separated cleaner
168    // Note: deletes entire generated directory!
169    generate_class_files(
170        &api,
171        &mut ctx,
172        &view,
173        &core_gen_path.join("classes"),
174        &mut submit_fn,
175    );
176    watch.record("generate_class_files");
177
178    generate_builtin_class_files(
179        &api,
180        &mut ctx,
181        &core_gen_path.join("builtin_classes"),
182        &mut submit_fn,
183    );
184    watch.record("generate_builtin_class_files");
185
186    generate_native_structures_files(
187        &api,
188        &mut ctx,
189        &core_gen_path.join("native"),
190        &mut submit_fn,
191    );
192    watch.record("generate_native_structures_files");
193
194    #[cfg(feature = "codegen-rustfmt")]
195    {
196        rustfmt_files();
197        watch.record("rustfmt");
198    }
199
200    watch.write_stats_to(&core_gen_path.join("codegen-stats.txt"));
201}