use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use crate::config::CodegenConfig;
use crate::context::CodegenContext;
const README: &str = "\
# bindings
Rust bindings for the Unreal Engine modules this project uses, generated by
`rusteal build --all` from the engine's reflection data, together with the C++
wrappers in `Plugins/Rusteal/Source/Rusteal/Generated/`.
Do not edit: every file here is overwritten on the next run. Regenerate after
changing the engine version, the enabled modules or the blocklist in
`rusteal.toml`, and commit the result — the game crate depends on it.
";
pub fn write_crate_files(crate_dir: &Path, ctx: &CodegenContext, config: &CodegenConfig) {
let manifest = format!(
"# Generated by rusteal-codegen. Do not edit.\n\
\n\
[package]\n\
name = \"bindings\"\n\
version = \"0.1.0\"\n\
edition = \"2024\"\n\
publish = false\n\
\n\
[dependencies]\n\
rusteal-core = {{ workspace = true }}\n\
rusteal-ffi = {{ workspace = true }}\n\
glam = {{ workspace = true }}\n\
\n\
{features}\
\n\
{lints}",
features = render_features_section(ctx, config),
lints = render_lints_section(),
);
let manifest_path = crate_dir.join("Cargo.toml");
crate::write_if_changed(&manifest_path, &manifest)
.unwrap_or_else(|e| panic!("Failed to write {}: {e}", manifest_path.display()));
let readme_path = crate_dir.join("README.md");
crate::write_if_changed(&readme_path, README)
.unwrap_or_else(|e| panic!("Failed to write {}: {e}", readme_path.display()));
eprintln!(" Cargo.toml: {} features", config.features.len());
}
fn render_features_section(ctx: &CodegenContext, config: &CodegenConfig) -> String {
let mut feature_to_modules: BTreeMap<String, Vec<String>> = BTreeMap::new();
for mapping in config.modules.values() {
feature_to_modules
.entry(mapping.feature.clone())
.or_default()
.push(mapping.module.clone());
}
let generated: BTreeSet<String> = config.features.iter().cloned().collect();
let mut out = String::from("[features]\n");
out.push_str(&format!(
"default = {}\n",
format_dep_array(&generated.iter().cloned().collect::<Vec<_>>())
));
for feature in &generated {
let mut dep_features: BTreeSet<String> = BTreeSet::new();
if let Some(modules) = feature_to_modules.get(feature) {
for module in modules {
if let Some(module_deps) = ctx.module_deps.get(module) {
for dep_module in module_deps {
if let Some(dep_feature) = ctx.feature_for_module(dep_module)
&& dep_feature != feature
{
dep_features.insert(dep_feature.to_string());
}
}
}
}
}
out.push_str(&format!(
"{feature} = {}\n",
format_dep_array(&dep_features.iter().cloned().collect::<Vec<_>>())
));
}
out
}
fn render_lints_section() -> String {
let features: Vec<String> = super::manual::gating_features()
.into_iter()
.map(|f| format!("\"{f}\""))
.collect();
format!(
"[lints.rust]\n\
unexpected_cfgs = {{ level = \"warn\", check-cfg = ['cfg(feature, values({}))'] }}\n",
features.join(", ")
)
}
fn format_dep_array(deps: &[String]) -> String {
if deps.is_empty() {
"[]".to_string()
} else {
let items: Vec<String> = deps.iter().map(|d| format!("\"{d}\"")).collect();
format!("[{}]", items.join(", "))
}
}