diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..75ad20a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,51 @@
+---
+name: Bug Report
+about: Something is broken or behaves unexpectedly
+title: "[bug] "
+labels: bug
+assignees: ''
+---
+
+## Description
+
+
+
+## Steps to Reproduce
+
+1.
+2.
+3.
+
+## Expected Behavior
+
+
+
+## Actual Behavior
+
+
+
+```
+paste error output here
+```
+
+## Environment
+
+- **DCR version** (`dcr --version`):
+- **OS**:
+- **Compiler / toolchain** (`gcc --version` or `clang --version`):
+
+## Minimal Reproducible Project
+
+
+
+```toml
+# dcr.toml
+```
+
+```c
+// src/main.c
+```
+
+## Additional Context
+
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..8f7548f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,35 @@
+---
+name: Feature Request
+about: Suggest an idea or improvement
+title: "[feat] "
+labels: enhancement
+assignees: ''
+---
+
+## Problem / Motivation
+
+
+
+## Proposed Solution
+
+
+
+## Example
+
+
+
+```sh
+# example command
+```
+
+```toml
+# example config
+```
+
+## Alternatives Considered
+
+
+
+## Additional Context
+
+
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c2f7c96..e6a3949 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,6 +13,10 @@ on:
description: "Build Linux"
type: boolean
default: true
+ run_bsd:
+ description: "Build BSD"
+ type: boolean
+ default: true
run_macos:
description: "Build macOS"
type: boolean
@@ -190,6 +194,55 @@ jobs:
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
+# ── BSD ────────────────────────────────────────────────────────────────────
+ build-bsd:
+ if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }}
+ needs: [setup, notes]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: x86_64-unknown-freebsd
+ # При необходимости сюда можно докинуть x86_64-unknown-netbsd
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ needs.setup.outputs.tag }}
+
+ - name: Download Release Notes
+ uses: actions/download-artifact@v4
+ with:
+ name: release-notes
+ path: .github
+
+ - name: Install cargo-zigbuild
+ run: |
+ pip install cargo-zigbuild
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.target }}
+
+ - name: Build
+ run: cargo zigbuild --release --target ${{ matrix.target }}
+
+ - name: Prepare Artifact
+ run: |
+ ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}"
+ cp target/${{ matrix.target }}/release/dcr "$ASSET"
+ echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV"
+
+ - name: Upload Release Asset
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ needs.setup.outputs.tag }}
+ files: ${{ env.ASSET_NAME }}
+ body_path: .github/release-notes.md
+ prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
+
# ── Windows ────────────────────────────────────────────────────────────────
build-windows:
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
@@ -241,7 +294,7 @@ jobs:
if: |
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
- needs: [setup, build-linux, build-macos, build-windows]
+ needs: [setup, build-linux, build-macos, build-windows, build-bsd]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -258,7 +311,7 @@ jobs:
if: |
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_brew == 'true')
- needs: [setup, build-linux, build-macos, build-windows]
+ needs: [setup, build-linux, build-macos]
runs-on: ubuntu-latest
steps:
- name: Download release assets and compute sha256
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c98278..4c90d28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,56 @@
# Changelog
+## [0.6.8] - 2026-05-27
+
+Added:
+
+- `build.cxx_standard` — separate C++ language standard for `.cpp`/`.cxx`/`.cc` files.
+ When set, overrides `build.standard` for C++ sources. Example:
+
+ ```toml
+ [build]
+ language = ["c", "c++"]
+ standard = "gnu11"
+ cxx_standard = "gnu++17"
+ ```
+
+- Variable substitution for `cflags`, `ldflags`, and `include` directory entries.
+ All `{version}`, `{version_major}`, `{name}`, `{profile}` and other step variables
+ are now available. Example:
+
+ ```toml
+ [build]
+ cflags = ['-DPROJECT_VERSION="{version}"']
+ ```
+
+- FreeBSD build support in CI — new `run_bsd` workflow input and `build-bsd` job
+ that cross-compiles `x86_64-unknown-freebsd` via `cargo-zigbuild`.
+
+Changed:
+
+- License changed from MIT to **GPL-3.0-or-later**. All source files now carry
+ the GPL-3.0 header. Default license in `dcr new` / `dcr init` changed to
+ `"GPL-3.0-or-later"`. README, FAQ, and CONTRIBUTING.md updated accordingly.
+- `get_list_with_profile_and_target` no longer duplicates list values when
+ `inherit = true` and the profile/target value is identical to the base value.
+
+Fixed:
+
+- Multi-language `language` array (e.g. `["c", "c++", "asm"]`) no longer drops `c++`
+ during internal parsing — `.cxx`/`.cpp`/`.cc` source files are correctly discovered
+ and compiled.
+- `-std=` flag is no longer passed to C++ files when only `standard` (C standard) is
+ configured — uses `cxx_standard` when available, otherwise skips `-std=` for C++
+ sources, avoiding clang error `invalid argument '-std=c11' not allowed with 'C++'`.
+- Object file collision: source files with the same stem but different extensions
+ (e.g. `gdt.cxx` and `gdt.S`) no longer overwrite each other. Object filenames
+ now include the original source extension (e.g. `gdt.cxx.o`, `gdt.S.o`).
+- MSVC backend compile step now detects language per source file extension instead
+ of using the global `language` field, fixing mixed C/C++ compilation with MSVC.
+- Assembler `-x` flag in `unix_cc.rs` and `gen.rs` is now placed **before** the
+ source file, as required by clang/gcc.
+- `{version}` variables in `build.clean` are now correctly substituted.
+
## [0.6.7] - 2026-05-26
Added:
diff --git a/README.md b/README.md
index 67d8a2a..99a3130 100644
--- a/README.md
+++ b/README.md
@@ -1,283 +1,208 @@
-# DCR (Dexoron Cargo Realization)
+
-DCR is a utility for managing C/C++ projects in a Cargo-like style.
-Created and maintained by **Dexoron (Bezotechestvo Vladimir)**.
+# DCR — Dexoron Cargo Realization
-The current implementation is written in Rust.
+**A Cargo-style build tool for C/C++ projects**
-## Why DCR
+[](https://github.com/dexoron/dcr/actions/workflows/ci.yml)
+[](https://github.com/dexoron/dcr/releases/latest)
+[](https://github.com/dexoron/dcr/releases)
+
+[](https://aur.archlinux.org/packages/dcr)
+[](https://crates.io/crates/dcr)
+[](https://github.com/dexoron/homebrew-dexoron)
+
+[](https://github.com/dexoron/dcr/stargazers)
+[](LICENSE)
-- Unified project structure without manual setup
-- Simple commands for common tasks
-- Transparent compilation and predictable build profiles
+
-## Features
+---
-- Create a new project or initialize the current directory
-- Build a project with `debug` and `release` profiles
-- Run the compiled binary
-- Clean build artifacts
-- Generate IDE integration files (VS Code, CLion) and compilation databases
-- Generate a minimal C project template
-- Build static, shared libraries, UEFI PE32+ executables, and bare-metal ELF executables
-- Dependency management:
- - Git and Path dependencies with `dcr.lock`
- - Registry-based dependency resolution
-- Library packaging functionality (auto-generate `include/` and `lib/` for `type = "lib"` packages)
-- ASM projects with NASM/GAS or via GCC/Clang
-- Mixed language projects
-- Cross-compilation with `--target` (full triple support and short names)
-- Target-specific configurations and inheritance
-- Update the binary via `dcr --update` (GitHub Releases)
+## Why DCR?
-## Supported Platforms
+- **No boilerplate** — one config file, predictable structure
+- **Cargo-like workflow** — `build`, `run`, `clean`, `test`, `add`
+- **Cross-compilation** — full target triple support with short names
+- **IDE integration** — VS Code, CLion, `compile_commands.json` out of the box
+- **Dependencies** — path, git, and registry-based with lock file
-- Linux: x86_64/aarch64 (GNU and Musl)
-- macOS: x86_64/aarch64
-- Windows: x86_64/aarch64 (MSVC, GNU, LLVM)
+---
## Installation
-### Package Manager
-
-**Arch Linux**
+
+
+
Arch Linux (AUR)
+
```sh
-yay -S dcr # or paru and other AUR package managers
+yay -S dcr
```
-### From Source
+
```powershell
irm https://dcr.dexoron.su/install.ps1 | iex
```
-When executed, both scripts ask whether to:
+
+
+
+
From source
+
-- download a prebuilt binary from GitHub Releases
-- or build the project from `git`
+```sh
+git clone https://github.com/dexoron/dcr.git
+cd dcr && cargo build --release
+ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
+```
-Release assets:
+
+
+
-- `dcr-x86_64-unknown-linux-gnu`
-- `dcr-x86_64-apple-darwin`
-- `dcr-aarch64-apple-darwin`
-- `dcr-x86_64-pc-windows-msvc.exe`
-
-## Update
-
-- If DCR was installed from GitHub release assets, `install.sh`, `install.ps1`, or built manually:
- - use `dcr --update`
-- If DCR is installed via `pacman/AUR`:
- - update with your package manager: `paru/yay -Syu dcr` or `sudo pacman -Syu dcr`
- - `dcr --update` detects package-managed installs and asks you to update via package manager
+---
## Quick Start
-Create a new project:
-`dcr new hello`
-
-Or initialize the current directory (the directory must be empty):
-`dcr init`
-
-Project structure:
-
-```txt
-hello/
-- src/
-- - main.c
-- dcr.toml
-```
-
-Build and run the project:
-`dcr run` or `dcr run --release`
-
-## Commands
-
-### `dcr new `
-
-Creates a project with the specified name in the current directory.
-
-### `dcr init`
-
-Creates a project in the current directory. The project name is taken from the directory name. The directory must be empty.
-
-### `dcr setup`
-
-Initializes the registry system by reading `~/.dcr/config.toml` and verifying the availability of configured indices.
-
-### `dcr add `
-
-Adds a dependency to `dcr.toml`.
-If a registry is configured, `dcr` automatically finds the package.
-Git/Path syntax: `path:`, `github:`, `gitlab:`, `git:`, or a full URL.
-Use `--branch`, `--tag`, or `--rev` for Git dependencies.
-
-### `dcr build [profile]`
-
-Builds the project. If no profile is specified, `--debug` is used.
-Use `--force` to rebuild without cache, `--clean` to clean before build.
-
-### `dcr run [profile]`
-
-Builds the project and runs the binary. If no profile is specified, `--debug` is used.
-Use `--force` to rebuild without cache, `--clean` to clean before build.
-
-Run manually:
-`./target//`
-
-### `dcr clean`
-
-Removes the `target` directory in the project root.
-Use `dcr clean --all` in a workspace root to clean all member projects.
-
-### `dcr gen`
-
-Generates IDE integration files and build tools.
-
-- `dcr gen vscode`: VS Code workspace config
-- `dcr gen clion`: CLion project files
-- `dcr gen compile-commands`: `compile_commands.json` for clang tools
-
-### `dcr test`
-
-Runs the test suite.
-Use `dcr test --init` to create test files.
-
-## Build Profiles
-
-Two profiles are supported:
-
-- `--debug` (default) - built-in flags per compiler
-- `--release` - built-in flags per compiler
-
-Custom flags can be added in `dcr.toml` via `build.cflags` and `build.ldflags`.
-Profile-specific flags can be added via `[build.debug]` and `[build.release]`.
-You can also set `build.target` to override the output directory (profile-independent).
-You can set `build.platform` to pass `-march=` (GCC/Clang) or `/arch:*` (MSVC) where supported.
-Use `build.kind = "staticlib"` to build a static library instead of a binary.
-Use `build.kind = "sharedlib"` to build a shared library (`.so`/`.dylib`/`.dll`).
-Use `build.kind = "efi"` to build a UEFI PE32+ executable (`.efi`).
-Use `build.kind = "elf"` to build a bare-metal ELF executable.
-`dcr run` is only for `build.kind = "bin"` and will fail for libraries, EFI, and ELF.
-Use `build.exclude`/`build.include` to control source/header collection.
-Use `build.roots` and `build.src_disable` to replace the default `src/` root.
-Use `build.steps` and `build.post_steps` to run custom commands.
-Use `build.clean` for extra cleanup paths and `[run].cmd` to override run command.
-
-## Cross-compilation
-
-DCR supports cross-compilation via `--target ` or short names:
-
```sh
-dcr build --target linux --release # x86_64-unknown-linux-gnu
-dcr build --target macos --release # x86_64-apple-darwin
-dcr build --target windows --release # x86_64-pc-windows-msvc
-dcr run --target aarch64-linux-gnu
+dcr new hello
+cd hello
+dcr run
```
-Configure multiple targets and target-specific settings in `dcr.toml`:
+**Project structure:**
-```toml
-[build.targets]
-targets = ["linux", "windows"]
-
-[build.linux]
-compiler = "gcc"
-cflags = ["-O2"]
-
-[build.windows]
-compiler = "x86_64-w64-mingw32-gcc"
+```
+hello/
+├── dcr.toml # project config
+└── src/
+ └── main.c
```
-See [Cross-compilation guide](docs/dependencies-and-build/cross-compilation.md) for details.
+---
-## Configuration
-
-The main project file is `dcr.toml`.
-For multi-registry management, use `~/.dcr/config.toml`.
-
-Example `dcr.toml`:
+## dcr.toml Example
```toml
[package]
name = "hello"
version = "0.1.0"
-type = "app" # or "lib", "none"
[build]
language = "c"
standard = "c11"
compiler = "clang"
-kind = "bin" # "bin", "staticlib", "sharedlib", "efi", "elf"
-# Optional platform hint
-# platform = "x86_64"
-# Optional custom flags
-cflags = ["-Wall", "-Wextra"]
-ldflags = ["-lm"]
-#
-# Optional include/exclude:
-# exclude = ["src/vendor", "src/legacy/**"]
-# include = ["src/boot/arch/**"]
-
-[toolchain]
-# Optional tool overrides:
-# cc = "clang"
-# cxx = "clang++"
-# as = "as"
-# ar = "ar"
-# ld = "ld"
-# uic = "uic"
-# moc = "moc"
-# rcc = "rcc"
+kind = "bin"
[dependencies]
-my_lib = "path:./libs/my_lib"
-cool_lib = { git = "https://github.com/user/cool_lib", tag = "v1.0" }
```
-Workspace example:
-...
-[workspace]
-kernel = { path = "src/kernel", deps = ["core", "userspace"] }
-core = { path = "src/core", deps = ["userspace"] }
-userspace = { path = "src/userspace" }
+---
+## Base Commands
+
+| Command | Description |
+|---|---|
+| `dcr new ` | Create a new project |
+| `dcr init` | Initialize current directory |
+| `dcr build [--release]` | Build the project |
+| `dcr run [--release]` | Build and run |
+| `dcr clean` | Remove build artifacts |
+
+other in [docs/](docs/) and `dcr --help`
+
+---
+
+## Platforms
+
+
+
+---
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md). Before a PR:
+
+```sh
+cargo fmt --all
+cargo clippy --all-targets --all-features -- -D warnings
+cargo test
```
-Git and Path dependencies are supported. DCR will resolve them on build and generate `dcr.lock`.
+---
-Incremental build note: object files are rebuilt when source `.c/.cpp` or included header files change.
+
+GPL-3.0 — see [LICENSE](LICENSE)
+Made with ❤️ by [Dexoron](https://github.com/dexoron) and contributors.
-## Requirements
-- Rust toolchain (`rustc`, `cargo`) - for building DCR from source
-- C compiler (`clang`, `gcc`, or 'cl'(msvc))
-
-## Releases
-Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
-
-## Authors
-
-- **Dexoron (Bezotechestvo Vladimir)** — main@dexoron.su — [github.com/dexoron](https://github.com/dexoron)
-- **Kiedie** — [github.com/peoplemiau1](https://github.com/peoplemiau1)
-
-## License
-
-This project is licensed under the **GNU General Public License v3.0**. See `LICENSE`.
+
\ No newline at end of file
diff --git a/docs/configuration/build-section.md b/docs/configuration/build-section.md
index dd5f03e..18790d7 100644
--- a/docs/configuration/build-section.md
+++ b/docs/configuration/build-section.md
@@ -31,7 +31,9 @@ kind = "bin"
- `language` (string or array, required): `c`, `c++`, `cpp`, `cxx`, or `asm`.
Mixed languages can be specified as an array, for example `["c", "c++", "asm"]`.
-- `standard` (string, required): language standard passed to compiler.
+- `standard` (string, required): C language standard passed to compiler (e.g. `c11`, `c17`, `gnu11`).
+- `cxx_standard` (string, optional): C++ language standard passed to compiler for `.cpp`/`.cxx`/`.cc` files
+ (e.g. `c++17`, `gnu++17`, `c++20`). When set, overrides `standard` for C++ sources.
- `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`).
- `kind` (string, required): `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
- `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact.
@@ -42,7 +44,9 @@ kind = "bin"
When set, DCR automatically injects `--target=` into `cflags` (useful for clang cross-compilation).
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
- `cflags` (string array, optional): extra compile flags.
+ Supports variable substitution — see [Variable substitution](#variable-substitution) below.
- `ldflags` (string array, optional): extra link flags.
+ Supports variable substitution — see [Variable substitution](#variable-substitution) below.
- `ldscript` (string, optional): linker script path. Passed as `-T ` to the linker. Useful for bare-metal/embedded/freestanding targets.
- `exclude` (string array, optional): paths or glob patterns to skip during source/header collection.
- `include` (string array, optional): allowlist paths or globs that override `exclude`.
@@ -60,9 +64,30 @@ Notes for `exclude`/`include`:
- Use `include` to re-allow nested paths, for example exclude `src/boot` but allow `src/boot/arch/**`.
When using glob patterns in `exclude`, DCR converts them internally, so `exclude` globs still work as expected.
-### Build steps
+### Variable substitution
-Build steps run shell commands before compilation. Post steps run after linking.
+`cflags`, `ldflags`, and `include` directory entries support the same `{placeholder}` variables
+as build/post steps:
+
+| Variable | Description |
+|---|---|
+| `{profile}` | Build profile (`debug` / `release`) |
+| `{name}` | Package name |
+| `{version}` | Full package version (e.g. `0.2.1`) |
+| `{version_major}` | Major version number |
+| `{version_minor}` | Minor version number |
+| `{version_patch}` | Patch version number |
+| `{version_suffix}` | Version suffix (e.g. `-rc1`) |
+| `{version_suffix_dash}` | Suffix with leading dash (or empty) |
+
+Example:
+
+```toml
+[build]
+cflags = ['-DPROJECT_VERSION="{version}"', '-DVERSION_MAJOR={version_major}']
+```
+
+## Build steps
```toml
[[build.steps]]
diff --git a/docs/configuration/package-section.md b/docs/configuration/package-section.md
index 56bdb65..0c37415 100644
--- a/docs/configuration/package-section.md
+++ b/docs/configuration/package-section.md
@@ -4,9 +4,12 @@
```toml
[package]
-name = "my-app"
-version = "0.1.0"
-type = "app"
+name = "my-app"
+version = "0.1.0"
+type = "app"
+description = "My application"
+author = "John Doe"
+license = "MIT"
```
## Fields
@@ -14,6 +17,15 @@ type = "app"
- `name` (string, required): used as output artifact base name.
- `version` (string, required): used in `dcr.lock` project entry.
- `type` (string, optional): project type (`app`, `lib`, `none`).
+- `description` (string, optional): human-readable project description.
+- `author` (string, optional): single author name.
+- `authors` (array of strings, optional): list of authors.
+- `license` (string, optional): project license identifier (e.g. `MIT`, `GPL-3.0-or-later`).
+- `homepage` (string, optional): project homepage URL.
+- `repository` (string, optional): source repository URL.
+- `readme` (string, optional): path to README file.
+- `keywords` (array of strings, optional): project keywords.
+- `categories` (array of strings, optional): project categories.
## Notes
diff --git a/docs/reference-and-help/config-reference.md b/docs/reference-and-help/config-reference.md
index 231a50e..e88dcf7 100644
--- a/docs/reference-and-help/config-reference.md
+++ b/docs/reference-and-help/config-reference.md
@@ -4,88 +4,131 @@
```toml
[package]
-name = "string" # required
-version = "string" # required
+name = "string" # required
+version = "string" # required
+type = "string" # optional: "app", "lib", "none"
+description = "string" # optional
+author = "string" # optional
+authors = ["string", "..."] # optional
+license = "string" # optional
+homepage = "string" # optional
+repository = "string" # optional
+readme = "string" # optional
+keywords = ["string", "..."] # optional
+categories = ["string", "..."] # optional
[build]
-language = "c|c++|cpp|cxx|asm" # required (string or array)
-standard = "string" # required
-compiler = "string" # required
-kind = "bin|staticlib|sharedlib" # required
-filename = "string" # optional (custom output name without extension)
-extension = "string" # optional (custom extension without dot, e.g. "BIN")
-target = "string" # optional
-platform = "string" # optional
-cflags = ["string", "..."] # optional
-ldflags = ["string", "..."] # optional
-ldscript = "string" # optional (linker script, passed as -T)
-exclude = ["string", "..."] # optional
-include = ["string", "..."] # optional
-roots = ["string", "..."] # optional
-src_disable = false # optional
-pkg_config = ["string", "..."] # optional
-generated = ["string", "..."] # optional
-expect = ["string", "..."] # optional
-clean = ["string", "..."] # optional
+language = "c|c++|cpp|cxx|asm" # required (string or array)
+standard = "string" # required (e.g. "c11", "gnu11")
+cxx_standard = "string" # optional (e.g. "c++17", "gnu++17")
+compiler = "string" # required (e.g. "clang", "gcc")
+kind = "bin|staticlib|sharedlib|efi|elf" # required
+target = "string" # optional (cross-compilation target triple)
+platform = "string" # optional (architecture hint, -march)
+type = "string" # optional
+cflags = ["string", "..."] # optional (supports {version} etc.)
+ldflags = ["string", "..."] # optional (supports {version} etc.)
+ldscript = "string" # optional (linker script path)
+exclude = ["string", "..."] # optional (glob patterns)
+include = ["string", "..."] # optional (allowlist + -I dirs)
+roots = ["string", "..."] # optional (source roots)
+src_disable = false # optional (disable default src/)
+pkg_config = ["string", "..."] # optional (pkg-config packages)
+generated = ["string", "..."] # optional (generated file patterns)
+expect = ["string", "..."] # optional (expected artifact patterns)
+clean = ["string", "..."] # optional (extra clean paths)
+targets = ["string", "..."] # optional (build targets)
+filename = "string" # optional (custom output name)
+extension = "string" # optional (custom extension)
+inherit = true # optional (inherit base settings)
[[build.steps]]
-name = "string"
-in = "glob"
-out = "path or glob"
-cmd = "string"
+name = "string" # required
+in = "glob" # required
+out = "path" # required
+cmd = "string" # required
+
+# Inline steps (equivalent to [[build.steps]])
+steps = [
+ { name = "string", in = "glob", out = "path", cmd = "string" },
+]
[[build.post_steps]]
-name = "string"
-in = "glob"
-out = "path or glob"
-cmd = "string"
+name = "string" # required
+in = "glob" # required (or "target/...")
+out = "path" # required
+cmd = "string" # required
-# Inline array form (equivalent to [[build.steps]] / [[build.post_steps]])
-steps = [
- { name = "string", in = "glob", out = "path or glob", cmd = "string" },
-]
+# Inline post_steps (equivalent to [[build.post_steps]])
post_steps = [
- { name = "string", in = "glob", out = "path or glob", cmd = "string" },
+ { name = "string", in = "glob", out = "path", cmd = "string" },
]
+# Profile overrides (inherit all from [build])
[build.debug]
-cflags = ["string", "..."] # optional
-ldflags = ["string", "..."] # optional
+cflags = ["string", "..."] # optional
+ldflags = ["string", "..."] # optional
[build.release]
-cflags = ["string", "..."] # optional
-ldflags = ["string", "..."] # optional
+cflags = ["string", "..."] # optional
+ldflags = ["string", "..."] # optional
+
+# Target-specific overrides
+[build.linux]
+compiler = "string"
+cflags = ["string", "..."]
+
+[build.windows]
+compiler = "string"
+
+# Target + profile override
+[build.linux.release]
+cflags = ["string", "..."]
[toolchain]
-cc = "string" # optional
-cxx = "string" # optional
-as = "string" # optional
-ar = "string" # optional
-ld = "string" # optional
-uic = "string" # optional
-moc = "string" # optional
-rcc = "string" # optional
+cc = "string" # optional (C compiler override)
+cxx = "string" # optional (C++ compiler override)
+as = "string" # optional (assembler override)
+ar = "string" # optional (archiver override)
+ld = "string" # optional (linker override)
+uic = "string" # optional (Qt uic)
+moc = "string" # optional (Qt moc)
+rcc = "string" # optional (Qt rcc)
[run]
-cmd = "string" # optional
+cmd = "string" # required for `dcr run`
+
+# Debug run variant
+[run.debug]
+cmd = "string"
[workspace]
-name = { path = "string", deps = ["string", "..."] }
+[workspace.member_name]
+path = "string" # required
+deps = ["string", "..."] # optional
[dependencies]
-name = {
- path = "string", # required for each dependency
- include = ["string", "..."], # optional
- lib = ["string", "..."], # optional
- libs = ["string", "..."], # optional
-}
+[dependencies.dep_name]
+path = "string" # local path
+# or
+version = "string" # registry version
+# or
+git = "string" # git URL
+branch = "string" # optional (git branch)
+tag = "string" # optional (git tag)
+rev = "string" # optional (git commit)
+# common fields:
+include = ["string", "..."] # optional (include dirs)
+lib = ["string", "..."] # optional (lib dirs)
+libs = ["string", "..."] # optional (link libs)
+features = ["string", "..."] # optional (registry features)
```
## Validation rules
- `[package]`, `[build]`, `[dependencies]` must exist.
- Required string fields must be non-empty.
-- `build.kind` must be `bin`, `staticlib`, or `sharedlib`.
+- `build.kind` must be `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
- Dependency fields `include/lib/libs` must be string arrays when provided.
- `build.exclude`/`build.include` must be string arrays when provided.
- `build.roots` must be a string array when provided.
diff --git a/src/cli/build.rs b/src/cli/build.rs
index 720f3e4..b7476a7 100644
--- a/src/cli/build.rs
+++ b/src/cli/build.rs
@@ -230,11 +230,18 @@ fn get_list_with_profile_and_target(
// Custom from target/profile
if let Some(val) = get_config_value_raw(config, "build", field, profile, target) {
if let Some(_arr) = val.as_array() {
- let custom = parse_string_array(&val, &format!("build.{field}"))?;
- if inherit {
- out.extend(custom);
- } else {
- out = custom;
+ let is_base = inherit
+ && config
+ .get(&format!("build.{field}"))
+ .map(|v| v == &val)
+ .unwrap_or(false);
+ if !is_base {
+ let custom = parse_string_array(&val, &format!("build.{field}"))?;
+ if inherit {
+ out.extend(custom);
+ } else {
+ out = custom;
+ }
}
}
} else if inherit {
@@ -513,6 +520,8 @@ fn build_project_at(
let build_language = get_language_with_profile(&config, profile)?;
let build_standard =
get_string_with_profile_and_target(&config, "standard", profile, build_target);
+ let build_cxx_standard =
+ get_string_with_profile_and_target(&config, "cxx_standard", profile, build_target);
let build_kind = get_string_with_profile_and_target(&config, "kind", profile, build_target);
let build_platform =
get_string_with_profile_and_target(&config, "platform", profile, build_target);
@@ -716,12 +725,29 @@ fn build_project_at(
}
let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile);
+ let version_info = parse_version_info(&project_version);
+ let substitute = |s: &str| -> String {
+ s.replace("{profile}", profile)
+ .replace("{name}", &project_name)
+ .replace("{version}", &version_info.full)
+ .replace("{version_major}", &version_info.major)
+ .replace("{version_minor}", &version_info.minor)
+ .replace("{version_patch}", &version_info.patch)
+ .replace("{version_suffix}", &version_info.suffix)
+ .replace("{version_suffix_dash}", &version_info.suffix_dash)
+ };
+ let resolved_cflags: Vec = resolved_cflags.iter().map(|f| substitute(f)).collect();
+ let modified_ldflags: Vec =
+ resolved_ldflags.iter().map(|f| substitute(f)).collect();
+ let merged_include_dirs: Vec =
+ merged_include_dirs.iter().map(|f| substitute(f)).collect();
let ctx = BuildContext {
profile,
project_name: &project_name,
compiler: &resolved_compiler,
language: &build_language,
standard: &build_standard,
+ cxx_standard: &build_cxx_standard,
target: build_target,
target_dir: target_dir_binding.as_deref(),
kind: normalize_kind(&build_kind),
@@ -740,7 +766,7 @@ fn build_project_at(
lib_dirs: &resolved.lib_dirs,
libs: &resolved.libs,
cflags: &resolved_cflags,
- ldflags: &resolved_ldflags,
+ ldflags: &modified_ldflags,
output_filename: if output_filename.is_empty() {
None
} else {
@@ -763,7 +789,6 @@ fn build_project_at(
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 version_info = parse_version_info(&project_version);
let step_vars = StepVars {
profile,
version: &version_info.full,
diff --git a/src/cli/gen.rs b/src/cli/gen.rs
index 2edd2de..b35aaba 100644
--- a/src/cli/gen.rs
+++ b/src/cli/gen.rs
@@ -171,6 +171,7 @@ pub struct ProjectInfo {
pub profile: String,
pub language: String,
pub standard: String,
+ pub cxx_standard: String,
pub compiler: String,
pub kind: String,
pub sources: Vec,
@@ -228,6 +229,7 @@ fn collect_project_info_inner(root: &Path, profile: &str) -> Result Result Result String {
" \"standard\": {},\n",
json_str(&info.standard)
));
+ out.push_str(&format!(
+ " \"cxx_standard\": {},\n",
+ json_str(&info.cxx_standard)
+ ));
out.push_str(&format!(
" \"compiler\": {},\n",
json_str(&info.compiler)
@@ -575,6 +583,13 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
};
cmd.push(compiler.to_string());
cmd.push("-c".to_string());
+
+ // ASM x flag — must be before source file
+ if let Some(flag) = asm_lang_flag(source) {
+ cmd.push("-x".to_string());
+ cmd.push(flag.to_string());
+ }
+
cmd.push(source.to_string());
// Object path (for -o, approximate — not critical for IntelliSense)
@@ -595,15 +610,18 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
cmd.push("-fPIC".to_string());
}
- // ASM x flag
- if let Some(flag) = asm_lang_flag(source) {
- cmd.push("-x".to_string());
- cmd.push(flag.to_string());
- }
-
// -std=
if !info.standard.is_empty() && info.language.to_lowercase() != "asm" {
- cmd.push(format!("-std={}", info.standard));
+ let ext = Path::new(source)
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("");
+ let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
+ if is_cpp && !info.cxx_standard.is_empty() {
+ cmd.push(format!("-std={}", info.cxx_standard));
+ } else if !is_cpp {
+ cmd.push(format!("-std={}", info.standard));
+ }
}
// Default profile flags (mirrors unix_cc.rs defaults)
diff --git a/src/core/builder/common.rs b/src/core/builder/common.rs
index 3afdc18..af956ce 100644
--- a/src/core/builder/common.rs
+++ b/src/core/builder/common.rs
@@ -262,7 +262,22 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
stripped.to_path_buf()
};
let mut out = obj_dir.join(rel);
- out.set_extension(obj_ext.trim_start_matches('.'));
+ let ext = out
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("")
+ .to_string();
+ let obj_ext = obj_ext.trim_start_matches('.');
+ if !ext.is_empty() {
+ let stem = out
+ .file_stem()
+ .and_then(|s| s.to_str())
+ .unwrap_or("")
+ .to_string();
+ out.set_file_name(format!("{stem}.{ext}.{obj_ext}"));
+ } else {
+ out.set_extension(obj_ext);
+ }
out.to_string_lossy().to_string()
}
@@ -379,28 +394,28 @@ mod tests {
fn object_path_basic() {
let obj_dir = Path::new("target/debug/obj");
let result = object_path(obj_dir, "./src/main.c", "o");
- assert_eq!(result, "target/debug/obj/main.o");
+ assert_eq!(result, "target/debug/obj/main.c.o");
}
#[test]
fn object_path_nested() {
let obj_dir = Path::new("target/debug/obj");
let result = object_path(obj_dir, "./src/core/utils.c", "o");
- assert_eq!(result, "target/debug/obj/core/utils.o");
+ assert_eq!(result, "target/debug/obj/core/utils.c.o");
}
#[test]
fn object_path_no_prefix() {
let obj_dir = Path::new("target/debug/obj");
let result = object_path(obj_dir, "src/main.c", "o");
- assert_eq!(result, "target/debug/obj/main.o");
+ assert_eq!(result, "target/debug/obj/main.c.o");
}
#[test]
fn object_path_msvc_ext() {
let obj_dir = Path::new("target/debug/obj");
let result = object_path(obj_dir, "./src/main.c", "obj");
- assert_eq!(result, "target/debug/obj/main.obj");
+ assert_eq!(result, "target/debug/obj/main.c.obj");
}
#[test]
diff --git a/src/core/builder/gas.rs b/src/core/builder/gas.rs
index 65e3fda..26f82a5 100644
--- a/src/core/builder/gas.rs
+++ b/src/core/builder/gas.rs
@@ -25,7 +25,7 @@ use std::time::Instant;
pub fn build(ctx: &BuildContext) -> Result {
let lang = ctx.language.to_lowercase();
- if lang.split('+').any(|p| p.trim() != "asm") {
+ if lang.split(',').any(|p| p.trim() != "asm") {
return Err("GAS backend requires build.language = \"asm\"".to_string());
}
let assembler = if ctx.compiler.is_empty() {
diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs
index 7114fba..cfbaaf0 100644
--- a/src/core/builder/mod.rs
+++ b/src/core/builder/mod.rs
@@ -27,7 +27,7 @@ pub struct BuildContext<'a> {
pub compiler: &'a str,
pub language: &'a str,
pub standard: &'a str,
- #[allow(dead_code)]
+ pub cxx_standard: &'a str,
pub target: Option<&'a str>,
pub target_dir: Option<&'a str>,
pub kind: &'a str,
diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs
index cfe4c5e..55be925 100644
--- a/src/core/builder/msvc.rs
+++ b/src/core/builder/msvc.rs
@@ -144,8 +144,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result, String>
fn source_extensions(language: &str) -> Vec<&str> {
let mut out = Vec::new();
- for part in language.split('+') {
- let lang = part.trim().to_lowercase();
+ for part in language.split(',').map(|s| s.trim()) {
+ let lang = part.to_lowercase();
match lang.as_str() {
"c" => out.extend(["c"]),
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
@@ -243,18 +243,26 @@ fn build_object(
let mut cmd = Command::new(compiler);
cmd.arg("/nologo");
- match ctx.language.to_lowercase().as_str() {
- "c" => {
- cmd.arg("/TC");
- }
- "c++" | "cpp" | "cxx" => {
- cmd.arg("/TP");
- }
- _ => return Err("Unsupported language".to_string()),
+ let ext = Path::new(source)
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("");
+ let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
+ if is_cpp {
+ cmd.arg("/TP");
+ } else {
+ cmd.arg("/TC");
}
- if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
- let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
+ let std_val = if is_cpp && !ctx.cxx_standard.is_empty() {
+ ctx.cxx_standard
+ } else if !is_cpp && !ctx.standard.is_empty() {
+ ctx.standard
+ } else {
+ ""
+ };
+ if !std_val.is_empty() {
+ let std_flag = msvc_standard_flag(if is_cpp { "c++" } else { "c" }, std_val)?;
cmd.arg(std_flag);
}
diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs
index c820773..b4c7458 100644
--- a/src/core/builder/nasm.rs
+++ b/src/core/builder/nasm.rs
@@ -25,7 +25,7 @@ use std::time::Instant;
pub fn build(ctx: &BuildContext) -> Result {
let lang = ctx.language.to_lowercase();
- if lang.split('+').any(|p| p.trim() != "asm") {
+ if lang.split(',').any(|p| p.trim() != "asm") {
return Err("NASM backend requires build.language = \"asm\"".to_string());
}
let assembler = if ctx.compiler.is_empty() {
diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs
index fca4cc6..7f11d8f 100644
--- a/src/core/builder/unix_cc.rs
+++ b/src/core/builder/unix_cc.rs
@@ -133,8 +133,8 @@ pub(crate) fn collect_sources(ctx: &BuildContext) -> Result, String>
fn source_extensions(language: &str) -> Vec<&str> {
let mut out = Vec::new();
- for part in language.split('+') {
- let lang = part.trim().to_lowercase();
+ for part in language.split(',').map(|s| s.trim()) {
+ let lang = part.to_lowercase();
match lang.as_str() {
"c" => out.extend(["c"]),
"c++" | "cpp" | "cxx" => out.extend(["cpp", "cxx", "cc"]),
@@ -210,16 +210,18 @@ fn build_object(
}
let mut cmd = Command::new(compiler);
- cmd.arg("-c").arg(source).arg("-o").arg(obj_path);
-
- if ctx.kind == "sharedlib" {
- cmd.arg("-fPIC");
- }
+ cmd.arg("-c");
if let Some(flag) = asm_lang_flag(source) {
cmd.arg("-x").arg(flag);
}
+ cmd.arg(source).arg("-o").arg(obj_path);
+
+ if ctx.kind == "sharedlib" {
+ cmd.arg("-fPIC");
+ }
+
if let Some(platform) = ctx.platform
&& !platform.trim().is_empty()
{
@@ -227,7 +229,16 @@ fn build_object(
}
if !ctx.standard.is_empty() && ctx.language.to_lowercase() != "asm" {
- cmd.arg(format!("-std={}", ctx.standard));
+ let ext = Path::new(source)
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("");
+ let is_cpp = matches!(ext, "cpp" | "cxx" | "cc");
+ if is_cpp && !ctx.cxx_standard.is_empty() {
+ cmd.arg(format!("-std={}", ctx.cxx_standard));
+ } else if !is_cpp {
+ cmd.arg(format!("-std={}", ctx.standard));
+ }
}
let use_dcr_defaults = ctx.cflags.is_empty() && !is_bare_metal_target(ctx.target);
diff --git a/src/core/config.rs b/src/core/config.rs
index 4a1ba02..c4bdcc8 100644
--- a/src/core/config.rs
+++ b/src/core/config.rs
@@ -100,6 +100,8 @@ pub struct PackageConfig {
pub struct BuildConfig {
pub language: LanguageConfig,
pub standard: String,
+ #[serde(default)]
+ pub cxx_standard: Option,
pub compiler: String,
#[serde(default)]
pub kind: Option,
diff --git a/src/utils/build.rs b/src/utils/build.rs
index 51ad455..6f21ed4 100644
--- a/src/utils/build.rs
+++ b/src/utils/build.rs
@@ -190,7 +190,7 @@ pub fn parse_language_value(value: &toml::Value, key: &str) -> Result String {
let parts: Vec = language
- .split('+')
+ .split(',')
.map(|p| p.trim().to_lowercase())
.filter(|p| !p.is_empty())
.collect();