/
dcr_labs
/
dcr
Обзор
Документация
Войти
/
dcr_labs
/
dcr
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/cli/run.rs
431 строка
14 KB
Dexoron
docs: add and clean up AI-generated rustdoc comments (grok-4.5)
05 авг 2026, 14:21
05 авг 2026, 14:21
a20c61c
Код
Авторство
О чём код?
// DCR — Cargo-like C/C++ project manager. // // Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su> // // 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 <https://www.gnu.org/licenses/>. 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; /// Retrieves the run command from the config, preferring target-specific, /// then profile-specific, then the base `run.cmd`. 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)) } } /// Handles the `dcr run` subcommand: parses flags, finds the project root, /// builds if needed, and runs the resulting binary or configured command. /// /// # Parameters /// - `args`: Tokens after `dcr run` (build flags; after `--` → binary args). /// /// # Returns /// Process exit code from the built binary / `run.cmd`, or non-zero on build/setup failure. 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; } }; // 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 } } } /// Builds and runs a single project at `root` (optionally as a workspace member). 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()) } /// Builds the list of arguments for the build command based on the provided flags. 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 } /// Constructs build arguments specifically for a workspace member. 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 } /// Executes the given shell command and returns its exit code. 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, } } /// Runs the command, appending escaped binary arguments if provided. 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) } /// Formats the run command string for console output, appending arguments if any. 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 } /// Formats the binary path for console display, appending arguments if any. 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 } /// Escapes the argument for safe shell execution, using double quotes on Windows and single quotes on Unix. fn shell_escape(arg: &str) -> String { if cfg!(target_os = "windows") { // cmd.exe: wrap in double quotes and escape embedded quotes. let escaped = arg.replace('"', "\\\""); format!("\"{escaped}\"") } else { // POSIX sh: single-quote and escape embedded single quotes as '\''. let mut out = String::from("'"); for ch in arg.chars() { if ch == '\'' { out.push_str("'\\''"); } else { out.push(ch); } } out.push('\''); out } } /// Replaces version info and profile in the command template. fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String { let info = parse_version_info(version); substitute_vars(cmd, &info, profile, "") }