Skip to main content

rusteal_codegen/rust_gen/
cargo_toml.rs

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