mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 20:59:37 +00:00
291 lines
8.9 KiB
Rust
291 lines
8.9 KiB
Rust
// 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::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<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 {
|
|
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<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("");
|
|
|
|
// 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 normalized_target_dir = 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<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.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)
|
|
}
|