rusteal_codegen/rust_gen/
cargo_toml.rs1use std::collections::{BTreeMap, BTreeSet};
2use std::path::Path;
3
4use crate::config::CodegenConfig;
5use crate::context::CodegenContext;
6
7const README: &str = "\
8# bindings
9
10Rust bindings for the Unreal Engine modules this project uses, generated by
11`rusteal build --all` from the engine's reflection data, together with the C++
12wrappers in `Plugins/Rusteal/Source/Rusteal/Generated/`.
13
14Do not edit: every file here is overwritten on the next run. Regenerate after
15changing the engine version, the enabled modules or the blocklist in
16`rusteal.toml`, and commit the result — the game crate depends on it.
17";
18
19pub fn write_crate_files(crate_dir: &Path, ctx: &CodegenContext, config: &CodegenConfig) {
20 let manifest = format!(
21 "# Generated by rusteal-codegen. Do not edit.\n\
22 \n\
23 [package]\n\
24 name = \"bindings\"\n\
25 version = \"0.1.0\"\n\
26 edition = \"2024\"\n\
27 publish = false\n\
28 \n\
29 [dependencies]\n\
30 rusteal-core = {{ workspace = true }}\n\
31 rusteal-ffi = {{ workspace = true }}\n\
32 glam = {{ workspace = true }}\n\
33 \n\
34 {features}\
35 \n\
36 {lints}",
37 features = render_features_section(ctx, config),
38 lints = render_lints_section(),
39 );
40
41 let manifest_path = crate_dir.join("Cargo.toml");
42
43 crate::write_if_changed(&manifest_path, &manifest)
44 .unwrap_or_else(|e| panic!("Failed to write {}: {e}", manifest_path.display()));
45
46 let readme_path = crate_dir.join("README.md");
47
48 crate::write_if_changed(&readme_path, README)
49 .unwrap_or_else(|e| panic!("Failed to write {}: {e}", readme_path.display()));
50
51 eprintln!(" Cargo.toml: {} features", config.features.len());
52}
53
54fn render_features_section(ctx: &CodegenContext, config: &CodegenConfig) -> String {
55 let mut feature_to_modules: BTreeMap<String, Vec<String>> = BTreeMap::new();
56
57 for mapping in config.modules.values() {
58 feature_to_modules
59 .entry(mapping.feature.clone())
60 .or_default()
61 .push(mapping.module.clone());
62 }
63
64 let generated: BTreeSet<String> = config.features.iter().cloned().collect();
65
66 let mut out = String::from("[features]\n");
67
68 out.push_str(&format!(
69 "default = {}\n",
70 format_dep_array(&generated.iter().cloned().collect::<Vec<_>>())
71 ));
72
73 for feature in &generated {
74 let mut dep_features: BTreeSet<String> = BTreeSet::new();
75
76 if let Some(modules) = feature_to_modules.get(feature) {
77 for module in modules {
78 if let Some(module_deps) = ctx.module_deps.get(module) {
79 for dep_module in module_deps {
80 if let Some(dep_feature) = ctx.feature_for_module(dep_module)
81 && dep_feature != feature
82 {
83 dep_features.insert(dep_feature.to_string());
84 }
85 }
86 }
87 }
88 }
89
90 out.push_str(&format!(
91 "{feature} = {}\n",
92 format_dep_array(&dep_features.iter().cloned().collect::<Vec<_>>())
93 ));
94 }
95
96 out
97}
98
99fn render_lints_section() -> String {
100 let features: Vec<String> = super::manual::gating_features()
101 .into_iter()
102 .map(|f| format!("\"{f}\""))
103 .collect();
104
105 format!(
106 "[lints.rust]\n\
107 unexpected_cfgs = {{ level = \"warn\", check-cfg = ['cfg(feature, values({}))'] }}\n",
108 features.join(", ")
109 )
110}
111
112fn format_dep_array(deps: &[String]) -> String {
113 if deps.is_empty() {
114 "[]".to_string()
115 } else {
116 let items: Vec<String> = deps.iter().map(|d| format!("\"{d}\"")).collect();
117
118 format!("[{}]", items.join(", "))
119 }
120}