use std::{
ffi::OsString,
fs,
path::{Path, PathBuf},
};
use crate::{
console::Console,
context::AppContext,
error::{DoomError, Result},
};
const DEFAULT_SOURCE_BINARY: &str = "target/release/doom-eternal.exe";
const DEFAULT_OUTPUT_ROOT: &str = "build/workflow-runtime";
const DEFAULT_RUNTIME_FOLDER: &str = "doom-eternal";
const LEGACY_RUNTIME_FOLDER: &str = "xylex_doom_cli";
#[derive(Debug, Clone)]
pub struct StageWorkflowRuntimeRequest {
pub source_binary: PathBuf,
pub output_root: PathBuf,
pub runtime_folder: String,
pub dry_run: bool,
}
impl Default for StageWorkflowRuntimeRequest {
fn default() -> Self {
Self {
source_binary: PathBuf::from(DEFAULT_SOURCE_BINARY),
output_root: PathBuf::from(DEFAULT_OUTPUT_ROOT),
runtime_folder: DEFAULT_RUNTIME_FOLDER.to_string(),
dry_run: false,
}
}
}
pub fn stage_workflow_runtime(
ctx: &AppContext,
request: StageWorkflowRuntimeRequest,
) -> Result<PathBuf> {
let source_binary = ctx.repo().repo_path(request.source_binary);
let output_root = ctx.repo().repo_path(request.output_root);
stage_workflow_runtime_files(
ctx.console(),
&source_binary,
&output_root,
&request.runtime_folder,
request.dry_run,
)
}
fn stage_workflow_runtime_files(
console: &Console,
source_binary: &Path,
output_root: &Path,
runtime_folder: &str,
dry_run: bool,
) -> Result<PathBuf> {
if runtime_folder.trim().is_empty() {
return Err(DoomError::message(
"Workflow runtime folder name cannot be empty.",
));
}
if !source_binary.is_file() {
return Err(DoomError::message(format!(
"Missing workflow runtime source binary: {}. Build it first with `cargo build --release -p doom-eternal`.",
console.shorten_path(source_binary)
)));
}
let runtime_root = output_root.join(runtime_folder);
let staged_binary = runtime_root.join(binary_name(source_binary));
let legacy_runtime_root = output_root.join(LEGACY_RUNTIME_FOLDER);
console.log_info(format!(
"Staging bundled workflow runtime from {} into {}",
console.format_path(source_binary),
console.format_path(&runtime_root)
));
if dry_run {
if runtime_root.exists() {
console.log_dry_run(format!(
"reset runtime folder {}",
console.format_path(&runtime_root)
));
}
if legacy_runtime_root.exists() && legacy_runtime_root != runtime_root {
console.log_dry_run(format!(
"remove legacy runtime folder {}",
console.format_path(&legacy_runtime_root)
));
}
console.log_dry_run(format!(
"copy {} -> {}",
console.format_path(source_binary),
console.format_path(&staged_binary)
));
return Ok(staged_binary);
}
if runtime_root.exists() {
fs::remove_dir_all(&runtime_root)?;
}
if legacy_runtime_root.exists() && legacy_runtime_root != runtime_root {
fs::remove_dir_all(&legacy_runtime_root)?;
}
fs::create_dir_all(&runtime_root)?;
fs::copy(source_binary, &staged_binary)?;
console.log_success(format!(
"Bundled workflow runtime: {}",
console.format_path(&staged_binary)
));
Ok(staged_binary)
}
fn binary_name(source_binary: &Path) -> OsString {
source_binary
.file_name()
.map(OsString::from)
.unwrap_or_else(|| OsString::from("doom-eternal.exe"))
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::TempDir;
use crate::console::Console;
use super::stage_workflow_runtime_files;
#[test]
fn stages_runtime_binary_and_cleans_legacy_folder() {
let temp_dir = TempDir::new().expect("tempdir");
let source_binary = temp_dir
.path()
.join("target")
.join("release")
.join("doom-eternal.exe");
let source_parent = source_binary
.parent()
.expect("source binary should have parent");
fs::create_dir_all(source_parent).expect("source parent");
fs::write(&source_binary, b"doom-eternal").expect("source binary");
let output_root = temp_dir.path().join("build").join("workflow-runtime");
let legacy_root = output_root.join("xylex_doom_cli");
fs::create_dir_all(&legacy_root).expect("legacy root");
fs::write(legacy_root.join("stale.txt"), b"stale").expect("legacy file");
let console = Console::new(temp_dir.path().to_path_buf());
let staged_binary = stage_workflow_runtime_files(
&console,
&source_binary,
&output_root,
"doom-eternal",
false,
)
.expect("stage runtime");
assert!(staged_binary.is_file());
assert_eq!(
fs::read(&staged_binary).expect("staged binary"),
b"doom-eternal"
);
assert!(!legacy_root.exists());
}
#[test]
fn errors_when_source_binary_is_missing() {
let temp_dir = TempDir::new().expect("tempdir");
let console = Console::new(temp_dir.path().to_path_buf());
let missing_binary = temp_dir
.path()
.join("target")
.join("release")
.join("missing.exe");
let output_root = temp_dir.path().join("build").join("workflow-runtime");
let error = stage_workflow_runtime_files(
&console,
&missing_binary,
&output_root,
"doom-eternal",
false,
)
.expect_err("missing source should fail");
assert!(error
.to_string()
.contains("Missing workflow runtime source binary"));
}
}