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         \n\
44         {lints}",
45        features = render_features_section(ctx, config),
46        lints = render_lints_section(),
47    );
48
49    let manifest_path = crate_dir.join("Cargo.toml");
50    std::fs::write(&manifest_path, manifest)
51        .unwrap_or_else(|e| panic!("Failed to write {}: {e}", manifest_path.display()));
52
53    let readme_path = crate_dir.join("README.md");
54    std::fs::write(&readme_path, README)
55        .unwrap_or_else(|e| panic!("Failed to write {}: {e}", readme_path.display()));
56
57    eprintln!("  Cargo.toml: {} features", config.features.len());
58}
59
60/// Render the `[features]` table: one entry per feature generated in this run,
61/// with dependencies from the cross-module reference graph, and `default`
62/// turning all of them on.
63fn render_features_section(ctx: &CodegenContext, config: &CodegenConfig) -> String {
64    // Reverse map: feature name → modules. Some features (e.g. "slate")
65    // map from multiple modules (SlateCore + Slate); we union their deps.
66    let mut feature_to_modules: BTreeMap<String, Vec<String>> = BTreeMap::new();
67    for mapping in config.modules.values() {
68        feature_to_modules
69            .entry(mapping.feature.clone())
70            .or_default()
71            .push(mapping.module.clone());
72    }
73
74    let generated: BTreeSet<String> = config.features.iter().cloned().collect();
75
76    let mut out = String::from("[features]\n");
77    out.push_str(&format!(
78        "default = {}\n",
79        format_dep_array(&generated.iter().cloned().collect::<Vec<_>>())
80    ));
81
82    for feature in &generated {
83        let mut dep_features: BTreeSet<String> = BTreeSet::new();
84        if let Some(modules) = feature_to_modules.get(feature) {
85            for module in modules {
86                if let Some(module_deps) = ctx.module_deps.get(module) {
87                    for dep_module in module_deps {
88                        if let Some(dep_feature) = ctx.feature_for_module(dep_module)
89                            && dep_feature != feature
90                        {
91                            dep_features.insert(dep_feature.to_string());
92                        }
93                    }
94                }
95            }
96        }
97        out.push_str(&format!(
98            "{feature} = {}\n",
99            format_dep_array(&dep_features.iter().cloned().collect::<Vec<_>>())
100        ));
101    }
102
103    out
104}
105
106/// `manual/` is the same in every project and gates modules on features this
107/// project may not generate; they are declared so rustc does not warn.
108fn render_lints_section() -> String {
109    let features: Vec<String> = super::manual::gating_features()
110        .into_iter()
111        .map(|f| format!("\"{f}\""))
112        .collect();
113    format!(
114        "[lints.rust]\n\
115         unexpected_cfgs = {{ level = \"warn\", check-cfg = ['cfg(feature, values({}))'] }}\n",
116        features.join(", ")
117    )
118}
119
120fn format_dep_array(deps: &[String]) -> String {
121    if deps.is_empty() {
122        "[]".to_string()
123    } else {
124        let items: Vec<String> = deps.iter().map(|d| format!("\"{d}\"")).collect();
125        format!("[{}]", items.join(", "))
126    }
127}