From 5e8037da970e616232b35f9721dca823734898b8 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 3 Aug 2026 00:40:51 +0800 Subject: [PATCH 1/3] feat: host-target config resolution and cargo-style run args --- Cargo.lock | 2 +- Cargo.toml | 2 +- man/man1/dcr-run.1 | 12 ++++++ src/cli/flags.rs | 44 ++++++++++++++++++- src/cli/run.rs | 77 ++++++++++++++++++++++++++++++--- src/core/build/engine.rs | 93 ++++++++++++++++++++++++++-------------- src/core/runner.rs | 9 +++- src/utils/build.rs | 2 + tests/cli_build.rs | 81 ++++++++++++++++++++++++++++++++++ 9 files changed, 279 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a44f4c..cccfe4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ dependencies = [ [[package]] name = "dcr" -version = "0.8.2" +version = "0.8.3" dependencies = [ "ctrlc", "fatfs", diff --git a/Cargo.toml b/Cargo.toml index 35cd7c1..bf160b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.8.2" +version = "0.8.3" edition = "2024" build = "build.rs" description = "DCR is a utility for managing C/C++ projects in a Cargo-like style." diff --git a/man/man1/dcr-run.1 b/man/man1/dcr-run.1 index c5939b3..c113d5f 100644 --- a/man/man1/dcr-run.1 +++ b/man/man1/dcr-run.1 @@ -8,9 +8,13 @@ dcr-run \- Build and run a DCR project [\fB\-\-force\fR] [\fB\-\-clean\fR] [\fB\-\-verbose\fR] +[\fB\-\-\fR \fI...\fR] .SH DESCRIPTION Builds and runs the project. Only available for projects with \fBkind = "bin"\fR. +Arguments after a bare +.B \-\- +are forwarded to the built binary (cargo-style). .SH OPTIONS .TP .B \-\-debug @@ -30,6 +34,14 @@ Clean before building. .TP .B \-\-verbose Print detailed build output. +.TP +.B \-\- \fI...\fR +Arguments passed to the built binary. +.SH EXAMPLES +.nf +dcr run --release -- --test_help +dcr run -- --foo bar +.fi .SH SEE ALSO .BR dcr (1), .BR dcr-build (1) diff --git a/src/cli/flags.rs b/src/cli/flags.rs index 0ecb458..c8c68b4 100644 --- a/src/cli/flags.rs +++ b/src/cli/flags.rs @@ -25,16 +25,25 @@ pub struct BuildRunFlags { pub force: bool, pub clean: bool, pub verbose: bool, + pub bin_args: Vec, +} + +pub fn split_double_dash(args: &[String]) -> (&[String], &[String]) { + match args.iter().position(|a| a == "--") { + Some(i) => (&args[..i], &args[i + 1..]), + None => (args, &[]), + } } pub fn parse_build_run_flags(args: &[String]) -> Result { + let (dcr_args, bin_args_slice) = split_double_dash(args); let mut profile = PROFILE.to_string(); let mut target = None; let mut workspace = None; let mut force = false; let mut clean = false; let mut verbose = false; - let mut iter = args.iter(); + let mut iter = dcr_args.iter(); while let Some(arg) = iter.next() { if !arg.starts_with("--") { @@ -91,5 +100,38 @@ pub fn parse_build_run_flags(args: &[String]) -> Result { force, clean, verbose, + bin_args: bin_args_slice.to_vec(), }) } + +#[cfg(test)] +mod tests { + use super::*; + + fn s(args: &[&str]) -> Vec { + args.iter().map(|a| a.to_string()).collect() + } + + #[test] + fn split_double_dash_empty_tail() { + let args = s(&["--release", "--force"]); + let (left, right) = split_double_dash(&args); + assert_eq!(left, &s(&["--release", "--force"])); + assert!(right.is_empty()); + } + + #[test] + fn split_double_dash_with_bin_args() { + let args = s(&["--release", "--", "--test_help", "x"]); + let (left, right) = split_double_dash(&args); + assert_eq!(left, &s(&["--release"])); + assert_eq!(right, &s(&["--test_help", "x"])); + } + + #[test] + fn parse_run_flags_forwards_after_double_dash() { + let flags = parse_build_run_flags(&s(&["--release", "--", "--test_help"])).unwrap(); + assert_eq!(flags.profile, "release"); + assert_eq!(flags.bin_args, s(&["--test_help"])); + } +} diff --git a/src/cli/run.rs b/src/cli/run.rs index 9bcc731..1c99357 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -59,12 +59,13 @@ 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 ] [--force] [--clean] [--verbose]", + " dcr run [--debug | --release] [--target ] [--force] [--clean] [--verbose] [-- ...]", 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)"); @@ -73,6 +74,7 @@ pub fn run(args: &[String]) -> i32 { println!(" --force Force a full rebuild"); println!(" --clean Clean before building"); println!(" --verbose Print detailed build output"); + println!(" -- ... Arguments forwarded to the binary"); return 0; } @@ -112,12 +114,13 @@ pub fn run(args: &[String]) -> i32 { 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), - cmd + display ); - return run_shell(&cmd); + return run_shell_with_args(&cmd, &flags.bin_args); } return build_status; } @@ -250,32 +253,36 @@ fn run_project( ); 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), - cmd + display ); - return Ok(run_shell(&cmd)); + 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), - bin_path + 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(&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 { @@ -345,6 +352,62 @@ fn run_shell(cmd: &str) -> i32 { } } +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") { + // 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 + } +} + fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String { let info = parse_version_info(version); substitute_vars(cmd, &info, profile, "") diff --git a/src/core/build/engine.rs b/src/core/build/engine.rs index f0d1ee4..9a1d6c8 100644 --- a/src/core/build/engine.rs +++ b/src/core/build/engine.rs @@ -36,10 +36,10 @@ use crate::core::build_config::Config; use crate::core::deps::{register, resolve_deps}; use crate::core::workspace::parse_workspace; use crate::utils::build::{ - get_bool_with_profile, get_config_opt, get_config_str, get_language_with_profile, - is_bare_metal_target, normalize_kind, normalize_platform, normalize_target, - normalize_target_os, parse_version_info, prepend_clang_target_flag, primary_language, - resolve_compiler, resolve_pkg_config_flags, resolve_tool, substitute_vars, + default_target_triple, get_bool_with_profile, get_config_opt, get_config_str, + get_language_with_profile, is_bare_metal_target, normalize_kind, normalize_platform, + normalize_target, normalize_target_os, parse_version_info, prepend_clang_target_flag, + primary_language, resolve_compiler, resolve_pkg_config_flags, resolve_tool, substitute_vars, }; use crate::utils::fs::check_dir; use std::fs; @@ -187,15 +187,27 @@ fn build_all( let project_version = get_config_str(&config, "package.version"); let targets_to_build: Vec> = if let Some(t) = target { - vec![Some(normalize_target_os(t).to_string())] + let normalized = normalize_target_os(t); + if normalized.is_empty() { + vec![Some(default_target_triple())] + } else { + vec![Some(normalized.to_string())] + } } else { let config_targets = get_targets(&config, profile)?; if config_targets.is_empty() { - vec![None] + vec![Some(default_target_triple())] } else { config_targets .into_iter() - .map(|t| Some(normalize_target_os(&t).to_string())) + .map(|t| { + let normalized = normalize_target_os(&t); + if normalized.is_empty() { + Some(default_target_triple()) + } else { + Some(normalized.to_string()) + } + }) .collect() } }; @@ -344,12 +356,32 @@ fn build_project_at( } let project_name = get_config_str(&config, "package.name"); let project_version = get_config_str(&config, "package.version"); + let host_target = default_target_triple(); let build_target_config = get_build_string_with_profile(&config, "target", profile); - let build_target = target.or(if build_target_config.is_empty() { - None - } else { - Some(build_target_config.as_str()) - }); + let build_target_owned = target + .filter(|t| !t.trim().is_empty()) + .map(|t| { + let normalized = normalize_target_os(t); + if normalized.is_empty() { + host_target.clone() + } else { + normalized.to_string() + } + }) + .or_else(|| { + if build_target_config.is_empty() { + None + } else { + let normalized = normalize_target_os(&build_target_config); + if normalized.is_empty() { + None + } else { + Some(normalized.to_string()) + } + } + }) + .unwrap_or(host_target); + let build_target = Some(build_target_owned.as_str()); let build_language = get_language_with_profile(&config, profile)?; let build_qt = get_bool_with_profile(&config, "qt", profile, false); let lang_key = primary_language(&build_language); @@ -530,27 +562,26 @@ fn build_project_at( root.join(&rel).to_string_lossy().to_string() } None => { - let base_dir = - if let Some(rel) = normalize_target(build_target.unwrap_or(""), profile) { - PathBuf::from(rel) + let base_dir = if let Some(rel) = normalize_target(&build_target_owned, profile) { + PathBuf::from(rel) + } else { + let default_dir = if cfg!(target_os = "linux") { + let arch = std::env::consts::ARCH; + format!("{arch}-unknown-linux-gnu/{profile}") + } else if cfg!(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly" + )) { + let arch = std::env::consts::ARCH; + let os = std::env::consts::OS; + format!("{arch}-unknown-{os}/{profile}") } else { - let default_dir = if cfg!(target_os = "linux") { - let arch = std::env::consts::ARCH; - format!("{arch}-unknown-linux-gnu/{profile}") - } else if cfg!(any( - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "dragonfly" - )) { - let arch = std::env::consts::ARCH; - let os = std::env::consts::OS; - format!("{arch}-unknown-{os}/{profile}") - } else { - profile.to_string() - }; - Path::new("target").join(&default_dir) + profile.to_string() }; + Path::new("target").join(&default_dir) + }; project_root.join(base_dir).to_string_lossy().to_string() } } diff --git a/src/core/runner.rs b/src/core/runner.rs index 68d2778..32fc08d 100644 --- a/src/core/runner.rs +++ b/src/core/runner.rs @@ -19,10 +19,15 @@ use crate::platform; use std::path::Path; use std::process::Command; -pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 { +pub fn run_binary( + project_name: &str, + profile: &str, + target_dir: Option<&str>, + bin_args: &[String], +) -> i32 { let bin_path = platform::bin_path(profile, project_name, target_dir); if Path::new(&bin_path).exists() { - let status = Command::new(&bin_path).status(); + let status = Command::new(&bin_path).args(bin_args).status(); match status { Ok(s) => { return s.code().unwrap_or(0); diff --git a/src/utils/build.rs b/src/utils/build.rs index 0f70a6b..155d8b8 100644 --- a/src/utils/build.rs +++ b/src/utils/build.rs @@ -105,6 +105,7 @@ pub fn normalize_platform(platform: &str) -> Option<&str> { pub fn normalize_target_os(target: &str) -> &str { match target { + "" => "", "linux" => "x86_64-unknown-linux-gnu", "macos" => "x86_64-apple-darwin", "windows" => "x86_64-pc-windows-msvc", @@ -515,6 +516,7 @@ mod tests { #[test] fn normalize_target_short_names() { + assert_eq!(normalize_target_os(""), ""); assert_eq!(normalize_target_os("linux"), "x86_64-unknown-linux-gnu"); assert_eq!(normalize_target_os("macos"), "x86_64-apple-darwin"); assert_eq!(normalize_target_os("windows"), "x86_64-pc-windows-msvc"); diff --git a/tests/cli_build.rs b/tests/cli_build.rs index 9543158..8b45f5b 100644 --- a/tests/cli_build.rs +++ b/tests/cli_build.rs @@ -87,6 +87,33 @@ fn build_run_clean_flags_normal_project() { "dcr run should start" ); + std::fs::write( + dir.join("src/main.c"), + r#"#include +int main(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + printf("ARG:%s\n", argv[i]); + } + return 0; +} +"#, + ) + .expect("rewrite main.c"); + let out = run_dcr_env(&["run", "--force", "--", "--test_help", "foo"], &dir, &envs); + let combined = format!( + "{}{}", + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr) + ); + assert!( + out.status.success(), + "dcr run -- should succeed:\n{combined}" + ); + assert!( + combined.contains("ARG:--test_help") && combined.contains("ARG:foo"), + "binary should receive args after `--`:\n{combined}" + ); + let out = run_dcr_env(&["clean", "--release"], &dir, &envs); assert!(out.status.success(), "dcr clean --release should succeed"); let release_dir = host_profile_dir(&dir, "release"); @@ -286,3 +313,57 @@ fn package_build_target_used_without_cli_flag() { "artifact should be under package build.target path", ); } + +#[test] +fn host_target_section_ldflags_apply_without_cli_target() { + let Some(compiler) = available_compiler() else { + eprintln!("no compiler found; skipping host target ldflags test"); + return; + }; + + let dir = unique_sandbox_dir("host_ldflags"); + let out = run_dcr(&["init"], &dir); + assert!(out.status.success(), "dcr init should succeed"); + + let host = if cfg!(target_os = "linux") { + let arch = std::env::consts::ARCH; + let env = if cfg!(target_env = "musl") { + "musl" + } else { + "gnu" + }; + format!("{arch}-unknown-linux-{env}") + } else if cfg!(target_os = "macos") { + format!("{}-apple-darwin", std::env::consts::ARCH) + } else if cfg!(target_os = "windows") { + let env = if cfg!(target_env = "gnu") { + "gnu" + } else { + "msvc" + }; + format!("{}-pc-windows-{env}", std::env::consts::ARCH) + } else { + eprintln!("unsupported host for host_target_section test; skipping"); + return; + }; + + let toml_path = dir.join("dcr.toml"); + let toml = std::fs::read_to_string(&toml_path).expect("read dcr.toml"); + let updated = format!( + "{toml}\n[build.{host}]\nldflags = [\"-Wl,-rpath,/nonexistent-dcr-host-ldflags\"]\n" + ); + std::fs::write(&toml_path, updated).expect("write dcr.toml"); + + let envs = [("DCR_COMPILER", compiler), ("DCR_DEBUG", "1")]; + let out = run_dcr_env(&["build", "--force"], &dir, &envs); + let combined = format!( + "{}{}", + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr) + ); + assert!( + combined.contains("-Wl,-rpath,/nonexistent-dcr-host-ldflags") + || combined.contains("rpath,/nonexistent-dcr-host-ldflags"), + "host target section ldflags must be applied on native build:\n{combined}" + ); +} From d2df85a064d61564323f47f15d1679cf8b668d62 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 3 Aug 2026 21:16:11 +0800 Subject: [PATCH 2/3] fix: preserve native artifact paths on Windows and macOS --- CHANGELOG.md | 81 ++++++++++++++++++++++++++++++++++------ docs/changelog.mdx | 22 +++++++++++ src/core/build/engine.rs | 9 ++++- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eacd841..5505c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## [0.8.3] - 2026-08-03 "Конфигурация host target, аргументы run и нативные пути артефактов / Host Target Config, Run Args & Native Artifact Paths" + +### RU + +**Добавлено:** + +- **`dcr run -- ...`** — cargo-style: всё после bare `--` передаётся собранному бинарнику + (`dcr run --release -- --test_help`). Работает и для прямого запуска, и для `[run].cmd`. +- **Интеграционные / unit-тесты** — host target sections применяют `ldflags` без `--target`; проброс аргументов после `--`; парсер `split_double_dash`. + +**Изменено:** + +- **Native-сборка резолвит host triple** — без `--target` / без `build.target` конфиг-секции вида + `[build.x86_64-unknown-linux-gnu]` (и аналоги host) снова применяются (`cflags` / `ldflags` / toolchain). + Раньше target был «пустым», секции молча игнорировались, а артефакты уже лежали под host triple — линковка без `-L`/`-l`. +- **Пустой target** — `normalize_target_os("")` больше не warn'ит `Unknown target ''`. +- **`dcr run --help` / man `dcr-run`** — документирован разделитель `--` и forwarding args. + +**Исправлено:** + +- **Нативные пути артефактов на Windows и macOS** — без явного `--target` / `build.target` артефакты снова сохраняются в `target/`, поэтому `dcr run` и flat-bin находят результат сборки. +- Target-specific `ldflags` на native Linux/macOS/Windows (host triple) не доходили до линкера. +- Ложный warning на пустой target string. + +### EN + +**Added:** + +- **`dcr run -- ...`** — cargo-style: arguments after a bare `--` are forwarded to the built binary + (`dcr run --release -- --test_help`). Works for direct binary runs and `[run].cmd`. +- **Tests** — host target sections apply `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests. + +**Changed:** + +- **Native builds resolve the host triple** — without `--target` / without package `build.target`, host sections such as + `[build.x86_64-unknown-linux-gnu]` (and host equivalents) apply again (`cflags` / `ldflags` / toolchain). + Previously the target was empty, those sections were silently ignored while artifacts already used the host triple path — link failed without `-L`/`-l` (e.g. sandbox micro-lang + `libsct-elf`). +- **Empty target** — `normalize_target_os("")` no longer warns `Unknown target ''`. +- **`dcr run --help` / man `dcr-run`** — document `--` and argument forwarding. + +**Fixed:** + +- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts again use `target/`, so `dcr run` and flat-bin find the build output. +- Target-specific `ldflags` on native host builds not reaching the linker. +- Spurious warning for an empty target string. + ## [0.8.2] - 2026-07-22 "CLI Progress, archive Feature, Workspace Path Fixes / CLI Progress, archive Feature, Workspace Path Fixes" ### RU @@ -215,25 +261,31 @@ ### RU **Добавлено:** + - Учтён `target_env` в `default_target_triple()` — Linux (gnu/musl) и Windows (msvc/gnu) теперь используют правильное окружение, а не хардкод. **Исправлено:** + - **Сборка на macOS Apple Silicon падала с `_main` undefined** — `--target=` теперь передаётся и в ldflags, не только в cflags. `default_target_triple()` на macOS использует `std::env::consts::ARCH` вместо хардкода `x86_64`. - **Бейджи GitHub Stars и GPL-3.0 не рендерились в README** — блок бейджей переведён на чистый HTML. **Изменено:** + - Вычисление таргета по умолчанию вынесено в общую функцию `default_target_triple()` — устранено дублирование в build, run, clean. ### EN **Added:** + - `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values. **Fixed:** + - **macOS Apple Silicon builds failed with `_main` undefined** — `--target=` is now passed to ldflags, not just cflags. `default_target_triple()` uses `std::env::consts::ARCH` on macOS instead of hardcoded `x86_64`. - **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML. **Changed:** + - Default target resolution extracted into shared `default_target_triple()` — removed duplication across build, run, clean. ## [0.7.1] - 2026-06-02 "Мультиархитектурное расширение / The Multi-Arch Expansion" @@ -241,6 +293,7 @@ ### RU **Добавлено:** + - Вывод `--help` для всех команд с цветным форматированием. - man-страницы (12 штук в `man/man1/`) и их установка во всех пакетах. - Валидация имен проектов в `dcr init` и `dcr new` (только ASCII буквы, цифры, `_` и `-`). @@ -251,6 +304,7 @@ - Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и сборка Snap в CI. **Изменено:** + - Функция `validate_package_name` стала публичной. - Команды `gen`, `tree`, `fmt`, `setup` больше не игнорируют аргументы и используют цветной вывод. - Инструкция в README изменена с `| bash` на `| sh` для POSIX. @@ -268,6 +322,7 @@ ### EN **Added:** + - `--help` for all commands with styled headers and usage lines. - Man pages (12 troff man pages in `man/man1/`) and their installation across all packaging formats. - Project name validation in `dcr init` and `dcr new` (ASCII letters, digits, `_`, `-` only). @@ -278,6 +333,7 @@ - AUR packages (`dcr-dev`, `dcr-dev-bin`) and Snap build in CI. **Changed:** + - `validate_package_name` made public and callable from CLI commands directly. - `gen.rs`, `tree.rs`, `fmt.rs`, `setup.rs` no longer ignore arguments and use styled output. - README install instructions changed from `| bash` to `| sh` for POSIX compatibility. @@ -454,6 +510,7 @@ Added: ``` Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`). + - Automatic injection of `--target=` into compiler flags when `build.target` is set in `dcr.toml`. This greatly simplifies clang-based cross-compilation (especially for bare-metal targets like `aarch64-none-elf`). - Bare-metal targets (containing `none`, `-elf`, `eabi`, `baremetal`) no longer receive DCR's internal default flags (`-g`, `-Wall`, `-Wextra`, `-fno-omit-frame-pointer`, `-DDCR_DEBUG`, etc.). Prevents unwanted sections (`.comment`, debug info, etc.) that break custom linker scripts when `inherit = true`. - `build.ldscript` in `dcr.toml` — linker script path passed as `-T ` to the linker. Essential for bare-metal/embedded/freestanding targets. @@ -488,28 +545,28 @@ Fixed: - Path dependencies now work correctly: `dcr add` stores them as `{ path = "./..." }` tables, `is_registry_dep` properly distinguishes registry strings -from path/git strings, and `deps/mod.rs` resolves both table-form and -legacy string-form path dependencies + from path/git strings, and `deps/mod.rs` resolves both table-form and + legacy string-form path dependencies - Registry dependency paths no longer hardcoded to `project_root/dcr-index`; -now use `package_root_from_registry_info()` which resolves relative to the -registry cache root (`~/.dcr/`) + now use `package_root_from_registry_info()` which resolves relative to the + registry cache root (`~/.dcr/`) - Registry dependencies are now actually built: `build_project_at()` is -called when `include_dir` or `lib_dir` is missing + called when `include_dir` or `lib_dir` is missing Added: - `SIGINT`/`Ctrl+C` handler via `ctrlc` crate — `dcr build` now checks -`BUILD_INTERRUPTED` flag at key points and aborts cleanly + `BUILD_INTERRUPTED` flag at key points and aborts cleanly - `utils/build.rs` — extracted shared utilities: `parse_version_info`, -`normalize_target_os`, `resolve_compiler`, `primary_language`, -`resolve_pkg_config_flags` and config helpers. Eliminates code duplication -between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs` + `normalize_target_os`, `resolve_compiler`, `primary_language`, + `resolve_pkg_config_flags` and config helpers. Eliminates code duplication + between `cli/build.rs`, `cli/run.rs`, `cli/clean.rs`, `cli/gen.rs` - `utils/fs.rs::with_dir` — extracted common directory-scoped execution - `run_command_sync_output` in `builder/common.rs` with global `OUTPUT_MUTEX` -— synchronized compiler output across all backends (unix_cc, gas, nasm, msvc) + — synchronized compiler output across all backends (unix_cc, gas, nasm, msvc) - New helper functions in `deps/register.rs`: `get_registry_cache_root`, -`package_root_from_registry_info`, `registry_include_dir`, `registry_lib_dir`, -`path_from_string_dep` + `package_root_from_registry_info`, `registry_include_dir`, `registry_lib_dir`, + `path_from_string_dep` - Unit tests for `register.rs` (`is_registry_dep`, `package_root_from_registry_info`), `deps/mod.rs` (`path_dep_path`, `push_default_lib_dirs`), `utils/build.rs` (`normalize_target_os`, `parse_version_info`) diff --git a/docs/changelog.mdx b/docs/changelog.mdx index 2e29677..0b7bc74 100644 --- a/docs/changelog.mdx +++ b/docs/changelog.mdx @@ -4,6 +4,28 @@ sidebar_label: Changelog # Changelog +## 0.8.3 (2026-08-03) + +### Added + +- **`dcr run -- ...`** — cargo-style argument forwarding after a bare `--` + (`dcr run --release -- --test_help`). Applies to direct binary runs and `[run].cmd`. +- **Tests** — host target section `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests. + +### Changed + +- **Native builds resolve the host triple** — without `--target` / package `build.target`, host sections such as + `[build.x86_64-unknown-linux-gnu]` apply again (`cflags` / `ldflags` / toolchain). + Previously the target was empty and those sections were ignored while artifacts already used the host triple path. +- **Empty target** — no more `Unknown target ''` warning. +- **`dcr run --help` / man `dcr-run`** — document `--` forwarding. + +### Fixed + +- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts use `target/`, so `dcr run` and flat-bin find the build output. +- Target-specific `ldflags` missing on native host builds (e.g. micro-lang + `libsct-elf`). +- Spurious empty-target warning. + ## 0.8.2 (2026-07-22) ### Added diff --git a/src/core/build/engine.rs b/src/core/build/engine.rs index 9a1d6c8..8c00784 100644 --- a/src/core/build/engine.rs +++ b/src/core/build/engine.rs @@ -358,6 +358,8 @@ fn build_project_at( let project_version = get_config_str(&config, "package.version"); let host_target = default_target_triple(); let build_target_config = get_build_string_with_profile(&config, "target", profile); + let has_explicit_target = + target.is_some_and(|t| !t.trim().is_empty()) || !build_target_config.is_empty(); let build_target_owned = target .filter(|t| !t.trim().is_empty()) .map(|t| { @@ -562,8 +564,11 @@ fn build_project_at( root.join(&rel).to_string_lossy().to_string() } None => { - let base_dir = if let Some(rel) = normalize_target(&build_target_owned, profile) { - PathBuf::from(rel) + let base_dir = if has_explicit_target { + PathBuf::from( + normalize_target(&build_target_owned, profile) + .expect("explicit build target must be non-empty"), + ) } else { let default_dir = if cfg!(target_os = "linux") { let arch = std::env::consts::ARCH; From 794d571d58179b721173f0e101ff20138097b8a3 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Mon, 3 Aug 2026 21:26:34 +0800 Subject: [PATCH 3/3] fix: distinguish implicit host builds from explicit targets --- src/core/build/engine.rs | 58 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/core/build/engine.rs b/src/core/build/engine.rs index 8c00784..ca9f935 100644 --- a/src/core/build/engine.rs +++ b/src/core/build/engine.rs @@ -159,6 +159,7 @@ fn build_all( &member.path, profile, target, + target.is_some_and(|t| !t.trim().is_empty()), &[], force, verbose, @@ -186,26 +187,26 @@ fn build_all( let project_name = get_config_str(&config, "package.name"); let project_version = get_config_str(&config, "package.version"); - let targets_to_build: Vec> = if let Some(t) = target { + let targets_to_build: Vec<(Option, bool)> = if let Some(t) = target { let normalized = normalize_target_os(t); if normalized.is_empty() { - vec![Some(default_target_triple())] + vec![(Some(default_target_triple()), true)] } else { - vec![Some(normalized.to_string())] + vec![(Some(normalized.to_string()), true)] } } else { let config_targets = get_targets(&config, profile)?; if config_targets.is_empty() { - vec![Some(default_target_triple())] + vec![(Some(default_target_triple()), false)] } else { config_targets .into_iter() .map(|t| { let normalized = normalize_target_os(&t); if normalized.is_empty() { - Some(default_target_triple()) + (Some(default_target_triple()), true) } else { - Some(normalized.to_string()) + (Some(normalized.to_string()), true) } }) .collect() @@ -213,7 +214,7 @@ fn build_all( }; let start_time = Instant::now(); - for (i, build_target) in targets_to_build.iter().enumerate() { + for (i, (build_target, explicit_target)) in targets_to_build.iter().enumerate() { if cancel.load(Ordering::SeqCst) { return Err("Build interrupted".to_string()); } @@ -238,6 +239,7 @@ fn build_all( &member.path, profile, build_target.as_deref(), + *explicit_target, &[], force, verbose, @@ -251,6 +253,7 @@ fn build_all( &ws, profile, build_target.as_deref(), + *explicit_target, force, verbose, Some(root), @@ -263,6 +266,7 @@ fn build_all( root, profile, build_target.as_deref(), + *explicit_target, &excludes, force, verbose, @@ -276,6 +280,7 @@ fn build_all( root, profile, build_target.as_deref(), + *explicit_target, &[], force, verbose, @@ -295,6 +300,7 @@ fn build_workspace( workspace: &crate::core::workspace::Workspace, profile: &str, target: Option<&str>, + explicit_target: bool, force: bool, verbose: bool, workspace_root: Option<&Path>, @@ -306,6 +312,7 @@ fn build_workspace( &member.path, profile, target, + explicit_target, &[], force, verbose, @@ -322,6 +329,7 @@ fn build_project_at( project_root: &Path, profile: &str, target: Option<&str>, + explicit_target: bool, exclude_dirs: &[std::path::PathBuf], force: bool, verbose: bool, @@ -358,9 +366,10 @@ fn build_project_at( let project_version = get_config_str(&config, "package.version"); let host_target = default_target_triple(); let build_target_config = get_build_string_with_profile(&config, "target", profile); - let has_explicit_target = - target.is_some_and(|t| !t.trim().is_empty()) || !build_target_config.is_empty(); - let build_target_owned = target + let has_explicit_target = explicit_target || !build_target_config.is_empty(); + let explicit_build_target = explicit_target + .then_some(target) + .flatten() .filter(|t| !t.trim().is_empty()) .map(|t| { let normalized = normalize_target_os(t); @@ -369,19 +378,27 @@ fn build_project_at( } else { normalized.to_string() } - }) - .or_else(|| { - if build_target_config.is_empty() { + }); + let configured_build_target = || { + if build_target_config.is_empty() { + None + } else { + let normalized = normalize_target_os(&build_target_config); + if normalized.is_empty() { None } else { - let normalized = normalize_target_os(&build_target_config); - if normalized.is_empty() { - None - } else { - Some(normalized.to_string()) - } + Some(normalized.to_string()) } - }) + } + }; + let implicit_build_target = || { + target + .filter(|t| !t.trim().is_empty()) + .map(|t| normalize_target_os(t).to_string()) + }; + let build_target_owned = explicit_build_target + .or_else(configured_build_target) + .or_else(implicit_build_target) .unwrap_or(host_target); let build_target = Some(build_target_owned.as_str()); let build_language = get_language_with_profile(&config, profile)?; @@ -730,6 +747,7 @@ fn build_project_at( &dep_root, profile, build_target, + has_explicit_target, &[], force, verbose,