// DCR — Cargo-like C/C++ project manager. // // Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::cli::build::build; use crate::cli::flags::parse_build_run_flags; use crate::core::config::Config; use crate::core::runner::run_binary; use crate::utils::build::{normalize_target_os, parse_version_info}; use crate::utils::fs::find_project_root; use crate::utils::fs::with_dir; use crate::utils::log::error; use crate::utils::text::{BOLD_GREEN, colored}; use std::path::Path; use std::process::Command; fn get_run_cmd( config: &Config, profile: &str, target: Option<&str>, version: &str, ) -> Option { 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 { 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, }; // Handle workspace-only root: delegate to workspace member if config.is_workspace_only() { 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; } }; // Build and run from the member's directory 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 { 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(""); // If no target specified, use default host target for target-specific config let mut target = flags.target.clone(); if target.is_none() { let default_target = if cfg!(target_os = "linux") { format!("{}-unknown-linux-gnu", std::env::consts::ARCH) } else if cfg!(target_os = "macos") { "x86_64-apple-darwin".to_string() } else if cfg!(target_os = "windows") { "x86_64-pc-windows-msvc".to_string() } else if cfg!(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly" )) { format!( "{}-unknown-{}", std::env::consts::ARCH, std::env::consts::OS ) } else { "unknown".to_string() }; target = Some(default_target); } 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 normalized_target_dir = if !out_dir.is_empty() { Some(out_dir) } else { match workspace_root { Some(wr) => target .as_ref() .and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)) .map(|rel| wr.join(&rel).to_string_lossy().to_string()), None => target .as_ref() .and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)), } }; 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") { return Err("Cannot run library build".to_string()); } let build_status = 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 { println!("\n {} {}", colored("Running", BOLD_GREEN), cmd); println!("--------------------------------"); return Ok(run_shell(&cmd)); } println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path); println!("--------------------------------"); return Ok(run_binary( project_name, &flags.profile, normalized_target_dir.as_deref(), )); } let fallback_code = if let Some(cmd) = run_cmd { run_shell(&cmd) } else { run_binary( project_name, &flags.profile, normalized_target_dir.as_deref(), ) }; 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 { 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.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 substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String { let info = parse_version_info(version); cmd.replace("{profile}", profile) .replace("{version}", &info.full) .replace("{version_major}", &info.major) .replace("{version_minor}", &info.minor) .replace("{version_patch}", &info.patch) .replace("{version_suffix}", &info.suffix) .replace("{version_suffix_dash}", &info.suffix_dash) }