use crate::cli::build::build;
use crate::cli::flags::parse_build_run_flags;
use crate::core::build_config::Config;
use crate::core::runner::run_binary;
use crate::utils::build::{normalize_target_os, parse_version_info, substitute_vars};
use crate::utils::fs::find_project_root;
use crate::utils::fs::with_dir;
use crate::utils::log::error;
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::path::Path;
use std::process::Command;
fn get_run_cmd(
config: &Config,
profile: &str,
target: Option<&str>,
version: &str,
) -> Option<String> {
let base = config.get("run.cmd").and_then(|v| v.as_str());
let target_cmd = if let Some(t) = target {
let normalized_t = normalize_target_os(t);
config
.get(&format!("run.{}.cmd", normalized_t))
.or_else(|| config.get(&format!("run.{}.cmd", t)))
.and_then(|v| v.as_str())
} else {
None
};
let profile_cmd = config
.get(&format!("run.{}.cmd", profile))
.and_then(|v| v.as_str());
let cmd = target_cmd.or(profile_cmd).or(base)?;
let trimmed = cmd.trim();
if trimmed.is_empty() {
None
} else {
Some(substitute_run_vars(trimmed, profile, version))
}
}
pub fn run(args: &[String]) -> i32 {
if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN);
printc(
" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose] [-- <args>...]",
BOLD_CYAN,
);
println!();
printc("DESCRIPTION:", BOLD_GREEN);
println!(" Builds and runs the project. Only available for kind = \"bin\".");
println!(" Arguments after `--` are passed to the built binary (cargo-style).");
println!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Run with debug profile (default)");
println!(" --release Run with release profile");
println!(" --target <triple> Cross-compile for the given target");
println!(" --force Force a full rebuild");
println!(" --clean Clean before building");
println!(" --verbose Print detailed build output");
println!(" -- <args>... Arguments forwarded to the binary");
return 0;
}
let start_dir = match std::env::current_dir() {
Ok(dir) => dir,
Err(_) => {
error("Failed to determine current directory");
return 1;
}
};
let root = match find_project_root(&start_dir) {
Ok(Some(dir)) => dir,
Ok(None) => {
error("dcr.toml file not found");
return 1;
}
Err(_) => {
error("Failed to find project root");
return 1;
}
};
let config = match with_dir(&root, || {
Config::open("./dcr.toml").map_err(|err| err.to_string())
}) {
Ok(cfg) => cfg,
Err(err) => {
error(&err);
return 1;
}
};
let flags = match parse_build_run_flags(args) {
Ok(v) => v,
Err(_) => return 1,
};
if config.is_workspace_only() {
if let Some(cmd) = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), "") {
let build_status = build(&args_for_build(&flags));
if build_status == 0 {
let display = display_run_cmd(&cmd, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return run_shell_with_args(&cmd, &flags.bin_args);
}
return build_status;
}
let ws = match crate::core::workspace::parse_workspace(
&config,
&flags.profile,
flags.target.as_deref(),
&root,
) {
Ok(Some(ws)) => ws,
Ok(None) => {
error("Workspace root has no members defined");
return 1;
}
Err(e) => {
error(&e);
return 1;
}
};
let member = match &flags.workspace {
Some(name) => ws.members.iter().find(|m| m.name == *name),
None => ws.main_member(),
};
let member = match member {
Some(m) => m,
None => {
if let Some(name) = &flags.workspace {
error(&format!("Workspace member '{name}' not found"));
} else {
error("No workspace member to run (set `main = true` on one member)");
}
return 1;
}
};
return match with_dir(&member.path, || {
run_project(&member.path, &flags, Some(root.as_path()))
}) {
Ok(code) => code,
Err(e) => {
error(&e);
1
}
};
}
match run_project(&root, &flags, None) {
Ok(code) => code,
Err(e) => {
error(&e);
1
}
}
}
fn run_project(
root: &Path,
flags: &crate::cli::flags::BuildRunFlags,
workspace_root: Option<&Path>,
) -> Result<i32, String> {
let config =
Config::open(root.join("dcr.toml").to_str().unwrap()).map_err(|err| err.to_string())?;
let project_name: &str = config
.get("package.name")
.and_then(|v| v.as_str())
.unwrap_or("");
let mut target = flags.target.clone();
if target.is_none() {
let bt =
crate::cli::build::get_build_string_with_profile(&config, "target", &flags.profile);
if !bt.is_empty() {
target = Some(bt);
}
}
let build_kind = config
.get(&format!("build.{}.kind", flags.profile))
.and_then(|v| v.as_str())
.or_else(|| config.get("build.kind").and_then(|v| v.as_str()))
.unwrap_or("");
let out_dir =
crate::cli::build::get_build_string_with_profile(&config, "out_dir", &flags.profile);
let build_target_str = target.clone().unwrap_or_default();
let has_explicit = target.as_ref().is_some_and(|t| !t.trim().is_empty());
let normalized_target_dir = Some(crate::utils::build::resolve_artifact_target_dir(
root,
workspace_root,
&flags.profile,
&build_target_str,
&out_dir,
has_explicit,
));
let version = config
.get("package.version")
.and_then(|v| v.as_str())
.unwrap_or("");
let run_cmd = get_run_cmd(&config, &flags.profile, target.as_deref(), version);
let kind = build_kind.trim();
if run_cmd.is_none()
&& (kind == "staticlib"
|| kind == "sharedlib"
|| kind == "efi"
|| kind == "elf"
|| kind == "flat-bin")
{
return Err("Cannot run library build".to_string());
}
let build_status = if let Some(wroot) = workspace_root {
with_dir(wroot, || {
Ok(build(&args_for_workspace_build(flags, project_name)))
})
.unwrap_or(1)
} else {
build(&args_for_build(flags))
};
let bin_path = crate::platform::bin_path(
&flags.profile,
project_name,
normalized_target_dir.as_deref(),
);
if build_status == 0 {
if let Some(cmd) = run_cmd {
let display = display_run_cmd(&cmd, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return Ok(run_shell_with_args(&cmd, &flags.bin_args));
}
let display = display_bin_run(&bin_path, &flags.bin_args);
println!(
" {} {}",
colored(&format!("{:<9}", "run"), BOLD_GREEN),
display
);
return Ok(run_binary(
project_name,
&flags.profile,
normalized_target_dir.as_deref(),
&flags.bin_args,
));
}
let fallback_code = if let Some(cmd) = run_cmd {
run_shell_with_args(&cmd, &flags.bin_args)
} else {
run_binary(
project_name,
&flags.profile,
normalized_target_dir.as_deref(),
&flags.bin_args,
)
};
if fallback_code != 1 {
return Ok(fallback_code);
}
Err("Fix errors in the code to run the project".to_string())
}
fn args_for_build(flags: &crate::cli::flags::BuildRunFlags) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("--{}", flags.profile));
if let Some(ref target) = flags.target {
args.push("--target".to_string());
args.push(target.clone());
}
if let Some(ref name) = flags.workspace {
args.push("--workspace".to_string());
args.push(name.clone());
}
if flags.force {
args.push("--force".to_string());
}
if flags.clean {
args.push("--clean".to_string());
}
if flags.verbose {
args.push("--verbose".to_string());
}
args
}
fn args_for_workspace_build(
flags: &crate::cli::flags::BuildRunFlags,
member_name: &str,
) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("--{}", flags.profile));
if let Some(ref target) = flags.target {
args.push("--target".to_string());
args.push(target.clone());
}
args.push("--workspace".to_string());
args.push(member_name.to_string());
if flags.force {
args.push("--force".to_string());
}
if flags.clean {
args.push("--clean".to_string());
}
if flags.verbose {
args.push("--verbose".to_string());
}
args
}
fn run_shell(cmd: &str) -> i32 {
let status = if cfg!(target_os = "windows") {
Command::new("cmd").arg("/C").arg(cmd).status()
} else {
Command::new("sh").arg("-c").arg(cmd).status()
};
match status {
Ok(s) if s.success() => 0,
Ok(s) => s.code().unwrap_or(1),
Err(_) => 1,
}
}
fn run_shell_with_args(cmd: &str, bin_args: &[String]) -> i32 {
if bin_args.is_empty() {
return run_shell(cmd);
}
let mut full = cmd.to_string();
for arg in bin_args {
full.push(' ');
full.push_str(&shell_escape(arg));
}
run_shell(&full)
}
fn display_run_cmd(cmd: &str, bin_args: &[String]) -> String {
if bin_args.is_empty() {
return cmd.to_string();
}
let mut display = cmd.to_string();
for arg in bin_args {
display.push(' ');
display.push_str(arg);
}
display
}
fn display_bin_run(bin_path: &str, bin_args: &[String]) -> String {
if bin_args.is_empty() {
return bin_path.to_string();
}
let mut display = bin_path.to_string();
for arg in bin_args {
display.push(' ');
display.push_str(arg);
}
display
}
fn shell_escape(arg: &str) -> String {
if cfg!(target_os = "windows") {
let escaped = arg.replace('"', "\\\"");
format!("\"{escaped}\"")
} else {
let mut out = String::from("'");
for ch in arg.chars() {
if ch == '\'' {
out.push_str("'\\''");
} else {
out.push(ch);
}
}
out.push('\'');
out
}
}
fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String {
let info = parse_version_info(version);
substitute_vars(cmd, &info, profile, "")
}