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::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/// Used by itest to determine true codegen status; see itest/build.rs.
52#[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    // TODO if ctx is no longer needed for below functions:
122    // Deallocate all the JSON models; no longer needed for codegen.
123    // drop(json_api);
124
125    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    // watch records inside the function.
136
137    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    // TODO if ctx is no longer needed for below functions:
168    // Deallocate all the JSON models; no longer needed for codegen.
169    // drop(json_api);
170
171    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    // Class files -- currently output in godot-core; could maybe be separated cleaner
178    // Note: deletes entire generated directory!
179    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}