diff --git a/CHANGELOG.md b/CHANGELOG.md index 213b006..d4a8ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.2.10] - 2026-03-16 +Added: +- `build.steps` for pre-build generators (e.g., `moc`/`uic`/`rcc`) +- `build.pkg_config` for `pkg-config`-driven cflags/ldflags resolution +- `[toolchain]` support for `uic`, `moc`, `rcc` +- Step command variables (`{uic}`, `{moc}`, `{rcc}`, `{cflags}`, `{stem}`, `{in}`, `{out}`) +- `{profile}` placeholder support for dependency paths + ## [0.2.9] - 2026-03-14 Added: - Header dependency tracking for fine-grained incremental builds (rebuilds when `#include` files change) diff --git a/Cargo.lock b/Cargo.lock index 4c23516..2579ce4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,8 +90,9 @@ dependencies = [ [[package]] name = "dcr" -version = "0.2.9" +version = "0.2.10" dependencies = [ + "glob", "reqwest", "self-replace", "serde", @@ -262,6 +263,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.15.5" diff --git a/Cargo.toml b/Cargo.toml index c449922..e4f9512 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "dcr" -version = "0.2.9" +version = "0.2.10" edition = "2024" build = "build.rs" [dependencies] +glob = "0.3" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } self-replace = "1" diff --git a/docs/configuration/build-profiles.md b/docs/configuration/build-profiles.md index fcb9b0f..1e6b63e 100644 --- a/docs/configuration/build-profiles.md +++ b/docs/configuration/build-profiles.md @@ -18,12 +18,12 @@ dcr clean --debug ### GCC/Clang-like toolchains -- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` +- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG` - `release`: `-O3 -DNDEBUG -march=native` ### MSVC toolchain -- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-` +- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-` - `release`: `/O2 /DNDEBUG` ## Artifacts @@ -37,12 +37,12 @@ Default compiler flags: GCC/Clang: -- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` +- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG` - `release`: `-O3 -DNDEBUG -march=native` MSVC: -- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-` +- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-` - `release`: `/O2 /DNDEBUG` Static library mode (`kind = "staticlib"`): diff --git a/docs/dependencies-and-build/compilers-and-linking.md b/docs/dependencies-and-build/compilers-and-linking.md index e905a61..b18a3c5 100644 --- a/docs/dependencies-and-build/compilers-and-linking.md +++ b/docs/dependencies-and-build/compilers-and-linking.md @@ -24,12 +24,12 @@ These flags are applied automatically based on profile and compiler backend. GCC/Clang: -- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` +- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG` - `release`: `-O3 -DNDEBUG -march=native` MSVC: -- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-` +- `debug`: `/Od /Zi /W4 /DDCR_DEBUG /Oy-` - `release`: `/O2 /DNDEBUG` ## Platform hint diff --git a/docs/dependencies-and-build/local-dependencies.md b/docs/dependencies-and-build/local-dependencies.md index 40dbb9a..42d5704 100644 --- a/docs/dependencies-and-build/local-dependencies.md +++ b/docs/dependencies-and-build/local-dependencies.md @@ -11,6 +11,11 @@ For each dependency in `[dependencies]`, DCR: 5. Synchronizes dependency files to `target//deps//`. 6. Adds include/lib/link options to compile/link commands. +### Profile placeholders + +`path`, `include`, and `lib` entries may use `{profile}` which expands to the active profile +(`debug` or `release`). + ## Failure cases - Dependency path is missing or not a directory. diff --git a/src/cli/build.rs b/src/cli/build.rs index 9bf5902..611d5c2 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -6,9 +6,10 @@ use crate::core::workspace::parse_workspace; use crate::utils::fs::{check_dir, find_project_root}; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; +use glob::glob; use sha2::{Digest, Sha256}; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::Instant; pub fn build(args: &[String]) -> i32 { @@ -190,8 +191,14 @@ fn build_project_at( let tc_as = get_config_opt(&config, "toolchain.as"); let tc_ar = get_config_opt(&config, "toolchain.ar"); let tc_ld = get_config_opt(&config, "toolchain.ld"); + let tc_uic = get_config_opt(&config, "toolchain.uic"); + let tc_moc = get_config_opt(&config, "toolchain.moc"); + let tc_rcc = get_config_opt(&config, "toolchain.rcc"); let build_cflags = get_config_list(&config, "build.cflags")?; let build_ldflags = get_config_list(&config, "build.ldflags")?; + let pkg_configs = get_config_list(&config, "build.pkg_config")?; + let build_generated = get_config_list(&config, "build.generated")?; + let build_steps = get_build_steps(&config)?; let resolved_compiler = resolve_compiler( &build_language, @@ -206,6 +213,8 @@ fn build_project_at( ensure_target_dirs(&items, profile, normalize_target(&build_target)); let resolved = resolve_deps(&config, profile, project_root)?; + let (resolved_cflags, resolved_ldflags) = + resolve_pkg_config_flags(&pkg_configs, &build_cflags, &build_ldflags)?; let ctx = BuildContext { profile, project_name: &project_name, @@ -221,14 +230,34 @@ fn build_project_at( include_dirs: &resolved.include_dirs, lib_dirs: &resolved.lib_dirs, libs: &resolved.libs, - cflags: &build_cflags, - ldflags: &build_ldflags, + cflags: &resolved_cflags, + ldflags: &resolved_ldflags, }; + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] debug: compiler={}", ctx.compiler); + eprintln!("[dcr] debug: cflags={:?}", ctx.cflags); + eprintln!("[dcr] debug: ldflags={:?}", ctx.ldflags); + eprintln!("[dcr] debug: lib_dirs={:?}", ctx.lib_dirs); + eprintln!("[dcr] debug: libs={:?}", ctx.libs); + } + let tool_execs = resolve_toolchain_execs(&tc_uic, &tc_moc, &tc_rcc, &pkg_configs); + let step_flags = + build_step_flags(&resolved_cflags, &resolved.include_dirs, &resolved_compiler); + let steps_dirty = build_steps_need_run(&build_steps)?; + if steps_dirty { + clean_generated_files(&build_generated)?; + run_build_steps(&build_steps, &tool_execs, &step_flags)?; + } let sources = collect_sources(&ctx)?; let headers = collect_header_files(&ctx, project_root)?; let lib_files = collect_lib_files(&ctx); let fingerprint = compute_build_fingerprint(&ctx, &sources, &headers, &lib_files)?; - if should_skip_build(&ctx, &fingerprint) { + let mut skip = should_skip_build(&ctx, &fingerprint); + if steps_dirty { + skip = false; + } + let debug_enabled = std::env::var("DCR_DEBUG").is_ok(); + if skip && !debug_enabled { return Ok(()); } println!( @@ -237,8 +266,10 @@ fn build_project_at( project_name, project_version ); - run_build(&ctx)?; - write_build_fingerprint(&ctx, &fingerprint)?; + if !skip { + run_build(&ctx)?; + write_build_fingerprint(&ctx, &fingerprint)?; + } Ok(()) }) } @@ -350,6 +381,443 @@ fn resolve_tool(env_key: &str, fallback: Option<&str>) -> Option { fallback.map(|v| v.to_string()) } +fn resolve_pkg_config_flags( + pkgs: &[String], + base_cflags: &[String], + base_ldflags: &[String], +) -> Result<(Vec, Vec), String> { + let mut cflags = base_cflags.to_vec(); + let mut ldflags = base_ldflags.to_vec(); + if pkgs.is_empty() { + return Ok((cflags, ldflags)); + } + for pkg in pkgs { + let c_out = run_pkg_config(pkg, "--cflags")?; + let l_out = run_pkg_config(pkg, "--libs")?; + cflags.extend(split_flags(&c_out)); + ldflags.extend(split_flags(&l_out)); + } + Ok((cflags, ldflags)) +} + +fn run_pkg_config(pkg: &str, arg: &str) -> Result { + let output = std::process::Command::new("pkg-config") + .arg(arg) + .arg(pkg) + .output() + .map_err(|err| format!("Failed to run pkg-config: {err}"))?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("pkg-config failed for {pkg}: {stderr}")); + } + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} + +fn split_flags(value: &str) -> Vec { + let mut out = Vec::new(); + let mut current = String::new(); + let mut chars = value.chars().peekable(); + let mut quote: Option = None; + while let Some(ch) = chars.next() { + if let Some(q) = quote { + if ch == q { + quote = None; + continue; + } + if ch == '\\' { + if let Some(next) = chars.next() { + current.push(next); + } + continue; + } + current.push(ch); + continue; + } + if ch == '"' || ch == '\'' { + quote = Some(ch); + continue; + } + if ch.is_whitespace() { + if !current.is_empty() { + out.push(current.clone()); + current.clear(); + } + continue; + } + if ch == '\\' { + if let Some(next) = chars.next() { + current.push(next); + } + continue; + } + current.push(ch); + } + if !current.is_empty() { + out.push(current); + } + out +} + +struct ToolchainExecs { + uic: String, + moc: String, + rcc: String, +} + +fn resolve_toolchain_execs( + uic: &Option, + moc: &Option, + rcc: &Option, + pkg_configs: &[String], +) -> ToolchainExecs { + let qt_bins = resolve_qt_host_bins(pkg_configs); + ToolchainExecs { + uic: resolve_qt_tool(uic, qt_bins.as_deref(), "uic"), + moc: resolve_qt_tool(moc, qt_bins.as_deref(), "moc"), + rcc: resolve_qt_tool(rcc, qt_bins.as_deref(), "rcc"), + } +} + +fn resolve_qt_tool(configured: &Option, qt_bins: Option<&Path>, tool: &str) -> String { + if let Some(value) = configured { + return value.clone(); + } + if let Some(dir) = qt_bins { + let candidate = dir.join(tool); + if candidate.is_file() { + return candidate.to_string_lossy().to_string(); + } + if cfg!(target_os = "windows") { + let candidate = dir.join(format!("{tool}.exe")); + if candidate.is_file() { + return candidate.to_string_lossy().to_string(); + } + } + } + if let Some(candidate) = detect_qt6_tool_variant(tool) { + return candidate; + } + tool.to_string() +} + +fn resolve_qt_host_bins(pkgs: &[String]) -> Option { + let qt_pkgs: Vec<&String> = pkgs.iter().filter(|p| p.starts_with("Qt6")).collect(); + if qt_pkgs.is_empty() { + return None; + } + let preferred = ["Qt6Core", "Qt6Widgets", "Qt6Gui"]; + for name in preferred { + if let Some(dir) = query_pkg_config_var(name, "host_bins") { + return Some(dir); + } + if let Some(dir) = query_pkg_config_var(name, "libexecdir") + && let Some(bin) = qt_bins_from_libexec(&dir) + { + return Some(bin); + } + if let Some(dir) = query_pkg_config_var(name, "bindir") { + return Some(dir); + } + } + for pkg in qt_pkgs { + if let Some(dir) = query_pkg_config_var(pkg, "host_bins") { + return Some(dir); + } + if let Some(dir) = query_pkg_config_var(pkg, "libexecdir") + && let Some(bin) = qt_bins_from_libexec(&dir) + { + return Some(bin); + } + if let Some(dir) = query_pkg_config_var(pkg, "bindir") { + return Some(dir); + } + } + None +} + +fn query_pkg_config_var(pkg: &str, var: &str) -> Option { + let output = std::process::Command::new("pkg-config") + .arg(format!("--variable={var}")) + .arg(pkg) + .output() + .ok()?; + if !output.status.success() { + return None; + } + let value = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if value.is_empty() { + return None; + } + let path = PathBuf::from(value); + if path.is_dir() { Some(path) } else { None } +} + +fn qt_bins_from_libexec(libexec: &Path) -> Option { + for tool in ["moc", "uic", "rcc"] { + if libexec.join(tool).is_file() { + return Some(libexec.to_path_buf()); + } + if cfg!(target_os = "windows") && libexec.join(format!("{tool}.exe")).is_file() { + return Some(libexec.to_path_buf()); + } + } + let bin = libexec.join("bin"); + if bin.is_dir() { Some(bin) } else { None } +} + +fn detect_qt6_tool_variant(tool: &str) -> Option { + [format!("{tool}6"), format!("{tool}-qt6")] + .into_iter() + .find(|candidate| is_on_path(candidate)) +} + +fn is_on_path(cmd: &str) -> bool { + std::process::Command::new("sh") + .arg("-c") + .arg(format!("command -v {cmd}")) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +#[derive(Clone)] +struct BuildStep { + name: String, + input: String, + output: String, + cmd: String, +} + +fn get_build_steps(config: &Config) -> Result, String> { + let value = match config.get("build.steps") { + Some(v) => v, + None => return Ok(Vec::new()), + }; + let arr = value + .as_array() + .ok_or_else(|| "build.steps must be an array".to_string())?; + let mut out = Vec::new(); + for item in arr { + let tbl = item + .as_table() + .ok_or_else(|| "build.steps entries must be tables".to_string())?; + let name = tbl + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or("") + .trim() + .to_string(); + let input = tbl + .get("in") + .and_then(|v| v.as_str()) + .unwrap_or("") + .trim() + .to_string(); + let output = tbl + .get("out") + .and_then(|v| v.as_str()) + .unwrap_or("") + .trim() + .to_string(); + let cmd = tbl + .get("cmd") + .and_then(|v| v.as_str()) + .unwrap_or("") + .trim() + .to_string(); + if name.is_empty() || input.is_empty() || output.is_empty() || cmd.is_empty() { + return Err("build.steps entries must include name, in, out, cmd".to_string()); + } + out.push(BuildStep { + name, + input, + output, + cmd, + }); + } + Ok(out) +} + +fn run_build_steps( + steps: &[BuildStep], + tools: &ToolchainExecs, + step_flags: &str, +) -> Result<(), String> { + for step in steps { + run_build_step(step, tools, step_flags)?; + } + Ok(()) +} + +fn run_build_step( + step: &BuildStep, + tools: &ToolchainExecs, + step_flags: &str, +) -> Result<(), String> { + let inputs = expand_glob(&step.input)?; + if inputs.is_empty() { + return Ok(()); + } + let needs_stem = step.output.contains("{stem}"); + if inputs.len() > 1 && !needs_stem { + return Err(format!( + "build.steps '{}' output must include {{stem}} for multiple inputs", + step.name + )); + } + for input in inputs { + let stem = input.file_stem().and_then(|v| v.to_str()).unwrap_or(""); + let out_path = PathBuf::from(step.output.replace("{stem}", stem)); + if !should_run_step(&input, &out_path) { + continue; + } + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent) + .map_err(|err| format!("Failed to create step output dir: {err}"))?; + } + let cmd = substitute_step_cmd(&step.cmd, &input, &out_path, tools, step_flags, stem); + let status = run_shell_command(&cmd) + .map_err(|err| format!("Failed to run step '{}': {err}", step.name))?; + if !status.success() { + return Err(format!("Step '{}' failed", step.name)); + } + } + Ok(()) +} + +fn clean_generated_files(patterns: &[String]) -> Result<(), String> { + for pattern in patterns { + for path in expand_glob(pattern)? { + if path.is_file() { + let _ = fs::remove_file(&path); + } + } + } + Ok(()) +} + +fn expand_glob(pattern: &str) -> Result, String> { + let mut out = Vec::new(); + let entries = glob(pattern).map_err(|err| format!("glob error: {err}"))?; + for entry in entries { + let path = entry.map_err(|err| format!("glob error: {err}"))?; + out.push(path); + } + Ok(out) +} + +fn should_run_step(input: &Path, output: &Path) -> bool { + let in_time = fs::metadata(input).and_then(|m| m.modified()); + let out_time = fs::metadata(output).and_then(|m| m.modified()); + match (in_time, out_time) { + (Ok(i), Ok(o)) => i > o, + (Ok(_), Err(_)) => true, + _ => true, + } +} + +fn substitute_step_cmd( + template: &str, + input: &Path, + output: &Path, + tools: &ToolchainExecs, + step_flags: &str, + stem: &str, +) -> String { + template + .replace("{in}", &input.to_string_lossy()) + .replace("{out}", &output.to_string_lossy()) + .replace("{uic}", &tools.uic) + .replace("{moc}", &tools.moc) + .replace("{rcc}", &tools.rcc) + .replace("{cflags}", step_flags) + .replace("{stem}", stem) +} + +fn build_step_flags(cflags: &[String], include_dirs: &[String], compiler: &str) -> String { + let mut out = Vec::new(); + let msvc_style = is_msvc_compiler(compiler) || cflags.iter().any(|f| f.starts_with('/')); + for flag in cflags { + if flag.starts_with("-I") || flag.starts_with("-D") { + out.push(flag.clone()); + } + if flag.starts_with("/I") || flag.starts_with("/D") { + out.push(flag.clone()); + } + if msvc_style && flag.starts_with("-D") { + out.push(format!("/D{}", flag.trim_start_matches("-D"))); + } + } + for dir in include_dirs { + out.push(format!("-I{dir}")); + if msvc_style { + out.push(format!("/I{dir}")); + } + } + let mut dedup = Vec::new(); + for item in out { + if !dedup.contains(&item) { + dedup.push(item); + } + } + dedup + .into_iter() + .map(quote_step_arg) + .collect::>() + .join(" ") +} + +fn quote_step_arg(arg: String) -> String { + if !arg.chars().any(|c| c.is_whitespace() || c == '"') { + return arg; + } + let escaped = arg.replace('"', "\\\""); + format!("\"{escaped}\"") +} + +fn is_msvc_compiler(compiler: &str) -> bool { + let lower = compiler.to_lowercase(); + lower.contains("cl.exe") + || lower == "cl" + || lower.contains("clang-cl") + || lower.contains("msvc") +} + +fn run_shell_command(cmd: &str) -> Result { + if cfg!(target_os = "windows") { + std::process::Command::new("cmd") + .arg("/C") + .arg(cmd) + .status() + } else { + std::process::Command::new("sh").arg("-c").arg(cmd).status() + } +} + +fn build_steps_need_run(steps: &[BuildStep]) -> Result { + for step in steps { + let inputs = expand_glob(&step.input)?; + if inputs.is_empty() { + continue; + } + let needs_stem = step.output.contains("{stem}"); + if inputs.len() > 1 && !needs_stem { + return Err(format!( + "build.steps '{}' output must include {{stem}} for multiple inputs", + step.name + )); + } + for input in inputs { + let stem = input.file_stem().and_then(|v| v.to_str()).unwrap_or(""); + let out_path = PathBuf::from(step.output.replace("{stem}", stem)); + if should_run_step(&input, &out_path) { + return Ok(true); + } + } + } + Ok(false) +} + fn compute_build_fingerprint( ctx: &BuildContext, sources: &[String], diff --git a/src/config.rs b/src/config.rs index 7f8b4ff..74579d5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ pub fn flags(profile: &str) -> Option<&'static [&'static str]> { "-Wall", "-Wextra", "-fno-omit-frame-pointer", - "-DDEBUG", + "-DDCR_DEBUG", ]), _ => None, } diff --git a/src/core/builder/common.rs b/src/core/builder/common.rs index 2d91e5e..1efc86e 100644 --- a/src/core/builder/common.rs +++ b/src/core/builder/common.rs @@ -175,9 +175,11 @@ mod tests { use super::*; use std::fs; use std::path::PathBuf; + use std::sync::Mutex; use std::sync::atomic::{AtomicUsize, Ordering}; static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0); + static CWD_LOCK: Mutex<()> = Mutex::new(()); fn temp_dir(prefix: &str) -> PathBuf { let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst); @@ -282,6 +284,7 @@ mod tests { fs::write(src.join("utils.c"), "").unwrap(); fs::write(src.join("README.md"), "").unwrap(); // should be ignored + let _guard = CWD_LOCK.lock().unwrap(); let prev = std::env::current_dir().unwrap(); std::env::set_current_dir(&dir).unwrap(); let result = collect_sources(&["c"], &[]); @@ -303,6 +306,7 @@ mod tests { fs::write(src.join("other.cc"), "").unwrap(); fs::write(src.join("skip.c"), "").unwrap(); // should be ignored + let _guard = CWD_LOCK.lock().unwrap(); let prev = std::env::current_dir().unwrap(); std::env::set_current_dir(&dir).unwrap(); let result = collect_sources(&["cpp", "cxx", "cc"], &[]); @@ -318,6 +322,7 @@ mod tests { let src = dir.join("src"); fs::create_dir_all(&src).unwrap(); + let _guard = CWD_LOCK.lock().unwrap(); let prev = std::env::current_dir().unwrap(); std::env::set_current_dir(&dir).unwrap(); let result = collect_sources(&["c"], &[]); @@ -336,6 +341,7 @@ mod tests { fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded let exclude = vec![vendor.clone()]; + let _guard = CWD_LOCK.lock().unwrap(); let prev = std::env::current_dir().unwrap(); std::env::set_current_dir(&dir).unwrap(); let result = collect_sources(&["c"], &exclude); @@ -355,6 +361,7 @@ mod tests { fs::write(src.join("main.c"), "").unwrap(); fs::write(sub.join("nested.c"), "").unwrap(); + let _guard = CWD_LOCK.lock().unwrap(); let prev = std::env::current_dir().unwrap(); std::env::set_current_dir(&dir).unwrap(); let result = collect_sources(&["c"], &[]); diff --git a/src/core/builder/gas.rs b/src/core/builder/gas.rs index e28c952..020142a 100644 --- a/src/core/builder/gas.rs +++ b/src/core/builder/gas.rs @@ -36,6 +36,9 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -73,6 +76,9 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index 7ece228..ea79817 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -27,6 +27,9 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -91,6 +94,9 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg(format!("/Fe:{out_path}")); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -158,7 +164,7 @@ fn msvc_arch_flag(platform: Option<&str>) -> Option<&'static str> { fn default_flags(profile: &str) -> &'static [&'static str] { match profile { "release" => &["/O2", "/DNDEBUG"], - "debug" => &["/Od", "/Zi", "/W4", "/DDEBUG", "/Oy-"], + "debug" => &["/Od", "/Zi", "/W4", "/DDCR_DEBUG", "/Oy-"], _ => &[], } } @@ -209,6 +215,9 @@ fn build_objects( cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path)); cmd.arg("/showIncludes"); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?; let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index 38150c3..0adc449 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -36,6 +36,9 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -73,6 +76,9 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -112,6 +118,9 @@ fn build_objects( for flag in ctx.cflags { cmd.arg(flag); } + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => {} Ok(_) => return Err("Build failed".to_string()), diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index 44d07de..03d3ef9 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -25,6 +25,9 @@ pub fn build(ctx: &BuildContext) -> Result { for obj in &objects { cmd.arg(obj); } + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -62,6 +65,9 @@ pub fn build(ctx: &BuildContext) -> Result { }; cmd.arg("-o").arg(out_path); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => { let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; @@ -96,7 +102,7 @@ fn default_flags(profile: &str) -> &'static [&'static str] { "-Wall", "-Wextra", "-fno-omit-frame-pointer", - "-DDEBUG", + "-DDCR_DEBUG", ], _ => &[], } @@ -143,6 +149,9 @@ fn build_objects( } let d_path = Path::new(&obj_path).with_extension("d"); cmd.arg("-MMD").arg("-MF").arg(&d_path); + if std::env::var("DCR_DEBUG").is_ok() { + eprintln!("[dcr] {:?}", cmd); + } match cmd.status() { Ok(status) if status.success() => {} Ok(_) => return Err("Build failed".to_string()), diff --git a/src/core/deps.rs b/src/core/deps.rs index 41b8e5a..7d90518 100644 --- a/src/core/deps.rs +++ b/src/core/deps.rs @@ -50,7 +50,8 @@ pub fn resolve_deps( let mut lock_packages = Vec::new(); for dep in deps { - let dep_path = resolve_path(project_root, &dep.path_raw)?; + let dep_path_raw = expand_profile(&dep.path_raw, profile); + let dep_path = resolve_path(project_root, &dep_path_raw)?; if !dep_path.is_dir() { return Err(format!( "Dependency '{}' path is not a directory: {}", @@ -59,8 +60,13 @@ pub fn resolve_deps( )); } - let include = resolve_paths(&dep_path, dep.include_raw.as_deref(), &["include"])?; - let lib = resolve_paths(&dep_path, dep.lib_raw.as_deref(), &["lib", "lib64"])?; + let include = resolve_paths(&dep_path, dep.include_raw.as_deref(), &["include"], profile)?; + let lib = resolve_paths( + &dep_path, + dep.lib_raw.as_deref(), + &["lib", "lib64"], + profile, + )?; let libs_list = dep .libs_raw .clone() @@ -200,11 +206,13 @@ fn resolve_paths( base: &Path, raw: Option<&[String]>, defaults: &[&str], + profile: &str, ) -> Result, String> { let mut out = Vec::new(); if let Some(raw) = raw { for r in raw { - let p = Path::new(r); + let expanded = expand_profile(r, profile); + let p = Path::new(&expanded); let full = if p.is_absolute() { p.to_path_buf() } else { @@ -227,6 +235,10 @@ fn resolve_paths( Ok(out) } +fn expand_profile(raw: &str, profile: &str) -> String { + raw.replace("{profile}", profile) +} + fn sync_dep_dir(src: &Path, dst: &Path) -> std::io::Result<()> { if dst.exists() { fs::remove_dir_all(dst)?;