use std::fs;
use std::path::{Path, PathBuf};
use rusteal_codegen::config::{find_plugins, find_uproject};
use crate::build_cmd::{self, HostPlatform};
pub fn run_package(root: &Path, engine: &Path, output: &Path, configuration: &str) {
eprintln!("rusteal package: the libraries, release profile");
build_cmd::run_build(root, engine, None, 1, true, None);
let uproject = find_uproject(root).unwrap_or_else(|| fail(&format!("no .uproject in {}", root.display())));
let uproject = build_cmd::canonical_no_prefix(&uproject);
let platform = &HostPlatform::CURRENT;
let uat = engine.join(platform.uat_script);
if !uat.exists() {
fail(&format!("UAT not found at {}", uat.display()));
}
fs::create_dir_all(output).unwrap_or_else(|e| fail(&format!("cannot create {}: {e}", output.display())));
let output = build_cmd::canonical_no_prefix(output);
eprintln!("rusteal package: BuildCookRun, {configuration}, {}", platform.ubt_platform);
build_cmd::run_cmd(&[
uat.to_str().unwrap(),
"BuildCookRun",
&format!("-project={}", uproject.display()),
&format!("-platform={}", platform.ubt_platform),
&format!("-clientconfig={configuration}"),
"-build",
"-cook",
"-stage",
"-pak",
"-archive",
&format!("-archivedirectory={}", output.display()),
"-nop4",
"-unattended",
"-utf8output",
]);
eprintln!("\nrusteal package: done, the game is in {}", output.display());
}
pub fn run_plugin_package(root: &Path, engine: &Path, name: &str, output: &Path) {
let plugins = find_plugins(root);
let Some(plugin) = plugins.iter().find(|p| p.name.eq_ignore_ascii_case(name)) else {
let names: Vec<&str> = plugins.iter().map(|p| p.name.as_str()).collect();
fail(&format!(
"no Rusteal plugin '{name}' in {}/Plugins (Rusteal plugins: {}).",
root.display(),
if names.is_empty() { "none".to_string() } else { names.join(", ") }
));
};
eprintln!("rusteal plugin package: the {} library, release profile", plugin.name);
build_cmd::run_build(root, engine, None, 1, true, Some(&plugin.name));
let target = output.join(&plugin.name);
if target.exists() {
fs::remove_dir_all(&target)
.unwrap_or_else(|e| fail(&format!("cannot replace {}: {e}", target.display())));
}
let copied = copy_plugin(&plugin.dir, &target);
eprintln!("\nrusteal plugin package: done, {copied} files in {}", target.display());
eprintln!(
" Copy it into another Rusteal project's Plugins/ (the project must be at Rusteal {}),",
env!("CARGO_PKG_VERSION")
);
eprintln!(" list it in the .uproject and run `rusteal build --all` there.");
}
fn excluded(rel: &Path) -> bool {
let rel = rel.to_string_lossy().replace('\\', "/");
let first = rel.split('/').next().unwrap_or_default();
if matches!(first, "Intermediate" | "Saved" | "DerivedDataCache") {
return true;
}
if rel.starts_with("Rust/target/") || rel == "Rust/target" || rel.contains("/obj/") {
return true;
}
let file = rel.rsplit('/').next().unwrap_or_default();
rel.starts_with("Binaries/")
&& (file.contains("_hot_") || file.ends_with(".debug") || file.ends_with(".pdb"))
}
fn copy_plugin(from: &Path, to: &Path) -> usize {
let mut copied = 0;
let mut stack: Vec<PathBuf> = vec![from.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = fs::read_dir(&dir).unwrap_or_else(|e| fail(&format!("cannot read {}: {e}", dir.display())));
for entry in entries.flatten() {
let path = entry.path();
let rel = path.strip_prefix(from).unwrap();
if excluded(rel) {
continue;
}
if path.is_dir() {
stack.push(path);
continue;
}
let dest = to.join(rel);
fs::create_dir_all(dest.parent().unwrap())
.unwrap_or_else(|e| fail(&format!("cannot create {}: {e}", dest.display())));
fs::copy(&path, &dest)
.unwrap_or_else(|e| fail(&format!("cannot copy {}: {e}", path.display())));
copied += 1;
}
}
copied
}
fn fail(message: &str) -> ! {
eprintln!("Error: {message}");
std::process::exit(1);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packages_leave_build_output_out() {
for rel in [
"Intermediate/Build/x.o",
"Rust/target/release/libinventory.so",
"Binaries/Linux/librusteal_Inventory_hot_3.so",
"Binaries/Linux/libUnrealEditor-Inventory.debug",
"Source/Inventory/obj/x",
] {
assert!(excluded(Path::new(rel)), "{rel} kept");
}
for rel in [
"Inventory.uplugin",
"rusteal.toml",
"Source/Inventory/Generated/RustealFuncIds.h",
"Rust/bindings/src/lib.rs",
"Rust/inventory/src/lib.rs",
"Binaries/Linux/librusteal_Inventory.so",
"Binaries/Linux/libUnrealEditor-Inventory.so",
"Content/Items/DA_Apple.uasset",
] {
assert!(!excluded(Path::new(rel)), "{rel} left out");
}
}
}