godot_codegen/
lib.rs

1#![cfg_attr(published_docs, feature(doc_cfg))]
2/*
3 * Copyright (c) godot-rust; Bromeon and contributors.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9//! # Internal crate of [**godot-rust**](https://godot-rust.github.io)
10//!
11//! Do not depend on this crate directly, instead use the `godot` crate.
12//! No SemVer or other guarantees are provided.
13
14// Codegen has no FFI and thus no reason to use unsafe code.
15#![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/// Used by itest to determine true codegen status; see itest/build.rs.
51#[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    // TODO if ctx is no longer needed for below functions:
120    // Deallocate all the JSON models; no longer needed for codegen.
121    // drop(json_api);
122
123    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    // watch records inside the function.
134
135    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    // TODO if ctx is no longer needed for below functions:
166    // Deallocate all the JSON models; no longer needed for codegen.
167    // drop(json_api);
168
169    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    // Class files -- currently output in godot-core; could maybe be separated cleaner
176    // Note: deletes entire generated directory!
177    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}