From acdd1cc175c31ebd491ecfc35b3bf41207b59d5e Mon Sep 17 00:00:00 2001 From: Dexoron Date: Wed, 27 May 2026 20:04:01 +0800 Subject: [PATCH] feat: enhance build configuration with C++ standard support and variable substitution - Added `cxx_standard` field to the build configuration for specifying C++ standards. - Updated documentation to reflect new `cxx_standard` and variable substitution capabilities in `cflags`, `ldflags`, and `include`. - Implemented logic to handle C++ standard flags in the build process. - Modified object file naming to include source file extensions. - Updated various builder modules to support new language parsing logic. - Introduced issue and feature request templates for better community engagement. --- .github/ISSUE_TEMPLATE/bug_report.md | 51 +++ .github/ISSUE_TEMPLATE/feature_request.md | 35 ++ .github/workflows/release.yml | 57 ++- CHANGELOG.md | 51 +++ README.md | 367 ++++++++------------ docs/configuration/build-section.md | 31 +- docs/configuration/package-section.md | 18 +- docs/reference-and-help/config-reference.md | 153 +++++--- src/cli/build.rs | 39 ++- src/cli/gen.rs | 32 +- src/core/builder/common.rs | 25 +- src/core/builder/gas.rs | 2 +- src/core/builder/mod.rs | 2 +- src/core/builder/msvc.rs | 32 +- src/core/builder/nasm.rs | 2 +- src/core/builder/unix_cc.rs | 27 +- src/core/config.rs | 2 + src/utils/build.rs | 4 +- 18 files changed, 602 insertions(+), 328 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md 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 +[![CI](https://github.com/dexoron/dcr/actions/workflows/ci.yml/badge.svg)](https://github.com/dexoron/dcr/actions/workflows/ci.yml) +[![GitHub Release](https://img.shields.io/github/v/release/dexoron/dcr)](https://github.com/dexoron/dcr/releases/latest) +[![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey)](https://github.com/dexoron/dcr/releases) +
+[![AUR](https://img.shields.io/aur/version/dcr)](https://aur.archlinux.org/packages/dcr) +[![Crates.io](https://img.shields.io/crates/v/dcr)](https://crates.io/crates/dcr) +[![Homebrew](https://img.shields.io/badge/homebrew-dexoron%2Fdexoron-orange)](https://github.com/dexoron/homebrew-dexoron) +
+[![GitHub Stars](https://img.shields.io/github/stars/dexoron/dcr?style=flat)](https://github.com/dexoron/dcr/stargazers) +[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](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 +
macOS/Linux(GNU) (Homebrew) ```sh -git clone https://github.com/dexoron/dcr.git -cd dcr -cargo build --release -mkdir -p ~/.local/bin -ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr +brew tap dexoron/dexoron +brew install dcr ``` -### Via `install.sh` (Linux/macOS) +
Cargo (crates.io) + +```sh +cargo install dcr +``` + +
Linux / macOS (script) ```sh curl -fsSL https://dcr.dexoron.su/install.sh | bash ``` -### Via `install.ps1` (Windows) +
Windows (PowerShell) ```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 + +
+ +| OS | x86_64 | aarch64 | armv7 | i686 | riscv64 | GNU | Musl | MSVC | MinGW | +|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Linux | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | +| macOS | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Windows | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | +| FreeBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ | +| OpenBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ | +| NetBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ | + +✅ officially supported · ⚠️ community / best-effort · ❌ not supported + +
+ +--- + +## Documentation + +Full docs at **[dcr.dexoron.su](https://dcr.dexoron.su)** or in the [`docs/`](docs/) directory. + +--- + +## Contributors + +
+ +| | Name | Role | GitHub | +|:---:|---|---|---| +| 👤 | Dexoron (Bezotechestvo Vladimir) | Maintainer, Creator | [@dexoron](https://github.com/dexoron) | +| 👤 | Kai | Maintainer | [@peoplemiau1](https://github.com/peoplemiau1) | + +
+ +--- + +## 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();