diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7098bcf..88e4fe1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,20 +93,22 @@ jobs: set -euo pipefail tag="${{ needs.setup.outputs.version }}" notes_file=".github/release-notes.md" - if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then + # Strip -dev suffix to match base version in CHANGELOG + changelog_tag="${tag%-dev}" + if grep -q "^## \\[${changelog_tag}\\]" CHANGELOG.md; then # Extract release name from header: ## [0.7.1] - 2026-06-02 "Title" - release_name=$(awk -v ver="$tag" ' - $0 ~ "^## \\[" ver "\\]" { + release_name=$(awk -v search_ver="$changelog_tag" -v display_ver="$tag" ' + $0 ~ "^## \\[" search_ver "\\]" { match($0, /"([^"]+)"/, arr) if (arr[1] != "") { - print "v" ver " - " arr[1] + print "v" display_ver " - " arr[1] } else { - print "v" ver + print "v" display_ver } } ' CHANGELOG.md) echo "release_name=${release_name}" >> "$GITHUB_OUTPUT" - awk -v ver="$tag" ' + awk -v ver="$changelog_tag" ' $0 ~ "^## \\[" ver "\\]" {found=1; next} found && $0 ~ "^## \\[" {exit} found {print} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3c040..e8f21e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## [0.7.4] - 2026-06-16 "Bare-Metal и Оптимизация Сборки / Bare-Metal & Build Optimization" + +### RU + +**Добавлено:** + +- **Полноценная автоматизация Bare-Metal / Freestanding** — добавлен конфигурационный флаг `build.freestanding = true`. При его включении (или при обнаружении bare-metal таргета) DCR автоматически инжектирует флаг `-ffreestanding` на этапе компиляции, а также `-nostdlib` и `-static` на этапе линковки. +- **Оптимизация артефактов (LTO и Strip)** — в `dcr.toml` добавлены параметры `build.lto` (авто-инжекция флага `-flto` для компилятора и линкера) и `build.strip` (автоматическое отсекание отладочной информации через флаг `-s` линкера). +- **Контроль многопоточности сборки** — добавлен параметр `build.codegen-units`, позволяющий жестко ограничить максимальное количество параллельных потоков сборки для кастомного движка `parallel_build`. +- **Управление паникой (Panic Abort)** — добавлен параметр `build.panic = "abort"`. Для C++ проектов он автоматически отключает генерацию таблиц раскрутки стека и исключений через флаги `-fno-exceptions`, `-fno-unwind-tables` и `-fno-asynchronous-unwind-tables`. + +**Изменено:** + +- **Интеллектуальные дефолтные флаги (DCR Defaults)** — генерация дефолтных флагов оптимизации (`-O3`/`-O0`), отладки (`-g`) и предупреждений (`-Wall -Wextra`) теперь автоматически отключается для bare-metal и freestanding целей, если пользователь переопределил `build.cflags`. +- **Глобальный вынос bare-metal детектора** — функция `is_bare_metal_target` вынесена в общие утилиты `src/utils/build.rs` для сквозного использования в конвейере сборки. + +### EN + +**Added:** + +- **Full Bare-Metal / Freestanding Automation** — introduced the `build.freestanding = true` configuration option. When enabled (or when a bare-metal target is detected), DCR automatically injects `-ffreestanding` during compilation and both `-nostdlib` and `-static` during linking. +- **Artifact Optimization (LTO & Strip)** — added `build.lto` (auto-injects `-flto` for compiler and linker) and `build.strip` (automatically strips debug symbols via linker `-s` flag) options to `dcr.toml`. +- **Compilation Thread Control** — added the `build.codegen-units` option to strictly limit the maximum number of parallel jobs utilized by the custom `parallel_build` worker pool. +- **Panic Behavior Management** — added `build.panic = "abort"` support. For C++ targets, it automatically strips exception handling and unwind tables via `-fno-exceptions`, `-fno-unwind-tables`, and `-fno-asynchronous-unwind-tables`. + +**Changed:** + +- **Intelligent Default Flags Generation** — automated injection of fallback optimization (`-O3`/`-O0`), debug (`-g`), and warning (`-Wall -Wextra`) flags is now suppressed for bare-metal and freestanding builds if `build.cflags` are overridden. +- **Centralized Bare-Metal Detection** — relocated the `is_bare_metal_target` helper to common build utilities (`src/utils/build.rs`) for unified access across the building core. + ## [0.7.3] - 2026-06-13 "Оптимизация Зависимостей и VCS / Dependency Optimization & VCS" ### RU diff --git a/Cargo.lock b/Cargo.lock index 5a5eecf..3a41b9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "dcr" -version = "0.7.3" +version = "0.7.4" dependencies = [ "ctrlc", "glob", diff --git a/Cargo.toml b/Cargo.toml index c599bce..5a85834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.7.3" +version = "0.7.4" edition = "2024" build = "build.rs" description = "DCR is a utility for managing C/C++ projects in a Cargo-like style." diff --git a/TODO.md b/TODO.md index 3d61f14..964cb6c 100644 --- a/TODO.md +++ b/TODO.md @@ -2,38 +2,35 @@ Priority list of tasks to turn DCR into a true Cargo killer for C/C++ developers. -## 🔥 High Priority (Next Releases) +**Legend:** `[ ]` — not implemented | `[-]` — partially implemented | `[x]` — implemented -- [x] **Git dependencies support** - `git = "https://github.com/user/repo.git#branch"` and `git = "..."@tag` -- [ ] **Conan and vcpkg integration** - Automatic download, installation and linking of packages -- [ ] **Full freestanding / embedded support** - `-nostdlib`, linker scripts, bare-metal targets (i686-elf, aarch64-none-elf, etc.) -- [ ] **EFI support** - Dedicated platform configuration for EFI targets -- [ ] **Custom project name** - Override binary name in `dcr.toml` -- [ ] **Custom build steps / targets** - Ability to define custom pre-build, post-build, codegen steps in `dcr.toml` -- [ ] **Improved mixed C/C++/ASM projects** - Better handling of `.S` and `.asm` files with correct flags -- [ ] **dcr publish + official package registry** - Publish and fetch your own libraries easily +## 🔥 High Priority + +- [ ] **CLI Output Refactoring & Cleanup** + - [ ] Move all terminal strings to a dedicated module (`src/ui/messages.rs`) + - [ ] Standardize logging formats (`error:`, `warning:`, `status:`) + - [x] Add build execution time statistics to the final output (e.g., `Finished in 4.23s`) +- [ ] **Internationalization (i18n) / Localization** + - [ ] Integrate a lightweight localization engine (`fluent` or `gettext` pure-Rust port) + - [ ] Create initial translation catalogs (`en.ftl`, `ru.ftl`) +- [x] **Self-Update Stability Validation** + - [x] Self-update implemented via `self-replace` + `ureq` in `flag_update.rs` +- [-] **Freestanding / Embedded Automation** + - *`-ffreestanding` injected at compile time, `-nostdlib -static` at link time for bare-metal targets or `build.freestanding = true`. Config option `build.freestanding` (bool) added.* +- [ ] **Conan and vcpkg integration** Automatic download, installation and linking of packages ## ✅ Medium Priority - [ ] **Static analysis** (`dcr check`) Integration with clang-tidy, cppcheck, include-what-you-use -- [ ] **Linter + Formatter** (`dcr fmt`, `dcr lint`) +- [-] **Linter + Formatter** (`dcr fmt`, `dcr lint`) + *`dcr fmt` (clang-format) implemented; `dcr lint` does not exist yet* - [ ] **Hot reload / Live reload** for applications (raylib, SFML, SDL, etc.) -- [ ] **Better cross-compilation experience** - Ready-to-use toolchains + automatic download -- [ ] **Linker script support** + direct linker control -- [ ] **Workspaces improvements** - Per-member include directories, selective builds, better dependency graph -- [ ] **Build-time code generation** - Support for protobuf, flatbuffers, Qt moc, custom generators +- [-] **Workspaces improvements** Per-member include directories, selective builds, better dependency graph + *Basic workspaces with dep ordering + topological sort work; advanced features missing* +- [-] **Build-time code generation** Support for protobuf, flatbuffers, Qt moc, custom generators + *Generic `build.steps` machinery + Qt toolchain (uic/moc/rcc) work; no dedicated protobuf/flatbuffers generators* +- [-] **dcr publish + official package registry** *Registry consumption (fetch, resolve, link) works; `dcr publish` command / local packing missing* ## 📌 Low Priority / Nice to Have @@ -57,13 +54,12 @@ Priority list of tasks to turn DCR into a true Cargo killer for C/C++ developers --- **Currently in progress:** - -- ... +- UI/UX terminal output unification and benchmarking metrics. **Completed in recent releases:** - -- Workspaces -- Cross-compilation +- Extreme binary size optimization (-50% weight reduction via `ureq` + `native-certs`) +- Complete removal of `git2` and `openssl` in favor of system Git execution +- Workspaces & Cross-compilation (`--target `) - IDE config generation (VS Code, CLion, compile_commands.json) -- Self-update (`dcr --update`) -- Header tracking +- EFI support & Linker script support (`build.ldscript`) +- Mixed C/C++/ASM compilation with custom `std::thread::scope` work-stealing engine \ No newline at end of file diff --git a/docs/reference/build-profiles.mdx b/docs/reference/build-profiles.mdx index 140e0fe..54a6d42 100644 --- a/docs/reference/build-profiles.mdx +++ b/docs/reference/build-profiles.mdx @@ -37,6 +37,23 @@ Set `inherit = false` to disable array inheritance (only profile's own arrays ar | `debug` | `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG` | | `release` | `-O3 -DNDEBUG` | +These defaults are composed from the `opt_level`, `debug`, and `warnings` fields and can +be overridden per-profile: + +```toml +[build.release] +opt_level = "z" +lto = true +strip = true +panic = "abort" +codegen-units = "1" + +[build.debug] +opt_level = "1" +debug = false +warnings = ["all", "error"] +``` + ## Target-specific profiles ```toml diff --git a/docs/reference/build-system.mdx b/docs/reference/build-system.mdx index 7786944..c21d726 100644 --- a/docs/reference/build-system.mdx +++ b/docs/reference/build-system.mdx @@ -20,6 +20,10 @@ DCR supports 4 compilation backends. Selection is automatic based on file extens - Compiler resolved via `resolve_compiler()`: `DCR_COMPILER` > `DCR_CC` > `[toolchain]` > `build.compiler` > `PATH` - Supports `.d` files for header dependency tracking - Flags: `-std=`, `-MMD -MF`, `-c -o`, `-I`, `-L`, `-l` +- Conditional flags based on config: + - `freestanding` or bare-metal target: `-ffreestanding` (compile), `-nostdlib -static` (link) + - `lto`: `-flto` (compile + link) + - `panic = "abort"`: `-fno-exceptions` (C++ only), `-fno-unwind-tables`, `-fno-asynchronous-unwind-tables` ### MSVC diff --git a/docs/reference/cross-compilation.mdx b/docs/reference/cross-compilation.mdx index 24b1c2e..ac3a321 100644 --- a/docs/reference/cross-compilation.mdx +++ b/docs/reference/cross-compilation.mdx @@ -37,9 +37,19 @@ target = "aarch64-unknown-linux-gnu" # Auto: cflags += ["--target=aarch64-unknown-linux-gnu"] ``` -## Bare-Metal +## Bare-Metal / Freestanding -For targets containing `none`, `-elf`, `eabi`, or `baremetal`, DCR default flags are disabled (no system include paths, no `-l` libc). +For targets containing `none`, `-elf`, `eabi`, or `baremetal`, DCR automatically: + +1. **Disables default flags** — no system include paths, no `-l` libc +2. **Injects `-ffreestanding`** at compile time and `-nostdlib -static` at link time + +You can also enable freestanding mode explicitly: + +```toml +[build] +freestanding = true +``` ```bash dcr build --target aarch64-none-elf diff --git a/docs/reference/dcr-toml.mdx b/docs/reference/dcr-toml.mdx index e127923..7f4c804 100644 --- a/docs/reference/dcr-toml.mdx +++ b/docs/reference/dcr-toml.mdx @@ -64,7 +64,7 @@ author = "John Doe" | `compiler` | `"clang"` | Preferred compiler | | `kind` | `"bin"` | `bin`, `staticlib`, `sharedlib`, `efi`, `elf`, `none`, `custom` | | `target` | host | Target triple for cross-compilation | -| `platform` | `"native"` | `native`, `efi`, `freestanding` | +| `platform` | `"native"` | `native`, `efi` | | `cflags` | `[]` | Additional C/C++/ASM flags | | `ldflags` | `[]` | Additional linker flags | | `filename` | `""` | Custom output file name | @@ -77,6 +77,14 @@ author = "John Doe" | `clean` | `[]` | Glob patterns for custom clean paths | | `out_dir` | `""` | Custom output directory | | `workspace_only` | `false` | Workspace-only, not built standalone | +| `freestanding` | `false` | Compile in freestanding mode (`-ffreestanding` + `-nostdlib -static`) | +| `opt_level` | `"0"` (debug) / `"3"` (release) | Optimization level: `0`-`3`, `"s"`, `"z"` | +| `debug` | `true` (debug) / `false` (release) | Emit debug symbols (`-g`) | +| `lto` | `false` | Link-time optimization (`-flto` for both compiler and linker) | +| `strip` | `false` | Strip symbols from output (`-s` in ldflags) | +| `warnings` | `["all", "extra"]` (debug) / `[]` (release) | Warning flags (e.g. `"all"`, `"extra"`, `"pedantic"`) | +| `panic` | `""` | Panic strategy: `"abort"` disables exceptions and unwind tables | +| `codegen-units` | `""` | Max parallel compilation jobs (`"0"` = auto) | Settings from raw config (not in typed struct): - `pkg_config` — list of pkg-config packages diff --git a/src/cli/build.rs b/src/cli/build.rs index ed39098..e45ac54 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -24,8 +24,9 @@ use crate::core::deps::{register, resolve_deps}; use crate::core::workspace::parse_workspace; use crate::utils::build::{ default_target_triple, get_bool_with_profile, get_config_opt, get_config_str, - get_language_with_profile, normalize_target_os, parse_version_info, prepend_clang_target_flag, - profile_table, resolve_compiler, resolve_pkg_config_flags, resolve_tool, + get_language_with_profile, is_bare_metal_target, normalize_target_os, parse_version_info, + prepend_clang_target_flag, profile_table, resolve_compiler, resolve_pkg_config_flags, + resolve_tool, }; use crate::utils::fs::{check_dir, find_project_root, with_dir}; use crate::utils::log::error; @@ -682,6 +683,63 @@ fn build_project_at( let build_roots = get_list_with_profile_and_target(&config, "roots", profile, build_target)?; let src_disable = get_bool_with_profile(&config, "src_disable", profile, false); + let freestanding = get_bool_with_profile(&config, "freestanding", profile, false); + let lto = get_bool_with_profile(&config, "lto", profile, false); + let strip = get_bool_with_profile(&config, "strip", profile, false); + let opt_level = + get_string_with_profile_and_target(&config, "opt_level", profile, build_target); + let debug = get_bool_with_profile(&config, "debug", profile, profile != "release"); + let warnings = + get_list_with_profile_and_target(&config, "warnings", profile, build_target)?; + let panic_val = get_string_with_profile_and_target(&config, "panic", profile, build_target); + let panic_abort = panic_val == "abort"; + let codegen_units: usize = + get_string_with_profile_and_target(&config, "codegen-units", profile, build_target) + .parse() + .unwrap_or(0); + + if build_cflags.is_empty() && !freestanding && !is_bare_metal_target(build_target) { + if !opt_level.is_empty() { + build_cflags.push(format!("-O{opt_level}")); + } else { + build_cflags.push(match profile { + "release" => "-O3".to_string(), + _ => "-O0".to_string(), + }); + } + if debug { + build_cflags.push("-g".to_string()); + } + if warnings.is_empty() { + if profile == "debug" { + build_cflags.push("-Wall".to_string()); + build_cflags.push("-Wextra".to_string()); + } + } else { + for w in &warnings { + build_cflags.push(format!("-W{w}")); + } + } + match profile { + "debug" => { + build_cflags.push("-fno-omit-frame-pointer".to_string()); + build_cflags.push("-DDCR_DEBUG".to_string()); + } + "release" => { + build_cflags.push("-DNDEBUG".to_string()); + } + _ => {} + } + } + + if lto { + build_cflags.push("-flto".to_string()); + build_ldflags.push("-flto".to_string()); + } + if strip { + build_ldflags.push("-s".to_string()); + } + let build_expects = get_list_with_profile_and_target(&config, "expect", profile, build_target)?; let pkg_configs = @@ -909,6 +967,9 @@ fn build_project_at( } else { Some(build_type.as_str()) }, + freestanding, + panic_abort, + codegen_units, source_roots: &source_roots, exclude_dirs: &combined_excludes, include_paths: &combined_includes, diff --git a/src/cli/gen.rs b/src/cli/gen.rs index c879a9e..257ae47 100644 --- a/src/cli/gen.rs +++ b/src/cli/gen.rs @@ -367,6 +367,9 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result bool { value.chars().any(|c| matches!(c, '*' | '?' | '[')) } -pub fn parallel_build(total_tasks: usize, task_fn: F) -> Result<(), String> +pub fn parallel_build(total_tasks: usize, task_fn: F, max_jobs: usize) -> Result<(), String> where F: Fn(usize) -> Result<(), String> + Sync + Send, { - let num_threads = std::thread::available_parallelism() + let available = std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1); + let num_threads = if max_jobs > 0 { + max_jobs.min(available) + } else { + available + }; let counter = std::sync::atomic::AtomicUsize::new(0); let err_msg = std::sync::Mutex::new(None); diff --git a/src/core/builder/gas.rs b/src/core/builder/gas.rs index 23406d5..502e121 100644 --- a/src/core/builder/gas.rs +++ b/src/core/builder/gas.rs @@ -151,9 +151,11 @@ fn build_objects( .map(|s| common::object_path(obj_dir, s, obj_ext)) .collect(); - common::parallel_build(sources.len(), |i| { - build_object(assembler, &sources[i], &objects[i], ctx) - })?; + common::parallel_build( + sources.len(), + |i| build_object(assembler, &sources[i], &objects[i], ctx), + ctx.codegen_units, + )?; Ok(objects) } diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs index cfbaaf0..fda501d 100644 --- a/src/core/builder/mod.rs +++ b/src/core/builder/mod.rs @@ -35,6 +35,9 @@ pub struct BuildContext<'a> { pub linker: Option<&'a str>, pub archiver: Option<&'a str>, pub package_type: Option<&'a str>, + pub freestanding: bool, + pub panic_abort: bool, + pub codegen_units: usize, pub source_roots: &'a [std::path::PathBuf], pub exclude_dirs: &'a [std::path::PathBuf], pub include_paths: &'a [String], diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index 110d61b..fc218a0 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -228,9 +228,11 @@ fn build_objects( .map(|s| common::object_path(obj_dir, s, obj_ext)) .collect(); - common::parallel_build(sources.len(), |i| { - build_object(compiler, &sources[i], &objects[i], ctx) - })?; + common::parallel_build( + sources.len(), + |i| build_object(compiler, &sources[i], &objects[i], ctx), + ctx.codegen_units, + )?; Ok(objects) } diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index 0e4dc05..6dff1af 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -153,9 +153,11 @@ fn build_objects( let format = nasm_format(ctx.platform); - common::parallel_build(sources.len(), |i| { - build_object(assembler, &sources[i], &objects[i], format, ctx) - })?; + common::parallel_build( + sources.len(), + |i| build_object(assembler, &sources[i], &objects[i], format, ctx), + ctx.codegen_units, + )?; Ok(objects) } diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index eacc37f..928209d 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -18,6 +18,7 @@ use crate::core::builder::BuildContext; use crate::core::builder::common; use crate::platform; +use crate::utils::build::is_bare_metal_target; use std::fs; use std::path::Path; use std::process::Command; @@ -80,6 +81,9 @@ pub fn build(ctx: &BuildContext) -> Result { cmd.arg("-nostdlib"); cmd.arg("-Wl,-dll"); cmd.arg("-Wl,--subsystem,10"); + } else if ctx.freestanding || is_bare_metal_target(ctx.target) { + cmd.arg("-nostdlib"); + cmd.arg("-static"); } for obj in &objects { cmd.arg(obj); @@ -172,19 +176,6 @@ fn default_flags(profile: &str) -> &'static [&'static str] { } } -fn is_bare_metal_target(target: Option<&str>) -> bool { - if let Some(t) = target { - let lower = t.to_lowercase(); - lower.contains("none") - || lower.contains("-elf") - || lower.contains("eabi") - || lower.contains("baremetal") - || lower.contains("bare-metal") - } else { - false - } -} - fn build_objects( compiler: &str, sources: &[String], @@ -197,9 +188,11 @@ fn build_objects( .map(|s| common::object_path(obj_dir, s, obj_ext)) .collect(); - common::parallel_build(sources.len(), |i| { - build_object(compiler, &sources[i], &objects[i], ctx) - })?; + common::parallel_build( + sources.len(), + |i| build_object(compiler, &sources[i], &objects[i], ctx), + ctx.codegen_units, + )?; Ok(objects) } @@ -231,6 +224,23 @@ fn build_object( cmd.arg("-fPIC"); } + if (ctx.freestanding || is_bare_metal_target(ctx.target)) + && ctx.language.to_lowercase() != "asm" + { + cmd.arg("-ffreestanding"); + } + + if ctx.panic_abort && ctx.language.to_lowercase() != "asm" { + if ctx.language.contains("++") + || ctx.language.contains("cpp") + || ctx.language.contains("cxx") + { + cmd.arg("-fno-exceptions"); + } + cmd.arg("-fno-unwind-tables"); + cmd.arg("-fno-asynchronous-unwind-tables"); + } + if let Some(platform) = ctx.platform && !platform.trim().is_empty() { @@ -250,7 +260,8 @@ fn build_object( } } - let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target); + let use_dcr_defaults = + ctx.cflags.is_empty() && !(ctx.freestanding || is_bare_metal_target(ctx.target)); if use_dcr_defaults { for flag in default_flags(ctx.profile) { cmd.arg(flag); diff --git a/src/utils/build.rs b/src/utils/build.rs index 83e99bf..c32a5a4 100644 --- a/src/utils/build.rs +++ b/src/utils/build.rs @@ -187,6 +187,19 @@ pub fn get_list_with_profile(config: &Config, field: &str, profile: &str) -> Vec out } +pub fn is_bare_metal_target(target: Option<&str>) -> bool { + if let Some(t) = target { + let lower = t.to_lowercase(); + lower.contains("none") + || lower.contains("-elf") + || lower.contains("eabi") + || lower.contains("baremetal") + || lower.contains("bare-metal") + } else { + false + } +} + pub fn get_bool_with_profile(config: &Config, field: &str, profile: &str, default: bool) -> bool { let base = config .get(&format!("build.{field}"))