Skip to main content

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 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/// Used by itest to determine true codegen status; see itest/build.rs.
53#[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    // watch records inside the function.
129
130    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    // Class files -- currently output in godot-core; could maybe be separated more cleanly.
169    // Note: deletes entire generated directory!
170    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    // Note – generated at the very end since context could be updated while processing other files.
197    // For example – SysPointerTypes (ones defined in gdextension_interface) are declared only
198    // as parameters for various APIs.
199    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    // From 4.4 onward, generate table that maps all virtual methods to their known hashes.
206    // This allows Godot to fall back to an older compatibility function if one is not supported.
207    // Also expose tuple signatures of virtual methods.
208    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}