mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 21:39:36 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c22d2dbddc | |||
| 83061cc460 | |||
| 467e64aa4f | |||
| 7817834790 | |||
| 9fe2604154 | |||
| cb8a3b2001 | |||
| e0fd729635 | |||
| e70edf8ae4 | |||
| 210e82f026 | |||
| ff12ca2758 | |||
| 468491b5b1 | |||
| 0f48151b68 | |||
| e87e0cbc47 | |||
| fff8baa3c9 | |||
| ad0d4cdcb7 | |||
| 45806f193e |
13
.github/workflows/aur.yml
vendored
13
.github/workflows/aur.yml
vendored
@@ -1,6 +1,11 @@
|
|||||||
name: AUR Publish
|
name: AUR Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Tag version without v prefix (e.g. 0.2.2)"
|
||||||
|
required: true
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
@@ -11,6 +16,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
publish-aur:
|
publish-aur:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
environment: aur
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -20,7 +26,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||||
|
PKGVER="${{ inputs.version }}"
|
||||||
|
else
|
||||||
PKGVER="${GITHUB_REF_NAME#v}"
|
PKGVER="${GITHUB_REF_NAME#v}"
|
||||||
|
fi
|
||||||
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/v${PKGVER}.tar.gz"
|
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/v${PKGVER}.tar.gz"
|
||||||
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
|
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
|
||||||
|
|
||||||
@@ -57,7 +67,8 @@ jobs:
|
|||||||
build() {
|
build() {
|
||||||
cd "\$srcdir/\$pkgname-\$pkgver"
|
cd "\$srcdir/\$pkgname-\$pkgver"
|
||||||
export CARGO_PROFILE_RELEASE_LTO=false
|
export CARGO_PROFILE_RELEASE_LTO=false
|
||||||
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
|
export RUSTFLAGS="\${RUSTFLAGS:-}"
|
||||||
|
export RUSTFLAGS="\${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
|
||||||
cargo build --release --locked
|
cargo build --release --locked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
@@ -9,6 +9,34 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
notes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Prepare Release Notes
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tag="${GITHUB_REF_NAME#v}"
|
||||||
|
notes_file=".github/release-notes.md"
|
||||||
|
if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then
|
||||||
|
awk -v ver="$tag" '
|
||||||
|
$0 ~ "^## \\[" ver "\\]" {found=1; next}
|
||||||
|
found && $0 ~ "^## \\[" {exit}
|
||||||
|
found {print}
|
||||||
|
' CHANGELOG.md | sed -e '${/^$/d;}' > "$notes_file"
|
||||||
|
else
|
||||||
|
echo "No CHANGELOG section for $tag." > "$notes_file"
|
||||||
|
git log -1 --pretty=format:"%s%n%n%b" >> "$notes_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Notes Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes
|
||||||
|
path: .github/release-notes.md
|
||||||
|
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -28,8 +56,14 @@ jobs:
|
|||||||
asset_name: dcr-x86_64-pc-windows-msvc.exe
|
asset_name: dcr-x86_64-pc-windows-msvc.exe
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
needs: notes
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Download Release Notes
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes
|
||||||
|
path: .github
|
||||||
|
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -55,3 +89,4 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ${{ matrix.asset_name }}
|
files: ${{ matrix.asset_name }}
|
||||||
|
body_path: .github/release-notes.md
|
||||||
|
|||||||
79
CHANGELOG.md
79
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.2.5] - 2026-02-26
|
||||||
|
Added:
|
||||||
|
- Incremental builds (object caching by mtime)
|
||||||
|
- `build.kind` with `staticlib` support
|
||||||
|
- Custom output directory via `build.target`
|
||||||
|
|
||||||
|
## [0.2.4] - 2026-02-22
|
||||||
|
Added:
|
||||||
|
- Custom build flags: `build.cflags` and `build.ldflags`
|
||||||
|
- Recursive source discovery inside `src/`
|
||||||
|
- Path dependencies with auto include/lib resolution and `dcr.lock` generation (experimental)
|
||||||
|
- Add docs/index.md and docs/dcr-toml.md
|
||||||
|
|
||||||
|
## [0.2.3] - 2026-02-21
|
||||||
|
Added:
|
||||||
|
- Modular builders for `gcc`, `clang`, `msvc`
|
||||||
|
- Platform-specific binary path generation (Linux/macOS/Windows)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Build configuration moved to `[build]` (`language`, `standard`, `compiler`)
|
||||||
|
- `dcr.toml` formatting now includes the `[build]` section
|
||||||
|
- Build uses built-in `debug/release` flags per compiler
|
||||||
|
- Build compiles all `*.c/*.cpp` files in `src/` into a single binary (no incremental build)
|
||||||
|
- Updated `dcr.toml` examples in documentation
|
||||||
|
|
||||||
## [0.2.2] - 2026-02-20
|
## [0.2.2] - 2026-02-20
|
||||||
Changed:
|
Changed:
|
||||||
- Reworked `dcr.toml` handling: added read, validation, and edit through `core::config`
|
- Reworked `dcr.toml` handling: added read, validation, and edit through `core::config`
|
||||||
@@ -14,40 +39,40 @@ Changed:
|
|||||||
- Translated installer script messages in `install.sh` and `install.ps1` to English
|
- Translated installer script messages in `install.sh` and `install.ps1` to English
|
||||||
|
|
||||||
## [0.2.0] - 2026-02-17
|
## [0.2.0] - 2026-02-17
|
||||||
Изменено:
|
Changed:
|
||||||
- Проект переведен с Python на Rust
|
- Project migrated from Python to Rust
|
||||||
- CLI и команды (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) перенесены на Rust
|
- CLI and commands (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) ported to Rust
|
||||||
- Обновлен флаг `--update` - добавлена поддержка GNU\Linux, Windows, MacOS
|
- Updated `--update` flag: added support for GNU/Linux, Windows, macOS
|
||||||
- Обновлены `README.md`, `CONTRIBUTING.md`, `install.sh` под Rust-реализацию
|
- Updated `README.md`, `CONTRIBUTING.md`, and `install.sh` for the Rust implementation
|
||||||
- Добавлен `install.ps1` для Windows
|
- Added `install.ps1` for Windows
|
||||||
- Обновлен `install.sh` для GNU/Linux и MacOS
|
- Updated `install.sh` for GNU/Linux and macOS
|
||||||
|
|
||||||
Добавлено:
|
Added:
|
||||||
- Добавлен `install.ps1` для Windows
|
- Added `install.ps1` for Windows
|
||||||
- Поддержка GNU\Linux, Windows, MacOS(x86_64/arm)
|
- Support for GNU/Linux, Windows, macOS (x86_64/arm)
|
||||||
|
|
||||||
ВАЖНО:
|
Important:
|
||||||
- Код перенесен с помощью нейронных сетей, в будущих версиях будет баг-фикс и изменение логики
|
- Code was ported with neural networks; future versions will include bug fixes and logic changes
|
||||||
|
|
||||||
## [0.1.2] - 2026-02-12
|
## [0.1.2] - 2026-02-12
|
||||||
Добавлено:
|
Added:
|
||||||
- Команда обновления
|
- Update command
|
||||||
- Скрипт установки `install.sh` и инструкции по установке в README
|
- `install.sh` install script and README instructions
|
||||||
- Флаг `--version` для вывода версии
|
- `--version` flag
|
||||||
|
|
||||||
Изменено:
|
Changed:
|
||||||
- Улучшен консольный вывод команд, добавлены цвета и обновлена справка `--help`
|
- Improved CLI output, added colors, and updated `--help`
|
||||||
- Обновлен запуск проекта и обработка сборки в `run.py`/`build.py`
|
- Updated project run/build handling in `run.py`/`build.py`
|
||||||
|
|
||||||
## [0.1.1] - 2026-02-11
|
## [0.1.1] - 2026-02-11
|
||||||
Изменено:
|
Changed:
|
||||||
- Обновлена справка `--help`
|
- Updated `--help`
|
||||||
- `main.py` теперь корректно запускается при прямом выполнении
|
- `main.py` now runs correctly when executed directly
|
||||||
|
|
||||||
## [0.1.0] - 2026-02-11
|
## [0.1.0] - 2026-02-11
|
||||||
Первый публичный релиз.
|
First public release.
|
||||||
|
|
||||||
Добавлено:
|
Added:
|
||||||
- Базовые команды `new`, `init`, `build`, `run`, `clean`
|
- Base commands `new`, `init`, `build`, `run`, `clean`
|
||||||
- Профили сборки `debug` и `release`
|
- Build profiles `debug` and `release`
|
||||||
- Шаблоны `dcr.toml` и `src/main.c`
|
- `dcr.toml` and `src/main.c` templates
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Вклад в проект
|
# Contributing
|
||||||
|
|
||||||
Спасибо за интерес к проекту.
|
Thanks for your interest in the project.
|
||||||
|
|
||||||
## Требования
|
## Requirements
|
||||||
|
|
||||||
- Использовать Rust stable.
|
- Use Rust stable.
|
||||||
- Перед PR код должен проходить форматирование, линт и сборку.
|
- Before opening a PR, the code must pass formatting, linting, and build.
|
||||||
|
|
||||||
## Быстрый старт
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/dexoron/dcr.git
|
git clone https://github.com/dexoron/dcr.git
|
||||||
@@ -15,16 +15,16 @@ cd dcr
|
|||||||
cargo check
|
cargo check
|
||||||
```
|
```
|
||||||
|
|
||||||
## Стиль кода
|
## Code Style
|
||||||
|
|
||||||
- Форматируйте код через `cargo fmt`.
|
- Format code with `cargo fmt`.
|
||||||
- Проверяйте предупреждения через `cargo clippy`.
|
- Check warnings with `cargo clippy`.
|
||||||
- Держите функции небольшими и сфокусированными.
|
- Keep functions small and focused.
|
||||||
- Предпочитайте понятные имена вместо "хитрых".
|
- Prefer clear names over clever ones.
|
||||||
|
|
||||||
## Проверки
|
## Checks
|
||||||
|
|
||||||
Перед PR запускайте:
|
Before PR, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
@@ -32,26 +32,26 @@ cargo clippy --all-targets --all-features -- -D warnings
|
|||||||
cargo check
|
cargo check
|
||||||
```
|
```
|
||||||
|
|
||||||
## CI/CD и релизы
|
## CI/CD and Releases
|
||||||
|
|
||||||
- CI: `.github/workflows/ci.yml`
|
- CI: `.github/workflows/ci.yml`
|
||||||
- Release: `.github/workflows/release.yml`
|
- Release: `.github/workflows/release.yml`
|
||||||
- Релиз запускается по тегу `v*` и публикует бинарники для:
|
- Releases are triggered by tags `v*` and publish binaries for:
|
||||||
- `x86_64-unknown-linux-gnu`
|
- `x86_64-unknown-linux-gnu`
|
||||||
- `x86_64-apple-darwin`
|
- `x86_64-apple-darwin`
|
||||||
- `aarch64-apple-darwin`
|
- `aarch64-apple-darwin`
|
||||||
- `x86_64-pc-windows-msvc`
|
- `x86_64-pc-windows-msvc`
|
||||||
|
|
||||||
## Коммиты и PR
|
## Commits and PRs
|
||||||
|
|
||||||
- Делайте коммиты понятными.
|
- Write clear commit messages.
|
||||||
- В PR указывайте краткое описание и мотивацию изменений.
|
- In PRs, include a short description and rationale.
|
||||||
- Обновляйте документацию при изменении поведения.
|
- Update documentation when behavior changes.
|
||||||
|
|
||||||
## Вопросы
|
## Questions
|
||||||
|
|
||||||
Если что-то непонятно, открой issue или задайте вопрос в PR.
|
If anything is unclear, open an issue or ask in the PR.
|
||||||
|
|
||||||
Также можно задать вопрос напрямую:
|
You can also reach out directly:
|
||||||
- TG: @dexoron
|
- TG: @dexoron
|
||||||
- DS: dexoron
|
- DS: dexoron
|
||||||
|
|||||||
74
Cargo.lock
generated
74
Cargo.lock
generated
@@ -26,6 +26,15 @@ version = "2.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
@@ -60,16 +69,46 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"self-replace",
|
"self-replace",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -173,6 +212,16 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -900,6 +949,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -1162,6 +1222,12 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -1198,6 +1264,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
@@ -8,4 +8,5 @@ build = "build.rs"
|
|||||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
self-replace = "1"
|
self-replace = "1"
|
||||||
|
sha2 = "0.10"
|
||||||
toml = "1.0.3"
|
toml = "1.0.3"
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -107,15 +107,20 @@ Builds the project. If no profile is specified, `--debug` is used.
|
|||||||
Builds the project and runs the binary. If no profile is specified, `--debug` is used.
|
Builds the project and runs the binary. If no profile is specified, `--debug` is used.
|
||||||
|
|
||||||
Run manually:
|
Run manually:
|
||||||
`./target/<profile>/main`
|
`./target/<profile>/<name>`
|
||||||
|
|
||||||
### `dcr clean`
|
### `dcr clean`
|
||||||
Removes the `target` directory in the project root.
|
Removes the `target` directory in the project root.
|
||||||
|
|
||||||
## Build Profiles
|
## Build Profiles
|
||||||
Two profiles are supported:
|
Two profiles are supported:
|
||||||
- `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
- `--debug` (default) - built-in flags per compiler
|
||||||
- `--release` - flags: `-O3 -DNDEBUG -march=native`
|
- `--release` - built-in flags per compiler
|
||||||
|
|
||||||
|
Custom flags can be added in `dcr.toml` via `build.cflags` and `build.ldflags`.
|
||||||
|
You can also set `build.target` to override the output directory (profile-independent).
|
||||||
|
Use `build.kind = "staticlib"` to build a static library instead of a binary.
|
||||||
|
`dcr run` is only for `build.kind = "bin"` and will fail for `staticlib`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
The main project file is `dcr.toml`.
|
The main project file is `dcr.toml`.
|
||||||
@@ -126,15 +131,25 @@ Example `dcr.toml`:
|
|||||||
[package]
|
[package]
|
||||||
name = "hello"
|
name = "hello"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
language = "c"
|
language = "c"
|
||||||
|
standard = "c11"
|
||||||
compiler = "clang"
|
compiler = "clang"
|
||||||
|
# Optional custom flags
|
||||||
|
cflags = ["-Wall", "-Wextra"]
|
||||||
|
ldflags = ["-lm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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` files change. Header dependency tracking is not implemented yet.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Rust toolchain (`rustc`, `cargo`) - for building DCR from source
|
- Rust toolchain (`rustc`, `cargo`) - for building DCR from source
|
||||||
- C compiler (`clang`, `gcc`, or others)
|
- C compiler (`clang`, `gcc`, or 'cl'(msvc))
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
|
Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
|
||||||
|
|||||||
6
TODO.md
Normal file
6
TODO.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
- Add incremental builds
|
||||||
|
- Add header dependency tracking for incremental builds (GCC/Clang: -MMD, MSVC: /showIncludes)
|
||||||
|
- Add external package managers (Conan, vcpkg)
|
||||||
|
- Support building static libraries (ar .a)
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Вклад в проект
|
|
||||||
|
|
||||||
Спасибо за интерес к проекту.
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
- Использовать Rust stable.
|
|
||||||
- Перед PR код должен проходить форматирование, линт и сборку.
|
|
||||||
|
|
||||||
## Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/dexoron/dcr.git
|
|
||||||
cd dcr
|
|
||||||
cargo check
|
|
||||||
```
|
|
||||||
|
|
||||||
## Стиль кода
|
|
||||||
|
|
||||||
- Форматируйте код через `cargo fmt`.
|
|
||||||
- Проверяйте предупреждения через `cargo clippy`.
|
|
||||||
- Держите функции небольшими и сфокусированными.
|
|
||||||
- Предпочитайте понятные имена вместо "хитрых".
|
|
||||||
|
|
||||||
## Проверки
|
|
||||||
|
|
||||||
Перед PR запускайте:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --all
|
|
||||||
cargo clippy --all-targets --all-features -- -D warnings
|
|
||||||
cargo check
|
|
||||||
```
|
|
||||||
|
|
||||||
## CI/CD и релизы
|
|
||||||
|
|
||||||
- CI: `.github/workflows/ci.yml`
|
|
||||||
- Release: `.github/workflows/release.yml`
|
|
||||||
- Релиз запускается по тегу `v*` и публикует бинарники для:
|
|
||||||
- `x86_64-unknown-linux-gnu`
|
|
||||||
- `x86_64-apple-darwin`
|
|
||||||
- `aarch64-apple-darwin`
|
|
||||||
- `x86_64-pc-windows-msvc`
|
|
||||||
|
|
||||||
## Коммиты и PR
|
|
||||||
|
|
||||||
- Делайте коммиты понятными.
|
|
||||||
- В PR указывайте краткое описание и мотивацию изменений.
|
|
||||||
- Обновляйте документацию при изменении поведения.
|
|
||||||
|
|
||||||
## Вопросы
|
|
||||||
|
|
||||||
Если что-то непонятно, открой issue или задайте вопрос в PR.
|
|
||||||
|
|
||||||
Также можно задать вопрос напрямую:
|
|
||||||
- TG: @dexoron
|
|
||||||
- DS: dexoron
|
|
||||||
102
docs/FAQ_ru.md
102
docs/FAQ_ru.md
@@ -1,102 +0,0 @@
|
|||||||
# FAQ
|
|
||||||
|
|
||||||
## В чем суть проекта?
|
|
||||||
`dcr` - это инструмент для управления C/C++ проектами и их зависимостями.
|
|
||||||
Он вдохновлен Cargo, но не является аналогом Makefile, CMake или других классических систем сборки.
|
|
||||||
|
|
||||||
Цель проекта - дать более простую и удобную в настройке среду для разработки.
|
|
||||||
|
|
||||||
## Как разрабатывается проект?
|
|
||||||
Проект написан на Rust и в основном развивается одним разработчиком - Dexoron.
|
|
||||||
|
|
||||||
В работе используются ИИ-инструменты, но они не пишут проект целиком: их роль - помощь с отдельными задачами (например, советы, проверка формулировок, переводы и т.д.).
|
|
||||||
|
|
||||||
## Какие инструменты используются для разработки?
|
|
||||||
Основной стек разработки:
|
|
||||||
- `RustRover` 2025 - IDE для разработки.
|
|
||||||
- `Codex` (`GPT-5.3-Codex (medium)`) - основной ИИ-инструмент, интегрированный через JetBrains AI Chat.
|
|
||||||
- `Rust` - основной язык проекта.
|
|
||||||
- `Cargo` - инструмент сборки, тестов и управления зависимостями Rust-проектов.
|
|
||||||
|
|
||||||
## Проект открытый?
|
|
||||||
Да, проект открыт (Open Source).
|
|
||||||
Вы можете предлагать идеи, находить ошибки и вносить изменения в код.
|
|
||||||
|
|
||||||
Проект распространяется по лицензии MIT (см. `LICENSE`).
|
|
||||||
|
|
||||||
## Как внести вклад?
|
|
||||||
Спасибо за желание помочь проекту.
|
|
||||||
Вы можете:
|
|
||||||
- помочь с кодом;
|
|
||||||
- помочь с переводом;
|
|
||||||
- помочь с документацией;
|
|
||||||
- предложить идею или сообщить о баге.
|
|
||||||
|
|
||||||
### Если вы хотите внести изменения (код, перевод, документация)
|
|
||||||
1. Сделайте форк нужного репозитория: `dexoron/dcr` (основной проект) или `dexoron/dcr-site` (сайт проекта).
|
|
||||||
2. Внесите изменения с учетом правил из `CONTRIBUTING.md`.
|
|
||||||
3. Отправьте изменения в свой форк.
|
|
||||||
4. Создайте Pull Request.
|
|
||||||
5. Дождитесь ревью.
|
|
||||||
|
|
||||||
### Если вы хотите сообщить о проблеме или предложить идею
|
|
||||||
1. Откройте нужный репозиторий: `dexoron/dcr` или `dexoron/dcr-site`.
|
|
||||||
2. Перейдите во вкладку `Issues`.
|
|
||||||
3. Создайте Issue и укажите тип (баг, предложение, вопрос/помощь).
|
|
||||||
4. Опишите проблему или идею кратко и понятно.
|
|
||||||
5. Дождитесь обратной связи.
|
|
||||||
|
|
||||||
## Как установить `dcr`?
|
|
||||||
Актуальные способы установки зависят от текущей версии проекта.
|
|
||||||
Рекомендуемый путь:
|
|
||||||
- проверьте инструкции в `README.md`;
|
|
||||||
- если готового пакета для вашей платформы нет, соберите проект из исходников через `cargo build --release`.
|
|
||||||
|
|
||||||
## Как быстро начать работу?
|
|
||||||
Минимальный сценарий:
|
|
||||||
1. Установите `dcr`.
|
|
||||||
2. Создайте проект (или перейдите в существующий C/C++ проект).
|
|
||||||
3. Инициализируйте конфигурацию `dcr`.
|
|
||||||
4. Добавьте зависимости проекта.
|
|
||||||
5. Запустите сборку через `dcr`.
|
|
||||||
|
|
||||||
Подробные команды и примеры смотрите в `README.md` и документации проекта.
|
|
||||||
|
|
||||||
## На какой стадии проект?
|
|
||||||
`dcr` активно развивается.
|
|
||||||
Это означает, что интерфейсы и поведение некоторых функций могут меняться между версиями.
|
|
||||||
|
|
||||||
Перед обновлением рекомендуется читать changelog и проверять заметки к релизу.
|
|
||||||
|
|
||||||
## С какими системами работает `dcr`?
|
|
||||||
`dcr` ориентирован на современные среды разработки C/C++.
|
|
||||||
Фактическая совместимость зависит от версии `dcr`, операционной системы и используемого toolchain.
|
|
||||||
|
|
||||||
Перед началом работы проверьте в документации:
|
|
||||||
- поддерживаемые ОС;
|
|
||||||
- поддерживаемые компиляторы и линкеры;
|
|
||||||
- ограничения конкретной версии.
|
|
||||||
|
|
||||||
## Чем `dcr` отличается от Make/CMake?
|
|
||||||
`dcr` не пытается быть прямой заменой Make/CMake.
|
|
||||||
Главный фокус `dcr` - упростить управление проектом и зависимостями в стиле, близком к опыту Cargo.
|
|
||||||
|
|
||||||
Если вам нужен низкоуровневый полный контроль над процессом сборки, классические инструменты могут быть привычнее.
|
|
||||||
Если нужна более простая и единообразная модель конфигурации - `dcr` может быть удобнее.
|
|
||||||
|
|
||||||
## Где смотреть документацию и примеры?
|
|
||||||
Основные источники:
|
|
||||||
- `README.md` в репозитории;
|
|
||||||
- документация проекта (смотреть сайт проекта);
|
|
||||||
- `Issues` для известных проблем и обсуждений;
|
|
||||||
|
|
||||||
## Как правильно сообщить о баге?
|
|
||||||
Чтобы баг-репорт можно было быстро воспроизвести, укажите:
|
|
||||||
1. Версию `dcr`.
|
|
||||||
2. ОС и версию компилятора/toolchain.
|
|
||||||
3. Шаги воспроизведения.
|
|
||||||
4. Ожидаемое поведение.
|
|
||||||
5. Фактическое поведение и текст ошибки.
|
|
||||||
6. Минимальный пример проекта (по возможности).
|
|
||||||
|
|
||||||
Баги и проблемы отправляйте через `Issues` в соответствующем репозитории.
|
|
||||||
35
docs/README.md
Normal file
35
docs/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
DCR (Dexoron Cargo Realization) is a Cargo-style CLI for C/C++ projects.
|
||||||
|
|
||||||
|
Current DCR focuses on a simple, predictable workflow:
|
||||||
|
|
||||||
|
- create or initialize a project (`dcr new`, `dcr init`)
|
||||||
|
- build with `debug` or `release` profiles (`dcr build`)
|
||||||
|
- run binaries (`dcr run`)
|
||||||
|
- clean artifacts (`dcr clean`)
|
||||||
|
- update the installed binary (`dcr --update`)
|
||||||
|
|
||||||
|
## What is implemented in this version
|
||||||
|
|
||||||
|
- Project configuration in `dcr.toml` with `[package]`, `[build]`, `[dependencies]`.
|
||||||
|
- Recursive source discovery in `src/`:
|
||||||
|
- C: `*.c`
|
||||||
|
- C++: `*.cpp`, `*.cxx`, `*.cc`
|
||||||
|
- Compiler selection from `dcr.toml` (`clang`, `gcc`, `cl`, `clang-cl`, etc.).
|
||||||
|
- Two build kinds:
|
||||||
|
- `bin` (default)
|
||||||
|
- `staticlib`
|
||||||
|
- Optional custom output directory via `build.target`.
|
||||||
|
- Path dependencies with include/lib resolution and `dcr.lock` generation.
|
||||||
|
|
||||||
|
## Important behavior notes
|
||||||
|
|
||||||
|
- `dcr.toml` is required for `build`, `run`, and `clean`.
|
||||||
|
- `run` works only with `build.kind = "bin"`.
|
||||||
|
- Incremental rebuild currently tracks source file timestamps (not header dependency graphs).
|
||||||
|
- Object files are always created under `target/<profile>/obj/`.
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
- <https://github.com/dexoron/dcr>
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# DCR (Dexoron Cargo Realization)
|
|
||||||
|
|
||||||
DCR — это утилита для управления проектами на C/C++ в стиле Cargo.
|
|
||||||
|
|
||||||
Текущая реализация написана на Rust.
|
|
||||||
|
|
||||||
## Зачем DCR
|
|
||||||
- Единая структура проекта без ручной настройки
|
|
||||||
- Простые команды для типовых задач
|
|
||||||
- Прозрачная компиляция и предсказуемые профили сборки
|
|
||||||
|
|
||||||
## Возможности
|
|
||||||
- Создание нового проекта или инициализация текущей директории
|
|
||||||
- Сборка проекта в профилях `debug` и `release`
|
|
||||||
- Запуск собранного бинарника
|
|
||||||
- Очистка результатов сборки
|
|
||||||
- Генерация минимального шаблона C-проекта
|
|
||||||
- Обновление бинарника через `dcr --update` (GitHub Releases, не для pacman-установок)
|
|
||||||
|
|
||||||
## Поддерживаемые платформы
|
|
||||||
- Linux: `x86_64-unknown-linux-gnu`
|
|
||||||
- macOS Intel: `x86_64-apple-darwin`
|
|
||||||
- macOS Apple Silicon: `aarch64-apple-darwin`
|
|
||||||
- Windows: `x86_64-pc-windows-msvc`
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
### Пакетный менеджер
|
|
||||||
|
|
||||||
**ArchLinux**
|
|
||||||
```sh
|
|
||||||
yay -S dcr # или paru и остальные менеджеры пакетов AUR
|
|
||||||
```
|
|
||||||
|
|
||||||
### Из исходников
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Через install.sh (Linux/macOS)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://dcr.dexoron.su/install.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
### Через install.ps1 (Windows)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://dcr.dexoron.su/install.ps1 | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
Оба скрипта при запуске спрашивают:
|
|
||||||
- скачать готовый бинарник из GitHub Release
|
|
||||||
- или собрать проект из `git`
|
|
||||||
|
|
||||||
Релизные ассеты:
|
|
||||||
- `dcr-x86_64-unknown-linux-gnu`
|
|
||||||
- `dcr-x86_64-apple-darwin`
|
|
||||||
- `dcr-aarch64-apple-darwin`
|
|
||||||
- `dcr-x86_64-pc-windows-msvc.exe`
|
|
||||||
|
|
||||||
## Обновление
|
|
||||||
|
|
||||||
- Если DCR установлен из GitHub Releases, через `install.sh`, `install.ps1` или собран вручную:
|
|
||||||
- используйте `dcr --update`
|
|
||||||
- Если DCR установлен через `pacman/AUR`:
|
|
||||||
- обновляйте через пакетный менеджер: `paru/yay -Syu dcr` или `sudo pacman -Syu dcr`
|
|
||||||
- `dcr --update` определяет пакетную установку и подсказывает обновляться через пакетный менеджер
|
|
||||||
|
|
||||||
## Быстрый старт
|
|
||||||
|
|
||||||
Создать новый проект:
|
|
||||||
`dcr new hello`
|
|
||||||
|
|
||||||
Или инициализировать текущую директорию (директория должна быть пустой):
|
|
||||||
`dcr init`
|
|
||||||
|
|
||||||
Структура проекта:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
hello/
|
|
||||||
- src/
|
|
||||||
- - main.c
|
|
||||||
- dcr.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Сборка и запуск проекта:
|
|
||||||
`dcr run` или `dcr run --release`
|
|
||||||
|
|
||||||
## Команды
|
|
||||||
|
|
||||||
### `dcr new <name>`
|
|
||||||
Создает проект в текущей директории с указанным именем.
|
|
||||||
|
|
||||||
### `dcr init`
|
|
||||||
Создает проект в текущей директории. Имя проекта будет равно имени директории. Директория должна быть пустой.
|
|
||||||
|
|
||||||
### `dcr build [profile]`
|
|
||||||
Собирает проект. Если профиль не указан, используется `--debug`.
|
|
||||||
|
|
||||||
### `dcr run [profile]`
|
|
||||||
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`.
|
|
||||||
|
|
||||||
Запуск вручную:
|
|
||||||
`./target/<profile>/main`
|
|
||||||
|
|
||||||
### `dcr clean`
|
|
||||||
Удаляет папку `target` в корне проекта.
|
|
||||||
|
|
||||||
## Профили сборки
|
|
||||||
Поддерживаются два профиля:
|
|
||||||
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
|
||||||
- `--release` — флаги: `-O3 -DNDEBUG -march=native`
|
|
||||||
|
|
||||||
## Конфигурация
|
|
||||||
Основной файл проекта — `dcr.toml`.
|
|
||||||
|
|
||||||
Пример `dcr.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[package]
|
|
||||||
name = "hello"
|
|
||||||
version = "0.1.0"
|
|
||||||
language = "c"
|
|
||||||
compiler = "clang"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
|
|
||||||
- C-компилятор (`clang`, `gcc` или другие)
|
|
||||||
|
|
||||||
## Релизы
|
|
||||||
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
См. файл `LICENSE`.
|
|
||||||
41
docs/SUMMARY.md
Normal file
41
docs/SUMMARY.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Table of contents
|
||||||
|
|
||||||
|
* [Introduction](README.md)
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
* [Installation](getting-started/installation.md)
|
||||||
|
* [Create your first project](getting-started/create-your-first-project.md)
|
||||||
|
* [Project structure](getting-started/project-structure.md)
|
||||||
|
* [Build and run](getting-started/build-and-run.md)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
* [dcr.toml overview](configuration/dcr.toml-overview.md)
|
||||||
|
* [Package section](configuration/package-section.md)
|
||||||
|
* [Build section](configuration/build-section.md)
|
||||||
|
* [Dependencies section](configuration/dependencies-section.md)
|
||||||
|
* [Build profiles](configuration/build-profiles.md)
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
* [Global options](commands/global-options.md)
|
||||||
|
* [dcr init/new](commands/dcr-init-or-new.md)
|
||||||
|
* [dcr build](commands/dcr-build.md)
|
||||||
|
* [dcr run](commands/dcr-run.md)
|
||||||
|
* [dcr clean](commands/dcr-clean.md)
|
||||||
|
|
||||||
|
## Dependencies & Build
|
||||||
|
|
||||||
|
* [Dependency format](dependencies-and-build/dependency-format.md)
|
||||||
|
* [Local dependencies](dependencies-and-build/local-dependencies.md)
|
||||||
|
* [Versioning & lock file](dependencies-and-build/versioning-and-lock-file.md)
|
||||||
|
* [Target directory](dependencies-and-build/target-directory.md)
|
||||||
|
* [Compilers & linking](dependencies-and-build/compilers-and-linking.md)
|
||||||
|
|
||||||
|
## Reference & Help
|
||||||
|
|
||||||
|
* [CLI reference](reference-and-help/cli-reference.md)
|
||||||
|
* [Config reference](reference-and-help/config-reference.md)
|
||||||
|
* [FAQ](reference-and-help/faq.md)
|
||||||
|
* [Contributing & Bug reports](reference-and-help/contributing-and-bug-reports.md)
|
||||||
42
docs/commands/dcr-build.md
Normal file
42
docs/commands/dcr-build.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# dcr build
|
||||||
|
|
||||||
|
Builds the project in `debug` or `release` profile.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr build
|
||||||
|
dcr build --debug
|
||||||
|
dcr build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## What `build` does
|
||||||
|
|
||||||
|
1. Checks that `dcr.toml` exists and is valid.
|
||||||
|
2. Reads build settings from `[build]`.
|
||||||
|
3. Resolves path dependencies from `[dependencies]`.
|
||||||
|
4. Creates required output directories.
|
||||||
|
5. Recursively compiles sources from `src/`.
|
||||||
|
6. Links final artifact (binary or static library).
|
||||||
|
|
||||||
|
## Config values used
|
||||||
|
|
||||||
|
- `package.name`
|
||||||
|
- `build.compiler`
|
||||||
|
- `build.language`
|
||||||
|
- `build.standard`
|
||||||
|
- `build.kind`
|
||||||
|
- `build.target`
|
||||||
|
- `build.cflags`
|
||||||
|
- `build.ldflags`
|
||||||
|
|
||||||
|
## Source selection
|
||||||
|
|
||||||
|
- `language = "c"` -> `*.c`
|
||||||
|
- `language = "c++" | "cpp" | "cxx"` -> `*.cpp`, `*.cxx`, `*.cc`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The profile is selected from the first argument only.
|
||||||
|
- Unknown profile flags return an error.
|
||||||
|
- Incremental rebuild is based on source/object mtime comparison.
|
||||||
23
docs/commands/dcr-clean.md
Normal file
23
docs/commands/dcr-clean.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# dcr clean
|
||||||
|
|
||||||
|
Removes build artifacts in `target/`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr clean
|
||||||
|
dcr clean --debug
|
||||||
|
dcr clean --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
- Without arguments: removes entire `target/`.
|
||||||
|
- With profile: removes only `target/<profile>/`.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- `dcr.toml` must exist.
|
||||||
|
- More than one argument is treated as error.
|
||||||
|
- Unknown profile is treated as error.
|
||||||
|
- If `target` or selected profile directory does not exist, command prints a warning.
|
||||||
43
docs/commands/dcr-init-or-new.md
Normal file
43
docs/commands/dcr-init-or-new.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# dcr init/new
|
||||||
|
|
||||||
|
## `dcr new <name>`
|
||||||
|
|
||||||
|
Creates a new project directory and initializes it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr new hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
1. Validates arguments (`<name>` is required, only one name is allowed).
|
||||||
|
2. Creates `<name>/`.
|
||||||
|
3. Creates `<name>/dcr.toml`.
|
||||||
|
4. Sets `package.name = "<name>"`.
|
||||||
|
5. Creates `<name>/src/main.c` with a Hello World template.
|
||||||
|
|
||||||
|
Errors:
|
||||||
|
|
||||||
|
- Missing project name.
|
||||||
|
- Extra arguments.
|
||||||
|
- Target directory already exists.
|
||||||
|
|
||||||
|
## `dcr init`
|
||||||
|
|
||||||
|
Initializes DCR in the current directory.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr init
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
1. Requires current directory to be empty.
|
||||||
|
2. Creates `dcr.toml`.
|
||||||
|
3. Sets `package.name` from the current directory name.
|
||||||
|
4. Creates `src/main.c`.
|
||||||
|
|
||||||
|
Errors:
|
||||||
|
|
||||||
|
- Any arguments are treated as invalid.
|
||||||
|
- Non-empty directory is rejected.
|
||||||
27
docs/commands/dcr-run.md
Normal file
27
docs/commands/dcr-run.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# dcr run
|
||||||
|
|
||||||
|
Builds the project and runs the resulting binary.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr run
|
||||||
|
dcr run --debug
|
||||||
|
dcr run --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
1. Validates `dcr.toml`.
|
||||||
|
2. Reads `package.name`, `build.kind`, and optional `build.target`.
|
||||||
|
3. Runs the same build flow as `dcr build`.
|
||||||
|
4. Executes the built artifact path for the selected profile.
|
||||||
|
|
||||||
|
## Restrictions
|
||||||
|
|
||||||
|
- If `build.kind = "staticlib"`, `run` exits with error.
|
||||||
|
- The profile is selected from the first argument.
|
||||||
|
|
||||||
|
## On build failure
|
||||||
|
|
||||||
|
`dcr run` attempts to launch an existing binary from the same profile if present; otherwise it reports that code errors must be fixed.
|
||||||
44
docs/commands/global-options.md
Normal file
44
docs/commands/global-options.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Global options
|
||||||
|
|
||||||
|
## `dcr --help`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints top-level CLI usage, commands, flags, and examples.
|
||||||
|
|
||||||
|
## `dcr --version`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dcr <version> (<target>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `dcr --update`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr --update
|
||||||
|
```
|
||||||
|
|
||||||
|
Update flow:
|
||||||
|
|
||||||
|
1. Requests latest release from GitHub API.
|
||||||
|
2. Finds asset matching current `DCR_TARGET`.
|
||||||
|
3. Downloads replacement binary.
|
||||||
|
4. Replaces current executable via `self-replace`.
|
||||||
|
|
||||||
|
Limitations and behavior:
|
||||||
|
|
||||||
|
- No extra arguments are accepted.
|
||||||
|
- Requires network access to GitHub API/assets.
|
||||||
|
- On Linux, if binary is owned by pacman, DCR prints package-manager update instructions instead of self-updating.
|
||||||
|
|
||||||
|
## Running without command
|
||||||
|
|
||||||
|
Running plain `dcr` (without arguments) shows help output.
|
||||||
39
docs/configuration/build-profiles.md
Normal file
39
docs/configuration/build-profiles.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Build profiles
|
||||||
|
|
||||||
|
DCR supports two profiles:
|
||||||
|
|
||||||
|
- `debug` (default)
|
||||||
|
- `release`
|
||||||
|
|
||||||
|
## How to select
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr build --debug
|
||||||
|
dcr build --release
|
||||||
|
dcr run --release
|
||||||
|
dcr clean --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default profile flags
|
||||||
|
|
||||||
|
### GCC/Clang-like toolchains
|
||||||
|
|
||||||
|
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
|
||||||
|
- `release`: `-O3 -DNDEBUG -march=native`
|
||||||
|
|
||||||
|
### MSVC toolchain
|
||||||
|
|
||||||
|
- `debug`: `/Od /Zi /W4 /DDEBUG /Oy-`
|
||||||
|
- `release`: `/O2 /DNDEBUG`
|
||||||
|
|
||||||
|
## Artifacts
|
||||||
|
|
||||||
|
By default:
|
||||||
|
|
||||||
|
- `target/debug/<name>` (or `.exe` on Windows)
|
||||||
|
- `target/release/<name>` (or `.exe` on Windows)
|
||||||
|
|
||||||
|
Static library mode (`kind = "staticlib"`):
|
||||||
|
|
||||||
|
- Linux/macOS: `lib<name>.a`
|
||||||
|
- Windows: `<name>.lib`
|
||||||
31
docs/configuration/build-section.md
Normal file
31
docs/configuration/build-section.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Build section
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
language = "c"
|
||||||
|
standard = "c11"
|
||||||
|
compiler = "clang"
|
||||||
|
kind = "bin"
|
||||||
|
# optional:
|
||||||
|
# target = "./dist"
|
||||||
|
# cflags = ["-Wall"]
|
||||||
|
# ldflags = ["-lm"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
- `language` (string, required): `c`, `c++`, `cpp`, or `cxx`.
|
||||||
|
- `standard` (string, required): language standard passed to compiler.
|
||||||
|
- `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`).
|
||||||
|
- `kind` (string, required): `bin` or `staticlib`.
|
||||||
|
- `target` (string, optional): custom output directory for final artifact.
|
||||||
|
- `cflags` (string array, optional): extra compile flags.
|
||||||
|
- `ldflags` (string array, optional): extra link flags.
|
||||||
|
|
||||||
|
## Behavior notes
|
||||||
|
|
||||||
|
- Compiler backend is selected by compiler string (`gcc`, `clang`, `cl`, `clang-cl`).
|
||||||
|
- Empty or unknown compiler value falls back to clang-like build path.
|
||||||
|
- `run` is not allowed for `kind = "staticlib"`.
|
||||||
45
docs/configuration/dcr.toml-overview.md
Normal file
45
docs/configuration/dcr.toml-overview.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# dcr.toml overview
|
||||||
|
|
||||||
|
`dcr.toml` is the main project file used by DCR.
|
||||||
|
|
||||||
|
## Minimal generated template
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "hello"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
language = "c"
|
||||||
|
standard = "c11"
|
||||||
|
compiler = "clang"
|
||||||
|
kind = "bin"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
```
|
||||||
|
|
||||||
|
`dcr new` and `dcr init` generate this structure automatically.
|
||||||
|
|
||||||
|
## Required sections
|
||||||
|
|
||||||
|
- `[package]`
|
||||||
|
- `[build]`
|
||||||
|
- `[dependencies]`
|
||||||
|
|
||||||
|
## Required keys
|
||||||
|
|
||||||
|
- `package.name`
|
||||||
|
- `package.version`
|
||||||
|
- `build.language`
|
||||||
|
- `build.standard`
|
||||||
|
- `build.compiler`
|
||||||
|
- `build.kind`
|
||||||
|
|
||||||
|
`build.kind` must be either `bin` or `staticlib`.
|
||||||
|
|
||||||
|
## Optional keys
|
||||||
|
|
||||||
|
- `build.target`
|
||||||
|
- `build.cflags`
|
||||||
|
- `build.ldflags`
|
||||||
|
- dependency fields (`include`, `lib`, `libs`, `system`)
|
||||||
30
docs/configuration/dependencies-section.md
Normal file
30
docs/configuration/dependencies-section.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Dependencies section
|
||||||
|
|
||||||
|
DCR currently supports path dependencies.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
frecli = { path = "./lib/frecli", include = ["."], lib = ["."], libs = ["frecli"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported fields per dependency
|
||||||
|
|
||||||
|
- `path` (string, required): absolute path or path relative to project root.
|
||||||
|
- `include` (string array, optional): include directories inside dependency.
|
||||||
|
- `lib` (string array, optional): library directories inside dependency.
|
||||||
|
- `libs` (string array, optional): library names for linker.
|
||||||
|
- `system` (bool, optional): currently `system = true` is rejected.
|
||||||
|
|
||||||
|
## Defaults
|
||||||
|
|
||||||
|
- If `include` is omitted, DCR tries `<dep>/include`.
|
||||||
|
- If `lib` is omitted, DCR tries `<dep>/lib`, then `<dep>/lib64`.
|
||||||
|
- If `libs` is omitted, dependency key name is used.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Dependency value must be a TOML table.
|
||||||
|
- `path` is mandatory.
|
||||||
|
- `include`/`lib`/`libs` must be arrays of strings if provided.
|
||||||
20
docs/configuration/package-section.md
Normal file
20
docs/configuration/package-section.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Package section
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "my-app"
|
||||||
|
version = "0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
- `name` (string, required): used as output artifact base name.
|
||||||
|
- `version` (string, required): used in `dcr.lock` project entry.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Empty `name` or `version` makes config invalid.
|
||||||
|
- `dcr new` sets `name` from the passed project name.
|
||||||
|
- `dcr init` sets `name` from current directory name.
|
||||||
30
docs/dependencies-and-build/compilers-and-linking.md
Normal file
30
docs/dependencies-and-build/compilers-and-linking.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Compilers & linking
|
||||||
|
|
||||||
|
## Compiler backend selection
|
||||||
|
|
||||||
|
DCR picks a backend from `build.compiler`:
|
||||||
|
|
||||||
|
- contains `clang-cl` -> MSVC backend
|
||||||
|
- contains `gcc` or `g++` -> GCC backend
|
||||||
|
- contains `clang` or `clang++` -> Clang backend
|
||||||
|
- equals `cl` or contains `msvc` -> MSVC backend
|
||||||
|
- otherwise -> Clang backend fallback
|
||||||
|
|
||||||
|
## Compilation model
|
||||||
|
|
||||||
|
- Source files are found recursively in `src/` based on `build.language`.
|
||||||
|
- Each source is compiled to `target/<profile>/obj/<relative-path>.o` (`.obj` on MSVC).
|
||||||
|
- Recompile happens when source mtime is newer than object mtime.
|
||||||
|
|
||||||
|
## Linking
|
||||||
|
|
||||||
|
For `kind = "bin"`:
|
||||||
|
|
||||||
|
- Object files are linked into executable artifact.
|
||||||
|
- Dependency lib dirs and library names are passed to linker.
|
||||||
|
- `build.ldflags` are appended to link command.
|
||||||
|
|
||||||
|
For `kind = "staticlib"`:
|
||||||
|
|
||||||
|
- Linux/macOS (GCC/Clang): `ar rcs` creates `.a` archive.
|
||||||
|
- Windows (MSVC): `lib` creates `.lib` archive.
|
||||||
26
docs/dependencies-and-build/dependency-format.md
Normal file
26
docs/dependencies-and-build/dependency-format.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Dependency format
|
||||||
|
|
||||||
|
Dependencies are defined in `dcr.toml` under `[dependencies]`.
|
||||||
|
|
||||||
|
## Full example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
fmt = { path = "./third_party/fmt", include = ["include"], lib = ["lib"], libs = ["fmt"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Field reference
|
||||||
|
|
||||||
|
- `path`: required dependency root path.
|
||||||
|
- `include`: optional include subpaths (absolute or relative to dep root).
|
||||||
|
- `lib`: optional library subpaths (absolute or relative to dep root).
|
||||||
|
- `libs`: optional linker library names.
|
||||||
|
|
||||||
|
## Common valid compact form
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
mylib = { path = "./libs/mylib" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this form DCR will try defaults for include/lib dirs and use `mylib` as the linker library name.
|
||||||
19
docs/dependencies-and-build/local-dependencies.md
Normal file
19
docs/dependencies-and-build/local-dependencies.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Local dependencies
|
||||||
|
|
||||||
|
## Resolution flow during build
|
||||||
|
|
||||||
|
For each dependency in `[dependencies]`, DCR:
|
||||||
|
|
||||||
|
1. Resolves `path` (absolute or project-relative).
|
||||||
|
2. Resolves include dirs (`include` or default `include`).
|
||||||
|
3. Resolves lib dirs (`lib` or defaults `lib`, `lib64`).
|
||||||
|
4. Resolves link libs (`libs` or dependency name).
|
||||||
|
5. Synchronizes dependency files to `target/<profile>/deps/<dep-name>/`.
|
||||||
|
6. Adds include/lib/link options to compile/link commands.
|
||||||
|
|
||||||
|
## Failure cases
|
||||||
|
|
||||||
|
- Dependency path is missing or not a directory.
|
||||||
|
- No valid include/lib directories were found.
|
||||||
|
- Any configured include/lib path does not exist.
|
||||||
|
- Invalid field types in dependency config.
|
||||||
23
docs/dependencies-and-build/target-directory.md
Normal file
23
docs/dependencies-and-build/target-directory.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Target directory
|
||||||
|
|
||||||
|
By default, DCR writes final artifacts to `target/<profile>/`.
|
||||||
|
|
||||||
|
You can override this with `build.target`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
target = "./dist"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
- Binary mode:
|
||||||
|
- Linux/macOS: `<target>/<name>`
|
||||||
|
- Windows: `<target>/<name>.exe`
|
||||||
|
- Static library mode:
|
||||||
|
- Linux/macOS: `<target>/lib<name>.a`
|
||||||
|
- Windows: `<target>/<name>.lib`
|
||||||
|
|
||||||
|
## Important detail
|
||||||
|
|
||||||
|
`build.target` changes only the final artifact location. Object cache and dependency sync directories still use `target/<profile>/...`.
|
||||||
32
docs/dependencies-and-build/versioning-and-lock-file.md
Normal file
32
docs/dependencies-and-build/versioning-and-lock-file.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Versioning & lock file
|
||||||
|
|
||||||
|
DCR generates `dcr.lock` when dependency resolution succeeds and at least one dependency exists.
|
||||||
|
|
||||||
|
## What is stored
|
||||||
|
|
||||||
|
- Project entry (`name`, `version`, dependency names).
|
||||||
|
- One entry per resolved dependency:
|
||||||
|
- `name`
|
||||||
|
- `version` (from dependency `dcr.toml`, fallback `0.0.0`)
|
||||||
|
- `source` (`path+...`)
|
||||||
|
- `checksum` (SHA-256 over dependency files, excluding nested `target/`).
|
||||||
|
|
||||||
|
## Example shape
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[package]]
|
||||||
|
name = "my-app"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = ["fmt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fmt"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "path+./third_party/fmt"
|
||||||
|
checksum = "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `dcr.lock` is rewritten on successful dependency resolution.
|
||||||
|
- If `[dependencies]` is empty, lock generation is skipped.
|
||||||
46
docs/getting-started/build-and-run.md
Normal file
46
docs/getting-started/build-and-run.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Build and run
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Debug (default):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr build
|
||||||
|
# or
|
||||||
|
dcr build --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Release:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr run
|
||||||
|
# or
|
||||||
|
dcr run --release
|
||||||
|
```
|
||||||
|
|
||||||
|
`dcr run` first builds, then launches the produced binary.
|
||||||
|
|
||||||
|
## Clean artifacts
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Profile-only cleanup:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr clean --debug
|
||||||
|
dcr clean --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `run` is unavailable for `build.kind = "staticlib"`.
|
||||||
|
- Build profile flag is parsed from the first command argument.
|
||||||
|
- Unknown profile flags produce an error.
|
||||||
41
docs/getting-started/create-your-first-project.md
Normal file
41
docs/getting-started/create-your-first-project.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Create your first project
|
||||||
|
|
||||||
|
## Create in a new folder
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr new hello
|
||||||
|
cd hello
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hello/
|
||||||
|
- dcr.toml
|
||||||
|
- src/
|
||||||
|
- - main.c
|
||||||
|
```
|
||||||
|
|
||||||
|
## Or initialize current folder
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir hello
|
||||||
|
cd hello
|
||||||
|
dcr init
|
||||||
|
```
|
||||||
|
|
||||||
|
`dcr init` requires the current directory to be empty.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
Build and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr run
|
||||||
|
```
|
||||||
|
|
||||||
|
Release build/run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr run --release
|
||||||
|
```
|
||||||
56
docs/getting-started/installation.md
Normal file
56
docs/getting-started/installation.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
## Option 1: Install via script
|
||||||
|
|
||||||
|
### Linux/macOS
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://dcr.dexoron.su/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternative with `wget`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wget -qO- https://dcr.dexoron.su/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy ByPass -c "irm https://dcr.dexoron.su/install.ps1 | iex"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Option 2: Build from source
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Option 3: Arch Linux (AUR)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yay -S dcr
|
||||||
|
```
|
||||||
|
|
||||||
|
(You can also use `paru` or other AUR helpers.)
|
||||||
|
|
||||||
|
## Verify installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcr --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dcr <version> (<target>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update policy
|
||||||
|
|
||||||
|
- If DCR was installed from GitHub releases, scripts, or manual build: use `dcr --update`.
|
||||||
|
- If DCR is managed by `pacman/AUR`: update via package manager. On Linux, `dcr --update` detects pacman-owned binaries and prints a package-manager hint.
|
||||||
39
docs/getting-started/project-structure.md
Normal file
39
docs/getting-started/project-structure.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Project structure
|
||||||
|
|
||||||
|
## Minimal project
|
||||||
|
|
||||||
|
```text
|
||||||
|
project/
|
||||||
|
- dcr.toml
|
||||||
|
- src/
|
||||||
|
- - main.c
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typical generated/optional files
|
||||||
|
|
||||||
|
```text
|
||||||
|
project/
|
||||||
|
- dcr.toml
|
||||||
|
- dcr.lock
|
||||||
|
- src/
|
||||||
|
- - ... source files ...
|
||||||
|
- target/
|
||||||
|
- - debug/
|
||||||
|
- - - obj/
|
||||||
|
- - - deps/
|
||||||
|
- - release/
|
||||||
|
- - - obj/
|
||||||
|
- - - deps/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Meaning
|
||||||
|
|
||||||
|
- `dcr.toml`: main project configuration.
|
||||||
|
- `dcr.lock`: generated lock file for resolved path dependencies.
|
||||||
|
- `src/`: recursively scanned source directory.
|
||||||
|
- `target/<profile>/obj/`: cached object files for incremental rebuild.
|
||||||
|
- `target/<profile>/deps/`: synchronized copies of path dependencies.
|
||||||
|
- `target/<profile>/<name>` (or `.exe` on Windows): output for `kind = "bin"`.
|
||||||
|
- `target/<profile>/lib<name>.a` (or `<name>.lib` on Windows): output for `kind = "staticlib"`.
|
||||||
|
|
||||||
|
If `build.target` is set, the final artifact is written to that custom directory, while object cache still stays in `target/<profile>/obj`.
|
||||||
26
docs/reference-and-help/cli-reference.md
Normal file
26
docs/reference-and-help/cli-reference.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# CLI reference
|
||||||
|
|
||||||
|
## Command list
|
||||||
|
|
||||||
|
```text
|
||||||
|
dcr new <name>
|
||||||
|
dcr init
|
||||||
|
dcr build [--debug|--release]
|
||||||
|
dcr run [--debug|--release]
|
||||||
|
dcr clean [--debug|--release]
|
||||||
|
dcr --help
|
||||||
|
dcr --version
|
||||||
|
dcr --update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes on argument parsing
|
||||||
|
|
||||||
|
- Most commands parse profile from the first argument.
|
||||||
|
- `new` requires exactly one argument.
|
||||||
|
- `init` and `--update` do not accept extra arguments.
|
||||||
|
- `clean` accepts at most one argument.
|
||||||
|
|
||||||
|
## Exit behavior overview
|
||||||
|
|
||||||
|
- Successful command execution returns `0`.
|
||||||
|
- Validation/build/runtime errors return non-zero status.
|
||||||
41
docs/reference-and-help/config-reference.md
Normal file
41
docs/reference-and-help/config-reference.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Config reference
|
||||||
|
|
||||||
|
## Full schema
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "string" # required
|
||||||
|
version = "string" # required
|
||||||
|
|
||||||
|
[build]
|
||||||
|
language = "c|c++|cpp|cxx" # required
|
||||||
|
standard = "string" # required
|
||||||
|
compiler = "string" # required
|
||||||
|
kind = "bin|staticlib" # required
|
||||||
|
target = "string" # optional
|
||||||
|
cflags = ["string", "..."] # optional
|
||||||
|
ldflags = ["string", "..."] # optional
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
name = {
|
||||||
|
path = "string", # required for each dependency
|
||||||
|
include = ["string", "..."], # optional
|
||||||
|
lib = ["string", "..."], # optional
|
||||||
|
libs = ["string", "..."], # optional
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation rules
|
||||||
|
|
||||||
|
- `[package]`, `[build]`, `[dependencies]` must exist.
|
||||||
|
- Required string fields must be non-empty.
|
||||||
|
- `build.kind` must be `bin` or `staticlib`.
|
||||||
|
- Dependency fields `include/lib/libs` must be string arrays when provided.
|
||||||
|
|
||||||
|
## Generated defaults (`dcr new` / `dcr init`)
|
||||||
|
|
||||||
|
- `package.version = "0.1.0"`
|
||||||
|
- `build.language = "c"`
|
||||||
|
- `build.standard = "c11"`
|
||||||
|
- `build.compiler = "clang"`
|
||||||
|
- `build.kind = "bin"`
|
||||||
34
docs/reference-and-help/contributing-and-bug-reports.md
Normal file
34
docs/reference-and-help/contributing-and-bug-reports.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Contributing & Bug reports
|
||||||
|
|
||||||
|
## Contribution workflow
|
||||||
|
|
||||||
|
1. Fork `dexoron/dcr` (and `dexoron/dcr-site` if docs website changes are needed).
|
||||||
|
2. Make focused changes.
|
||||||
|
3. Run checks locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
cargo check
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Open a PR with short rationale and behavior impact.
|
||||||
|
5. Update docs when CLI behavior changes.
|
||||||
|
|
||||||
|
## Bug report checklist
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
1. DCR version (`dcr --version`).
|
||||||
|
2. OS and compiler/toolchain version.
|
||||||
|
3. Reproduction steps.
|
||||||
|
4. Expected result.
|
||||||
|
5. Actual result and full error output.
|
||||||
|
6. Minimal reproducible project.
|
||||||
|
|
||||||
|
## Where to report
|
||||||
|
|
||||||
|
Use GitHub Issues in the relevant repository:
|
||||||
|
|
||||||
|
- <https://github.com/dexoron/dcr>
|
||||||
|
- <https://github.com/dexoron/dcr-site>
|
||||||
34
docs/reference-and-help/faq.md
Normal file
34
docs/reference-and-help/faq.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
## What commands are available?
|
||||||
|
|
||||||
|
`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`.
|
||||||
|
|
||||||
|
## Is `dcr.toml` mandatory?
|
||||||
|
|
||||||
|
Yes, for `build`, `run`, and `clean`.
|
||||||
|
|
||||||
|
## What source files are compiled?
|
||||||
|
|
||||||
|
DCR scans `src/` recursively:
|
||||||
|
|
||||||
|
- for C: `*.c`
|
||||||
|
- for C++: `*.cpp`, `*.cxx`, `*.cc`
|
||||||
|
|
||||||
|
## Can I switch compiler?
|
||||||
|
|
||||||
|
Yes. Set `build.compiler` in `dcr.toml` (for example `gcc`, `clang`, `cl`, `clang-cl`).
|
||||||
|
|
||||||
|
## How does incremental rebuild work?
|
||||||
|
|
||||||
|
It compares source file mtime with object file mtime. Header dependency tracking is not implemented yet.
|
||||||
|
|
||||||
|
## What does `dcr run` do if build fails?
|
||||||
|
|
||||||
|
It tries to run an existing binary from the selected profile; if none exists, it reports an error.
|
||||||
|
|
||||||
|
## How do I update DCR?
|
||||||
|
|
||||||
|
Use `dcr --update` for non-package-managed installs.
|
||||||
|
|
||||||
|
If installed through pacman/AUR, update via package manager.
|
||||||
@@ -18,6 +18,7 @@ sha256sums=('30e334fa98cd51073c0fa84c80a982584b00156b57084311ad53419441ce6235')
|
|||||||
build() {
|
build() {
|
||||||
cd "$srcdir/$pkgname-$pkgver"
|
cd "$srcdir/$pkgname-$pkgver"
|
||||||
export CARGO_PROFILE_RELEASE_LTO=false
|
export CARGO_PROFILE_RELEASE_LTO=false
|
||||||
|
export RUSTFLAGS="${RUSTFLAGS:-}"
|
||||||
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
|
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
|
||||||
cargo build --release --locked
|
cargo build --release --locked
|
||||||
}
|
}
|
||||||
|
|||||||
199
src/cli/build.rs
199
src/cli/build.rs
@@ -1,15 +1,14 @@
|
|||||||
use crate::config::{PROFILE, flags};
|
use crate::config::{PROFILE, flags};
|
||||||
|
use crate::core::builder::{BuildContext, build as build_project};
|
||||||
use crate::core::config::Config;
|
use crate::core::config::Config;
|
||||||
|
use crate::core::deps::resolve_deps;
|
||||||
use crate::utils::fs::check_dir;
|
use crate::utils::fs::check_dir;
|
||||||
use crate::utils::log::{error, warn};
|
use crate::utils::log::{error, warn};
|
||||||
use crate::utils::text::{BOLD_GREEN, colored};
|
use crate::utils::text::{BOLD_GREEN, colored};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process::Command;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
pub fn build(args: &[String]) -> i32 {
|
pub fn build(args: &[String]) -> i32 {
|
||||||
let mut active_profile = PROFILE.to_string();
|
|
||||||
|
|
||||||
let items = check_dir(None).unwrap_or_default();
|
let items = check_dir(None).unwrap_or_default();
|
||||||
if !items.contains(&"dcr.toml".to_string()) {
|
if !items.contains(&"dcr.toml".to_string()) {
|
||||||
error("dcr.toml file not found");
|
error("dcr.toml file not found");
|
||||||
@@ -23,73 +22,165 @@ pub fn build(args: &[String]) -> i32 {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let project_compiler: &str = config
|
let active_profile = match parse_profile(args) {
|
||||||
.get("package.compiler")
|
Ok(profile) => profile,
|
||||||
.and_then(|v| v.as_str())
|
Err(code) => return code,
|
||||||
.unwrap_or("");
|
};
|
||||||
let project_name: &str = config
|
let project_name = get_config_str(&config, "package.name");
|
||||||
.get("package.name")
|
let project_compiler = get_config_str(&config, "build.compiler");
|
||||||
.and_then(|v| v.as_str())
|
let build_language = get_config_str(&config, "build.language");
|
||||||
.unwrap_or("");
|
let build_standard = get_config_str(&config, "build.standard");
|
||||||
|
let build_target = get_config_str(&config, "build.target");
|
||||||
if let Some(first_arg) = args.first() {
|
let build_kind = get_config_str(&config, "build.kind");
|
||||||
if first_arg.starts_with("--") {
|
let build_cflags = match get_config_list(&config, "build.cflags") {
|
||||||
let candidate = first_arg.trim_start_matches("--");
|
Ok(v) => v,
|
||||||
if flags(candidate).is_some() {
|
Err(msg) => {
|
||||||
active_profile = candidate.to_string();
|
error(&msg);
|
||||||
} else {
|
|
||||||
warn("Unknown build flag");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
warn("Unknown argument");
|
let build_ldflags = match get_config_list(&config, "build.ldflags") {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(msg) => {
|
||||||
|
error(&msg);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
" Building project `{}`\n Profile: {}\n Compiler: {}\n",
|
" Building project `{}`\n Profile: {}\n Compiler: {}\n",
|
||||||
colored(project_name, BOLD_GREEN),
|
colored(&project_name, BOLD_GREEN),
|
||||||
colored(&active_profile, BOLD_GREEN),
|
colored(&active_profile, BOLD_GREEN),
|
||||||
colored(project_compiler, BOLD_GREEN)
|
colored(&project_compiler, BOLD_GREEN)
|
||||||
);
|
);
|
||||||
|
|
||||||
if !items.contains(&"target".to_string()) {
|
ensure_target_dirs(&items, &active_profile, normalize_target(&build_target));
|
||||||
let _ = fs::create_dir("./target");
|
|
||||||
}
|
|
||||||
let target_items = check_dir(Some("target")).unwrap_or_default();
|
|
||||||
if !target_items.contains(&active_profile) {
|
|
||||||
let _ = fs::create_dir(format!("./target/{active_profile}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let src_items = check_dir(Some("src")).unwrap_or_default();
|
let project_root = match std::env::current_dir() {
|
||||||
if src_items.contains(&"main.c".to_string()) {
|
Ok(p) => p,
|
||||||
let compile_flags = flags(&active_profile).unwrap_or(&[]);
|
Err(_) => {
|
||||||
let start_time = Instant::now();
|
error("Failed to determine project root");
|
||||||
let mut cmd = Command::new(project_compiler);
|
return 1;
|
||||||
cmd.arg("./src/main.c");
|
|
||||||
for flag in compile_flags {
|
|
||||||
cmd.arg(flag);
|
|
||||||
}
|
}
|
||||||
cmd.arg("-o")
|
};
|
||||||
.arg(format!("./target/{active_profile}/{project_name}"));
|
let resolved = match resolve_deps(&config, &active_profile, &project_root) {
|
||||||
|
Ok(r) => r,
|
||||||
match cmd.status() {
|
Err(msg) => {
|
||||||
Ok(status) if status.success() => {
|
error(&msg);
|
||||||
let times = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ctx = BuildContext {
|
||||||
|
profile: &active_profile,
|
||||||
|
project_name: &project_name,
|
||||||
|
compiler: &project_compiler,
|
||||||
|
language: &build_language,
|
||||||
|
standard: &build_standard,
|
||||||
|
target_dir: normalize_target(&build_target),
|
||||||
|
kind: normalize_kind(&build_kind),
|
||||||
|
include_dirs: &resolved.include_dirs,
|
||||||
|
lib_dirs: &resolved.lib_dirs,
|
||||||
|
libs: &resolved.libs,
|
||||||
|
cflags: &build_cflags,
|
||||||
|
ldflags: &build_ldflags,
|
||||||
|
};
|
||||||
|
match run_build(&ctx) {
|
||||||
|
Ok(times) => {
|
||||||
println!(
|
println!(
|
||||||
" {} Build completed successfully in {} seconds",
|
" {} Build completed successfully in {} seconds",
|
||||||
colored("✔", BOLD_GREEN),
|
colored("✔", BOLD_GREEN),
|
||||||
colored(×.to_string(), BOLD_GREEN)
|
colored(×.to_string(), BOLD_GREEN)
|
||||||
);
|
);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error("Build failed");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
0
|
0
|
||||||
|
}
|
||||||
|
Err(msg) => {
|
||||||
|
error(&msg);
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_profile(args: &[String]) -> Result<String, i32> {
|
||||||
|
if let Some(first_arg) = args.first() {
|
||||||
|
if first_arg.starts_with("--") {
|
||||||
|
let candidate = first_arg.trim_start_matches("--");
|
||||||
|
if flags(candidate).is_some() {
|
||||||
|
return Ok(candidate.to_string());
|
||||||
|
}
|
||||||
|
warn("Unknown build flag");
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
warn("Unknown argument");
|
||||||
|
return Err(1);
|
||||||
|
}
|
||||||
|
Ok(PROFILE.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_str(config: &Config, key: &str) -> String {
|
||||||
|
config
|
||||||
|
.get(key)
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_list(config: &Config, key: &str) -> Result<Vec<String>, String> {
|
||||||
|
let value = match config.get(key) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
let arr = value
|
||||||
|
.as_array()
|
||||||
|
.ok_or_else(|| format!("{key} must be an array of strings"))?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for item in arr {
|
||||||
|
let s = item
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| format!("{key} must be an array of strings"))?;
|
||||||
|
out.push(s.to_string());
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_target_dirs(items: &[String], profile: &str, target_dir: Option<&str>) {
|
||||||
|
if let Some(dir) = target_dir {
|
||||||
|
let _ = fs::create_dir_all(dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !items.contains(&"target".to_string()) {
|
||||||
|
let _ = fs::create_dir("./target");
|
||||||
|
}
|
||||||
|
let target_items = check_dir(Some("target")).unwrap_or_default();
|
||||||
|
if !target_items.contains(&profile.to_string()) {
|
||||||
|
let _ = fs::create_dir(format!("./target/{profile}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
|
let start_time = Instant::now();
|
||||||
|
match build_project(ctx) {
|
||||||
|
Ok(times) => {
|
||||||
|
let times = if times == 0.0 {
|
||||||
|
((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0
|
||||||
|
} else {
|
||||||
|
times
|
||||||
|
};
|
||||||
|
Ok(times)
|
||||||
|
}
|
||||||
|
Err(_) => Err("Build failed".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_target(target: &str) -> Option<&str> {
|
||||||
|
let trimmed = target.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_kind(kind: &str) -> &str {
|
||||||
|
let trimmed = kind.trim();
|
||||||
|
if trimmed.is_empty() { "bin" } else { trimmed }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::cli::build::build;
|
use crate::cli::build::build;
|
||||||
use crate::config::{PROFILE, flags};
|
use crate::config::{PROFILE, flags};
|
||||||
use crate::core::config::Config;
|
use crate::core::config::Config;
|
||||||
|
use crate::core::runner::run_binary;
|
||||||
use crate::utils::fs::check_dir;
|
use crate::utils::fs::check_dir;
|
||||||
use crate::utils::log::{error, warn};
|
use crate::utils::log::{error, warn};
|
||||||
use crate::utils::text::{BOLD_GREEN, colored};
|
use crate::utils::text::{BOLD_GREEN, colored};
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
pub fn run(args: &[String]) -> i32 {
|
pub fn run(args: &[String]) -> i32 {
|
||||||
let mut active_profile = PROFILE.to_string();
|
let mut active_profile = PROFILE.to_string();
|
||||||
@@ -26,6 +26,15 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
.get("package.name")
|
.get("package.name")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
let build_kind = config
|
||||||
|
.get("build.kind")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
let target_dir = config
|
||||||
|
.get("build.target")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(|s| s.trim())
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
|
|
||||||
if let Some(first_arg) = args.first() {
|
if let Some(first_arg) = args.first() {
|
||||||
if first_arg.starts_with("--") {
|
if first_arg.starts_with("--") {
|
||||||
@@ -42,30 +51,23 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if build_kind.trim() == "staticlib" {
|
||||||
|
error("Cannot run static library");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
let build_status = build(args);
|
let build_status = build(args);
|
||||||
|
let bin_path = crate::platform::bin_path(&active_profile, project_name, target_dir);
|
||||||
if build_status == 0 {
|
if build_status == 0 {
|
||||||
println!(
|
println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path);
|
||||||
"\n {} target/{active_profile}/{project_name}",
|
|
||||||
colored("Running", BOLD_GREEN)
|
|
||||||
);
|
|
||||||
println!("--------------------------------");
|
println!("--------------------------------");
|
||||||
return Command::new(format!("./target/{active_profile}/{project_name}"))
|
return run_binary(project_name, &active_profile, target_dir);
|
||||||
.status()
|
|
||||||
.map(|status| status.code().unwrap_or(1))
|
|
||||||
.unwrap_or(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if check_dir(Some(&format!("target/{active_profile}")))
|
let fallback_code = run_binary(project_name, &active_profile, target_dir);
|
||||||
.unwrap_or_default()
|
if fallback_code != 1 {
|
||||||
.contains(&active_profile)
|
return fallback_code;
|
||||||
{
|
|
||||||
warn("Launch of the latest release");
|
|
||||||
return Command::new(format!("./target/{active_profile}/{project_name}"))
|
|
||||||
.status()
|
|
||||||
.map(|status| status.code().unwrap_or(1))
|
|
||||||
.unwrap_or(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error("Fix errors in the code to run the project");
|
error("Fix errors in the code to run the project");
|
||||||
0
|
1
|
||||||
}
|
}
|
||||||
|
|||||||
183
src/core/builder/clang.rs
Normal file
183
src/core/builder/clang.rs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
use crate::core::builder::BuildContext;
|
||||||
|
use crate::platform;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
|
let compiler = if ctx.compiler.is_empty() {
|
||||||
|
"cc"
|
||||||
|
} else {
|
||||||
|
ctx.compiler
|
||||||
|
};
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let sources = collect_sources(ctx.language)?;
|
||||||
|
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||||
|
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?;
|
||||||
|
|
||||||
|
if ctx.kind == "staticlib" {
|
||||||
|
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||||
|
let mut cmd = Command::new("ar");
|
||||||
|
cmd.arg("rcs").arg(&lib_path);
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
return Ok(elapsed);
|
||||||
|
}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = Command::new(compiler);
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.lib_dirs {
|
||||||
|
cmd.arg(format!("-L{dir}"));
|
||||||
|
}
|
||||||
|
for lib in ctx.libs {
|
||||||
|
cmd.arg(format!("-l{lib}"));
|
||||||
|
}
|
||||||
|
for flag in ctx.ldflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
cmd.arg("-o").arg(platform::bin_path(
|
||||||
|
ctx.profile,
|
||||||
|
ctx.project_name,
|
||||||
|
ctx.target_dir,
|
||||||
|
));
|
||||||
|
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
Ok(elapsed)
|
||||||
|
}
|
||||||
|
Ok(_) => Err("Build failed".to_string()),
|
||||||
|
Err(err) => Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources(language: &str) -> Result<Vec<String>, String> {
|
||||||
|
let lang = language.to_lowercase();
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
collect_sources_rec("./src", &lang, &mut sources)?;
|
||||||
|
sources.sort();
|
||||||
|
if sources.is_empty() {
|
||||||
|
return Err("No source files found in ./src".to_string());
|
||||||
|
}
|
||||||
|
Ok(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources_rec(dir: &str, lang: &str, out: &mut Vec<String>) -> Result<(), String> {
|
||||||
|
let entries = fs::read_dir(dir).map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
collect_sources_rec(&path.to_string_lossy(), lang, out)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ext = path
|
||||||
|
.extension()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase();
|
||||||
|
let file = path.to_string_lossy().to_string();
|
||||||
|
let allowed = (lang == "c" && ext == "c")
|
||||||
|
|| ((lang == "c++" || lang == "cpp" || lang == "cxx")
|
||||||
|
&& (ext == "cpp" || ext == "cxx" || ext == "cc"));
|
||||||
|
if allowed {
|
||||||
|
out.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||||
|
match profile {
|
||||||
|
"release" => &["-O3", "-DNDEBUG", "-march=native"],
|
||||||
|
"debug" => &[
|
||||||
|
"-O0",
|
||||||
|
"-g",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-fno-omit-frame-pointer",
|
||||||
|
"-DDEBUG",
|
||||||
|
],
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_objects(
|
||||||
|
compiler: &str,
|
||||||
|
sources: &[String],
|
||||||
|
obj_dir: &Path,
|
||||||
|
ctx: &BuildContext,
|
||||||
|
obj_ext: &str,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for source in sources {
|
||||||
|
let obj_path = object_path(obj_dir, source, obj_ext);
|
||||||
|
if let Some(parent) = Path::new(&obj_path).parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||||
|
}
|
||||||
|
if needs_rebuild(source, &obj_path) {
|
||||||
|
let mut cmd = Command::new(compiler);
|
||||||
|
cmd.arg("-c").arg(source).arg("-o").arg(&obj_path);
|
||||||
|
if !ctx.standard.is_empty() {
|
||||||
|
cmd.arg(format!("-std={}", ctx.standard));
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.include_dirs {
|
||||||
|
cmd.arg(format!("-I{dir}"));
|
||||||
|
}
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.push(obj_path);
|
||||||
|
}
|
||||||
|
Ok(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||||
|
let src_path = Path::new(source);
|
||||||
|
let rel = src_path
|
||||||
|
.strip_prefix("./src")
|
||||||
|
.or_else(|_| src_path.strip_prefix("src"))
|
||||||
|
.unwrap_or(src_path);
|
||||||
|
let mut out = obj_dir.join(rel);
|
||||||
|
out.set_extension(obj_ext.trim_start_matches('.'));
|
||||||
|
out.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||||
|
let src_time = fs::metadata(source).and_then(|m| m.modified());
|
||||||
|
let obj_time = fs::metadata(object).and_then(|m| m.modified());
|
||||||
|
match (src_time, obj_time) {
|
||||||
|
(Ok(s), Ok(o)) => s > o,
|
||||||
|
(Ok(_), Err(_)) => true,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/core/builder/gcc.rs
Normal file
183
src/core/builder/gcc.rs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
use crate::core::builder::BuildContext;
|
||||||
|
use crate::platform;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
|
let compiler = if ctx.compiler.is_empty() {
|
||||||
|
"cc"
|
||||||
|
} else {
|
||||||
|
ctx.compiler
|
||||||
|
};
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let sources = collect_sources(ctx.language)?;
|
||||||
|
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||||
|
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "o")?;
|
||||||
|
|
||||||
|
if ctx.kind == "staticlib" {
|
||||||
|
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||||
|
let mut cmd = Command::new("ar");
|
||||||
|
cmd.arg("rcs").arg(&lib_path);
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
return Ok(elapsed);
|
||||||
|
}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = Command::new(compiler);
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.lib_dirs {
|
||||||
|
cmd.arg(format!("-L{dir}"));
|
||||||
|
}
|
||||||
|
for lib in ctx.libs {
|
||||||
|
cmd.arg(format!("-l{lib}"));
|
||||||
|
}
|
||||||
|
for flag in ctx.ldflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
cmd.arg("-o").arg(platform::bin_path(
|
||||||
|
ctx.profile,
|
||||||
|
ctx.project_name,
|
||||||
|
ctx.target_dir,
|
||||||
|
));
|
||||||
|
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
Ok(elapsed)
|
||||||
|
}
|
||||||
|
Ok(_) => Err("Build failed".to_string()),
|
||||||
|
Err(err) => Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources(language: &str) -> Result<Vec<String>, String> {
|
||||||
|
let lang = language.to_lowercase();
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
collect_sources_rec("./src", &lang, &mut sources)?;
|
||||||
|
sources.sort();
|
||||||
|
if sources.is_empty() {
|
||||||
|
return Err("No source files found in ./src".to_string());
|
||||||
|
}
|
||||||
|
Ok(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources_rec(dir: &str, lang: &str, out: &mut Vec<String>) -> Result<(), String> {
|
||||||
|
let entries = fs::read_dir(dir).map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
collect_sources_rec(&path.to_string_lossy(), lang, out)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ext = path
|
||||||
|
.extension()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase();
|
||||||
|
let file = path.to_string_lossy().to_string();
|
||||||
|
let allowed = (lang == "c" && ext == "c")
|
||||||
|
|| ((lang == "c++" || lang == "cpp" || lang == "cxx")
|
||||||
|
&& (ext == "cpp" || ext == "cxx" || ext == "cc"));
|
||||||
|
if allowed {
|
||||||
|
out.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||||
|
match profile {
|
||||||
|
"release" => &["-O3", "-DNDEBUG", "-march=native"],
|
||||||
|
"debug" => &[
|
||||||
|
"-O0",
|
||||||
|
"-g",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-fno-omit-frame-pointer",
|
||||||
|
"-DDEBUG",
|
||||||
|
],
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_objects(
|
||||||
|
compiler: &str,
|
||||||
|
sources: &[String],
|
||||||
|
obj_dir: &Path,
|
||||||
|
ctx: &BuildContext,
|
||||||
|
obj_ext: &str,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for source in sources {
|
||||||
|
let obj_path = object_path(obj_dir, source, obj_ext);
|
||||||
|
if let Some(parent) = Path::new(&obj_path).parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||||
|
}
|
||||||
|
if needs_rebuild(source, &obj_path) {
|
||||||
|
let mut cmd = Command::new(compiler);
|
||||||
|
cmd.arg("-c").arg(source).arg("-o").arg(&obj_path);
|
||||||
|
if !ctx.standard.is_empty() {
|
||||||
|
cmd.arg(format!("-std={}", ctx.standard));
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.include_dirs {
|
||||||
|
cmd.arg(format!("-I{dir}"));
|
||||||
|
}
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.push(obj_path);
|
||||||
|
}
|
||||||
|
Ok(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||||
|
let src_path = Path::new(source);
|
||||||
|
let rel = src_path
|
||||||
|
.strip_prefix("./src")
|
||||||
|
.or_else(|_| src_path.strip_prefix("src"))
|
||||||
|
.unwrap_or(src_path);
|
||||||
|
let mut out = obj_dir.join(rel);
|
||||||
|
out.set_extension(obj_ext.trim_start_matches('.'));
|
||||||
|
out.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||||
|
let src_time = fs::metadata(source).and_then(|m| m.modified());
|
||||||
|
let obj_time = fs::metadata(object).and_then(|m| m.modified());
|
||||||
|
match (src_time, obj_time) {
|
||||||
|
(Ok(s), Ok(o)) => s > o,
|
||||||
|
(Ok(_), Err(_)) => true,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/core/builder/mod.rs
Normal file
35
src/core/builder/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
pub mod clang;
|
||||||
|
pub mod gcc;
|
||||||
|
pub mod msvc;
|
||||||
|
|
||||||
|
pub struct BuildContext<'a> {
|
||||||
|
pub profile: &'a str,
|
||||||
|
pub project_name: &'a str,
|
||||||
|
pub compiler: &'a str,
|
||||||
|
pub language: &'a str,
|
||||||
|
pub standard: &'a str,
|
||||||
|
pub target_dir: Option<&'a str>,
|
||||||
|
pub kind: &'a str,
|
||||||
|
pub include_dirs: &'a [String],
|
||||||
|
pub lib_dirs: &'a [String],
|
||||||
|
pub libs: &'a [String],
|
||||||
|
pub cflags: &'a [String],
|
||||||
|
pub ldflags: &'a [String],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
|
let compiler = ctx.compiler.to_lowercase();
|
||||||
|
if compiler.contains("clang-cl") {
|
||||||
|
return msvc::build(ctx);
|
||||||
|
}
|
||||||
|
if compiler.contains("gcc") || compiler.contains("g++") {
|
||||||
|
return gcc::build(ctx);
|
||||||
|
}
|
||||||
|
if compiler.contains("clang") || compiler.contains("clang++") {
|
||||||
|
return clang::build(ctx);
|
||||||
|
}
|
||||||
|
if compiler == "cl" || compiler.contains("msvc") {
|
||||||
|
return msvc::build(ctx);
|
||||||
|
}
|
||||||
|
clang::build(ctx)
|
||||||
|
}
|
||||||
236
src/core/builder/msvc.rs
Normal file
236
src/core/builder/msvc.rs
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
use crate::core::builder::BuildContext;
|
||||||
|
use crate::platform;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||||
|
let compiler = if ctx.compiler.is_empty() {
|
||||||
|
"cl"
|
||||||
|
} else {
|
||||||
|
ctx.compiler
|
||||||
|
};
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let sources = collect_sources(ctx.language)?;
|
||||||
|
let obj_dir = Path::new("./target").join(ctx.profile).join("obj");
|
||||||
|
let objects = build_objects(compiler, &sources, &obj_dir, ctx, "obj")?;
|
||||||
|
|
||||||
|
if ctx.kind == "staticlib" {
|
||||||
|
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||||
|
let mut cmd = Command::new("lib");
|
||||||
|
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
return Ok(elapsed);
|
||||||
|
}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.standard.is_empty() {
|
||||||
|
let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
|
||||||
|
cmd.arg(std_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
for obj in &objects {
|
||||||
|
cmd.arg(obj);
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.lib_dirs {
|
||||||
|
cmd.arg(format!("/LIBPATH:{dir}"));
|
||||||
|
}
|
||||||
|
for lib in ctx.libs {
|
||||||
|
if lib.to_lowercase().ends_with(".lib") {
|
||||||
|
cmd.arg(lib);
|
||||||
|
} else {
|
||||||
|
cmd.arg(format!("{lib}.lib"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ctx.ldflags.is_empty() {
|
||||||
|
cmd.arg("/link");
|
||||||
|
for flag in ctx.ldflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.arg(format!(
|
||||||
|
"/Fe:{}",
|
||||||
|
platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir)
|
||||||
|
));
|
||||||
|
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||||
|
Ok(elapsed)
|
||||||
|
}
|
||||||
|
Ok(_) => Err("Build failed".to_string()),
|
||||||
|
Err(err) => Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources(language: &str) -> Result<Vec<String>, String> {
|
||||||
|
let lang = language.to_lowercase();
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
collect_sources_rec("./src", &lang, &mut sources)?;
|
||||||
|
sources.sort();
|
||||||
|
if sources.is_empty() {
|
||||||
|
return Err("No source files found in ./src".to_string());
|
||||||
|
}
|
||||||
|
Ok(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_sources_rec(dir: &str, lang: &str, out: &mut Vec<String>) -> Result<(), String> {
|
||||||
|
let entries = fs::read_dir(dir).map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|err| format!("src dir error: {err}"))?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
collect_sources_rec(&path.to_string_lossy(), lang, out)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !path.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ext = path
|
||||||
|
.extension()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase();
|
||||||
|
let file = path.to_string_lossy().to_string();
|
||||||
|
let allowed = (lang == "c" && ext == "c")
|
||||||
|
|| ((lang == "c++" || lang == "cpp" || lang == "cxx")
|
||||||
|
&& (ext == "cpp" || ext == "cxx" || ext == "cc"));
|
||||||
|
if allowed {
|
||||||
|
out.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msvc_standard_flag(language: &str, standard: &str) -> Result<String, String> {
|
||||||
|
let lang = language.to_lowercase();
|
||||||
|
let std = standard.to_lowercase();
|
||||||
|
if lang == "c" {
|
||||||
|
return match std.as_str() {
|
||||||
|
"c11" => Ok("/std:c11".to_string()),
|
||||||
|
"c17" => Ok("/std:c17".to_string()),
|
||||||
|
_ => Err("Unsupported C standard for MSVC".to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if lang == "c++" || lang == "cpp" || lang == "cxx" {
|
||||||
|
return match std.as_str() {
|
||||||
|
"c++11" => Ok("/std:c++11".to_string()),
|
||||||
|
"c++14" => Ok("/std:c++14".to_string()),
|
||||||
|
"c++17" => Ok("/std:c++17".to_string()),
|
||||||
|
"c++20" => Ok("/std:c++20".to_string()),
|
||||||
|
"c++23" => Ok("/std:c++latest".to_string()),
|
||||||
|
_ => Err("Unsupported C++ standard for MSVC".to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err("Unsupported language".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||||
|
match profile {
|
||||||
|
"release" => &["/O2", "/DNDEBUG"],
|
||||||
|
"debug" => &["/Od", "/Zi", "/W4", "/DDEBUG", "/Oy-"],
|
||||||
|
_ => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_objects(
|
||||||
|
compiler: &str,
|
||||||
|
sources: &[String],
|
||||||
|
obj_dir: &Path,
|
||||||
|
ctx: &BuildContext,
|
||||||
|
obj_ext: &str,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
let mut objects = Vec::new();
|
||||||
|
for source in sources {
|
||||||
|
let obj_path = object_path(obj_dir, source, obj_ext);
|
||||||
|
if let Some(parent) = Path::new(&obj_path).parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|err| format!("obj dir error: {err}"))?;
|
||||||
|
}
|
||||||
|
if needs_rebuild(source, &obj_path) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ctx.standard.is_empty() {
|
||||||
|
let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?;
|
||||||
|
cmd.arg(std_flag);
|
||||||
|
}
|
||||||
|
for flag in default_flags(ctx.profile) {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for flag in ctx.cflags {
|
||||||
|
cmd.arg(flag);
|
||||||
|
}
|
||||||
|
for dir in ctx.include_dirs {
|
||||||
|
cmd.arg(format!("/I{dir}"));
|
||||||
|
}
|
||||||
|
cmd.arg("/c").arg(source).arg(format!("/Fo:{}", obj_path));
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) if status.success() => {}
|
||||||
|
Ok(_) => return Err("Build failed".to_string()),
|
||||||
|
Err(err) => return Err(format!("Build failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.push(obj_path);
|
||||||
|
}
|
||||||
|
Ok(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
||||||
|
let src_path = Path::new(source);
|
||||||
|
let rel = src_path
|
||||||
|
.strip_prefix("./src")
|
||||||
|
.or_else(|_| src_path.strip_prefix("src"))
|
||||||
|
.unwrap_or(src_path);
|
||||||
|
let mut out = obj_dir.join(rel);
|
||||||
|
out.set_extension(obj_ext.trim_start_matches('.'));
|
||||||
|
out.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||||
|
let src_time = fs::metadata(source).and_then(|m| m.modified());
|
||||||
|
let obj_time = fs::metadata(object).and_then(|m| m.modified());
|
||||||
|
match (src_time, obj_time) {
|
||||||
|
(Ok(s), Ok(o)) => s > o,
|
||||||
|
(Ok(_), Err(_)) => true,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ use toml::map::Map;
|
|||||||
|
|
||||||
const DEFAULT_VERSION: &str = "0.1.0";
|
const DEFAULT_VERSION: &str = "0.1.0";
|
||||||
const DEFAULT_LANGUAGE: &str = "c";
|
const DEFAULT_LANGUAGE: &str = "c";
|
||||||
|
const DEFAULT_STANDARD: &str = "c11";
|
||||||
const DEFAULT_COMPILER: &str = "clang";
|
const DEFAULT_COMPILER: &str = "clang";
|
||||||
|
const DEFAULT_KIND: &str = "bin";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
@@ -101,12 +103,30 @@ impl Config {
|
|||||||
.and_then(|v| v.as_table())
|
.and_then(|v| v.as_table())
|
||||||
.ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?;
|
.ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?;
|
||||||
|
|
||||||
for key in ["name", "version", "language", "compiler"] {
|
for key in ["name", "version"] {
|
||||||
let value = package.get(key).and_then(|v| v.as_str()).unwrap_or("");
|
let value = package.get(key).and_then(|v| v.as_str()).unwrap_or("");
|
||||||
if value.trim().is_empty() {
|
if value.trim().is_empty() {
|
||||||
return Err(ConfigError::Invalid(format!("package.{key} is empty")));
|
return Err(ConfigError::Invalid(format!("package.{key} is empty")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let build = self
|
||||||
|
.get("build")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.ok_or_else(|| ConfigError::Invalid("missing [build]".into()))?;
|
||||||
|
|
||||||
|
for key in ["language", "standard", "compiler", "kind"] {
|
||||||
|
let value = build.get(key).and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
if value.trim().is_empty() {
|
||||||
|
return Err(ConfigError::Invalid(format!("build.{key} is empty")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(kind) = build.get("kind").and_then(|v| v.as_str()) {
|
||||||
|
let kind = kind.trim();
|
||||||
|
if kind != "bin" && kind != "staticlib" {
|
||||||
|
return Err(ConfigError::Invalid("build.kind is invalid".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +148,7 @@ fn read_toml(path: &Path) -> Result<Value, ConfigError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> {
|
fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> {
|
||||||
let content = toml::to_string(value)?;
|
let content = format_toml(value)?;
|
||||||
fs::write(path, content)?;
|
fs::write(path, content)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -145,22 +165,134 @@ fn default_toml() -> Result<Value, ConfigError> {
|
|||||||
"version".to_string(),
|
"version".to_string(),
|
||||||
Value::String(DEFAULT_VERSION.to_string()),
|
Value::String(DEFAULT_VERSION.to_string()),
|
||||||
);
|
);
|
||||||
package.insert(
|
let mut build = Map::new();
|
||||||
|
build.insert(
|
||||||
"language".to_string(),
|
"language".to_string(),
|
||||||
Value::String(DEFAULT_LANGUAGE.to_string()),
|
Value::String(DEFAULT_LANGUAGE.to_string()),
|
||||||
);
|
);
|
||||||
package.insert(
|
build.insert(
|
||||||
|
"standard".to_string(),
|
||||||
|
Value::String(DEFAULT_STANDARD.to_string()),
|
||||||
|
);
|
||||||
|
build.insert(
|
||||||
"compiler".to_string(),
|
"compiler".to_string(),
|
||||||
Value::String(DEFAULT_COMPILER.to_string()),
|
Value::String(DEFAULT_COMPILER.to_string()),
|
||||||
);
|
);
|
||||||
|
build.insert("kind".to_string(), Value::String(DEFAULT_KIND.to_string()));
|
||||||
|
|
||||||
let mut root = Map::new();
|
let mut root = Map::new();
|
||||||
root.insert("package".to_string(), Value::Table(package));
|
root.insert("package".to_string(), Value::Table(package));
|
||||||
|
root.insert("build".to_string(), Value::Table(build));
|
||||||
root.insert("dependencies".to_string(), Value::Table(Map::new()));
|
root.insert("dependencies".to_string(), Value::Table(Map::new()));
|
||||||
|
|
||||||
Ok(Value::Table(root))
|
Ok(Value::Table(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
||||||
|
let root = value
|
||||||
|
.as_table()
|
||||||
|
.ok_or_else(|| ConfigError::Invalid("root is not a table".into()))?;
|
||||||
|
|
||||||
|
let package = root
|
||||||
|
.get("package")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?;
|
||||||
|
let build = root
|
||||||
|
.get("build")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.ok_or_else(|| ConfigError::Invalid("missing [build]".into()))?;
|
||||||
|
let deps = root
|
||||||
|
.get("dependencies")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.ok_or_else(|| ConfigError::Invalid("missing [dependencies]".into()))?;
|
||||||
|
|
||||||
|
let name = package.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let version = package
|
||||||
|
.get("version")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
let language = build.get("language").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let standard = build.get("standard").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let compiler = build.get("compiler").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str("[package]\n");
|
||||||
|
out.push_str(&format!("name = \"{name}\"\n"));
|
||||||
|
out.push_str(&format!("version = \"{version}\"\n\n"));
|
||||||
|
|
||||||
|
out.push_str("[build]\n");
|
||||||
|
out.push_str(&format!("language = \"{language}\"\n"));
|
||||||
|
out.push_str(&format!("standard = \"{standard}\"\n"));
|
||||||
|
out.push_str(&format!("compiler = \"{compiler}\"\n"));
|
||||||
|
let kind = build
|
||||||
|
.get("kind")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or(DEFAULT_KIND);
|
||||||
|
out.push_str(&format!("kind = \"{kind}\"\n"));
|
||||||
|
if let Some(target) = build.get("target").and_then(|v| v.as_str())
|
||||||
|
&& !target.trim().is_empty()
|
||||||
|
{
|
||||||
|
out.push_str(&format!("target = \"{target}\"\n"));
|
||||||
|
}
|
||||||
|
if let Some(cflags) = build.get("cflags") {
|
||||||
|
out.push_str(&format!("cflags = {}\n", format_string_array(cflags)));
|
||||||
|
}
|
||||||
|
if let Some(ldflags) = build.get("ldflags") {
|
||||||
|
out.push_str(&format!("ldflags = {}\n", format_string_array(ldflags)));
|
||||||
|
}
|
||||||
|
out.push('\n');
|
||||||
|
|
||||||
|
out.push_str("[dependencies]\n");
|
||||||
|
if !deps.is_empty() {
|
||||||
|
let mut keys: Vec<&String> = deps.keys().collect();
|
||||||
|
keys.sort();
|
||||||
|
for key in keys {
|
||||||
|
if let Some(val) = deps.get(key) {
|
||||||
|
out.push_str(&format!("{key} = {}\n", format_dep_value(val)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_dep_value(value: &Value) -> String {
|
||||||
|
match value {
|
||||||
|
Value::String(s) => format!("\"{s}\""),
|
||||||
|
Value::Table(tbl) => {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
if let Some(v) = tbl.get("path").and_then(|v| v.as_str()) {
|
||||||
|
parts.push(format!("path = \"{v}\""));
|
||||||
|
}
|
||||||
|
if let Some(v) = tbl.get("system").and_then(|v| v.as_bool()) {
|
||||||
|
parts.push(format!("system = {}", if v { "true" } else { "false" }));
|
||||||
|
}
|
||||||
|
if let Some(v) = tbl.get("include") {
|
||||||
|
parts.push(format!("include = {}", format_string_array(v)));
|
||||||
|
}
|
||||||
|
if let Some(v) = tbl.get("lib") {
|
||||||
|
parts.push(format!("lib = {}", format_string_array(v)));
|
||||||
|
}
|
||||||
|
if let Some(v) = tbl.get("libs") {
|
||||||
|
parts.push(format!("libs = {}", format_string_array(v)));
|
||||||
|
}
|
||||||
|
format!("{{ {} }}", parts.join(", "))
|
||||||
|
}
|
||||||
|
_ => "\"\"".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_string_array(value: &Value) -> String {
|
||||||
|
if let Some(arr) = value.as_array() {
|
||||||
|
let items: Vec<String> = arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_str().map(|s| format!("\"{s}\"")))
|
||||||
|
.collect();
|
||||||
|
return format!("[{}]", items.join(", "));
|
||||||
|
}
|
||||||
|
"[]".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> {
|
fn get_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> {
|
||||||
let mut current = value;
|
let mut current = value;
|
||||||
for key in path {
|
for key in path {
|
||||||
|
|||||||
353
src/core/deps.rs
Normal file
353
src/core/deps.rs
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
use crate::core::config::Config;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use toml::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DepSpec {
|
||||||
|
pub name: String,
|
||||||
|
pub path_raw: String,
|
||||||
|
pub include_raw: Option<Vec<String>>,
|
||||||
|
pub lib_raw: Option<Vec<String>>,
|
||||||
|
pub libs_raw: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolvedDeps {
|
||||||
|
pub include_dirs: Vec<String>,
|
||||||
|
pub lib_dirs: Vec<String>,
|
||||||
|
pub libs: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_deps(
|
||||||
|
config: &Config,
|
||||||
|
profile: &str,
|
||||||
|
project_root: &Path,
|
||||||
|
) -> Result<ResolvedDeps, String> {
|
||||||
|
let project_name = config
|
||||||
|
.get("package.name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
let project_version = config
|
||||||
|
.get("package.version")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
let deps = parse_dependencies(config)?;
|
||||||
|
if deps.is_empty() {
|
||||||
|
return Ok(ResolvedDeps {
|
||||||
|
include_dirs: Vec::new(),
|
||||||
|
lib_dirs: Vec::new(),
|
||||||
|
libs: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut include_dirs = Vec::new();
|
||||||
|
let mut lib_dirs = Vec::new();
|
||||||
|
let mut libs = Vec::new();
|
||||||
|
let mut lock_packages = Vec::new();
|
||||||
|
|
||||||
|
for dep in deps {
|
||||||
|
let dep_path = resolve_path(project_root, &dep.path_raw)?;
|
||||||
|
if !dep_path.is_dir() {
|
||||||
|
return Err(format!(
|
||||||
|
"Dependency '{}' path is not a directory: {}",
|
||||||
|
dep.name,
|
||||||
|
dep_path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let include = resolve_paths(&dep_path, dep.include_raw.as_deref(), &["include"])?;
|
||||||
|
let lib = resolve_paths(&dep_path, dep.lib_raw.as_deref(), &["lib", "lib64"])?;
|
||||||
|
let libs_list = dep
|
||||||
|
.libs_raw
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| vec![dep.name.clone()]);
|
||||||
|
|
||||||
|
if include.is_empty() || lib.is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"Dependency '{}' missing include/lib dirs. Add include/lib in dcr.toml.",
|
||||||
|
dep.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
include_dirs.extend(include.iter().map(|p| p.to_string_lossy().to_string()));
|
||||||
|
lib_dirs.extend(lib.iter().map(|p| p.to_string_lossy().to_string()));
|
||||||
|
libs.extend(libs_list.iter().cloned());
|
||||||
|
|
||||||
|
let dep_cache = project_root
|
||||||
|
.join("target")
|
||||||
|
.join(profile)
|
||||||
|
.join("deps")
|
||||||
|
.join(&dep.name);
|
||||||
|
sync_dep_dir(&dep_path, &dep_cache)
|
||||||
|
.map_err(|err| format!("Failed to sync dep {}: {err}", dep.name))?;
|
||||||
|
|
||||||
|
let dep_version = read_dep_version(&dep_path).unwrap_or_else(|| "0.0.0".to_string());
|
||||||
|
let dep_checksum = compute_checksum(&dep_path)
|
||||||
|
.map_err(|err| format!("Failed to hash dep {}: {err}", dep.name))?;
|
||||||
|
lock_packages.push(DepLock {
|
||||||
|
name: dep.name.clone(),
|
||||||
|
version: dep_version,
|
||||||
|
checksum: dep_checksum,
|
||||||
|
source: format!("path+{}", dep.path_raw),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
write_lock(
|
||||||
|
project_root,
|
||||||
|
&project_name,
|
||||||
|
&project_version,
|
||||||
|
&lock_packages,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(ResolvedDeps {
|
||||||
|
include_dirs,
|
||||||
|
lib_dirs,
|
||||||
|
libs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct DepLock {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
checksum: String,
|
||||||
|
source: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dependencies(config: &Config) -> Result<Vec<DepSpec>, String> {
|
||||||
|
let deps_val = match config.get("dependencies") {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
let deps_table = deps_val
|
||||||
|
.as_table()
|
||||||
|
.ok_or_else(|| "dependencies is not a table".to_string())?;
|
||||||
|
let mut deps = Vec::new();
|
||||||
|
for (name, value) in deps_table {
|
||||||
|
match value {
|
||||||
|
Value::Table(tbl) => {
|
||||||
|
if tbl.get("system").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||||
|
return Err(format!(
|
||||||
|
"Dependency '{}' uses system=true, which is not supported yet",
|
||||||
|
name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let path = tbl
|
||||||
|
.get("path")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| format!("Dependency '{}' missing path", name))?
|
||||||
|
.to_string();
|
||||||
|
let include = parse_string_list(tbl.get("include"), name, "include")?;
|
||||||
|
let lib = parse_string_list(tbl.get("lib"), name, "lib")?;
|
||||||
|
let libs = parse_string_list(tbl.get("libs"), name, "libs")?;
|
||||||
|
deps.push(DepSpec {
|
||||||
|
name: name.to_string(),
|
||||||
|
path_raw: path,
|
||||||
|
include_raw: include,
|
||||||
|
lib_raw: lib,
|
||||||
|
libs_raw: libs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"Dependency '{}' must be a table with path/include/lib",
|
||||||
|
name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deps.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
Ok(deps)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_string_list(
|
||||||
|
value: Option<&Value>,
|
||||||
|
dep_name: &str,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<Option<Vec<String>>, String> {
|
||||||
|
let Some(value) = value else { return Ok(None) };
|
||||||
|
let list = value.as_array().ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Dependency '{}' field '{}' must be an array of strings",
|
||||||
|
dep_name, key
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for item in list {
|
||||||
|
let s = item
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| format!("Dependency '{}' field '{}' must be strings", dep_name, key))?;
|
||||||
|
out.push(s.to_string());
|
||||||
|
}
|
||||||
|
Ok(Some(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_path(project_root: &Path, raw: &str) -> Result<PathBuf, String> {
|
||||||
|
let p = Path::new(raw);
|
||||||
|
let full = if p.is_absolute() {
|
||||||
|
p.to_path_buf()
|
||||||
|
} else {
|
||||||
|
project_root.join(p)
|
||||||
|
};
|
||||||
|
Ok(full)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_paths(
|
||||||
|
base: &Path,
|
||||||
|
raw: Option<&[String]>,
|
||||||
|
defaults: &[&str],
|
||||||
|
) -> Result<Vec<PathBuf>, String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if let Some(raw) = raw {
|
||||||
|
for r in raw {
|
||||||
|
let p = Path::new(r);
|
||||||
|
let full = if p.is_absolute() {
|
||||||
|
p.to_path_buf()
|
||||||
|
} else {
|
||||||
|
base.join(p)
|
||||||
|
};
|
||||||
|
if !full.exists() {
|
||||||
|
return Err(format!("Path does not exist: {}", full.display()));
|
||||||
|
}
|
||||||
|
out.push(full);
|
||||||
|
}
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
for d in defaults {
|
||||||
|
let candidate = base.join(d);
|
||||||
|
if candidate.exists() {
|
||||||
|
out.push(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_dep_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
|
if dst.exists() {
|
||||||
|
fs::remove_dir_all(dst)?;
|
||||||
|
}
|
||||||
|
copy_dir_all(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
|
fs::create_dir_all(dst)?;
|
||||||
|
for entry in fs::read_dir(src)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let ty = entry.file_type()?;
|
||||||
|
let from = entry.path();
|
||||||
|
let to = dst.join(entry.file_name());
|
||||||
|
if ty.is_dir() {
|
||||||
|
copy_dir_all(&from, &to)?;
|
||||||
|
} else {
|
||||||
|
fs::copy(&from, &to)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_lock(
|
||||||
|
project_root: &Path,
|
||||||
|
project_name: &str,
|
||||||
|
project_version: &str,
|
||||||
|
packages: &[DepLock],
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut out = String::new();
|
||||||
|
if !packages.is_empty() {
|
||||||
|
out.push_str("[[package]]\n");
|
||||||
|
out.push_str(&format!("name = \"{}\"\n", escape_value(project_name)));
|
||||||
|
out.push_str(&format!(
|
||||||
|
"version = \"{}\"\n",
|
||||||
|
escape_value(project_version)
|
||||||
|
));
|
||||||
|
out.push_str(&format!(
|
||||||
|
"dependencies = [{}]\n\n",
|
||||||
|
quote_list(&packages.iter().map(|p| p.name.clone()).collect::<Vec<_>>())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for pkg in packages {
|
||||||
|
out.push_str("[[package]]\n");
|
||||||
|
out.push_str(&format!("name = \"{}\"\n", escape_value(&pkg.name)));
|
||||||
|
out.push_str(&format!("version = \"{}\"\n", escape_value(&pkg.version)));
|
||||||
|
out.push_str(&format!("source = \"{}\"\n", escape_value(&pkg.source)));
|
||||||
|
out.push_str(&format!("checksum = \"{}\"\n", escape_value(&pkg.checksum)));
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
fs::write(project_root.join("dcr.lock"), out)
|
||||||
|
.map_err(|err| format!("Failed to write dcr.lock: {err}"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quote_list(items: &[String]) -> String {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.map(|s| format!("\"{}\"", escape_value(s)))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape_value(input: &str) -> String {
|
||||||
|
input.replace('\\', "\\\\").replace('"', "\\\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dep_version(dep_path: &Path) -> Option<String> {
|
||||||
|
let path = dep_path.join("dcr.toml");
|
||||||
|
let content = fs::read_to_string(path).ok()?;
|
||||||
|
let value: Value = content.parse().ok()?;
|
||||||
|
value
|
||||||
|
.get("package")
|
||||||
|
.and_then(|v| v.as_table())
|
||||||
|
.and_then(|t| t.get("version"))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_checksum(root: &Path) -> Result<String, String> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
collect_files(root, root, &mut files)?;
|
||||||
|
files.sort();
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
for rel in files {
|
||||||
|
hasher.update(rel.as_bytes());
|
||||||
|
let data =
|
||||||
|
fs::read(root.join(&rel)).map_err(|err| format!("failed to read {}: {err}", rel))?;
|
||||||
|
hasher.update(&data);
|
||||||
|
}
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
Ok(to_hex(&hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_files(root: &Path, dir: &Path, out: &mut Vec<String>) -> Result<(), String> {
|
||||||
|
for entry in fs::read_dir(dir).map_err(|err| format!("read_dir failed: {err}"))? {
|
||||||
|
let entry = entry.map_err(|err| format!("read_dir failed: {err}"))?;
|
||||||
|
let path = entry.path();
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name_str = name.to_string_lossy();
|
||||||
|
if name_str == "target" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if path.is_dir() {
|
||||||
|
collect_files(root, &path, out)?;
|
||||||
|
} else if path.is_file() {
|
||||||
|
let rel = path
|
||||||
|
.strip_prefix(root)
|
||||||
|
.unwrap_or(&path)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
out.push(rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_hex(bytes: &[u8]) -> String {
|
||||||
|
let mut out = String::with_capacity(bytes.len() * 2);
|
||||||
|
for b in bytes {
|
||||||
|
out.push_str(&format!("{:02x}", b));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
pub mod builder;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod deps;
|
||||||
|
pub mod runner;
|
||||||
|
|||||||
37
src/core/runner.rs
Normal file
37
src/core/runner.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::platform;
|
||||||
|
use crate::utils::fs::check_dir;
|
||||||
|
use crate::utils::log::warn;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 {
|
||||||
|
let bin_path = platform::bin_path(profile, project_name, target_dir);
|
||||||
|
if Path::new(&bin_path).exists() {
|
||||||
|
return Command::new(bin_path)
|
||||||
|
.status()
|
||||||
|
.map(|status| status.code().unwrap_or(1))
|
||||||
|
.unwrap_or(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_dir.is_none()
|
||||||
|
&& check_dir(Some(&format!("target/{profile}")))
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains(&profile.to_string())
|
||||||
|
{
|
||||||
|
warn("Launch of the latest release");
|
||||||
|
return Command::new(platform::bin_path(profile, project_name, target_dir))
|
||||||
|
.status()
|
||||||
|
.map(|status| status.code().unwrap_or(1))
|
||||||
|
.unwrap_or(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_dir.is_some() && Path::new(&bin_path).exists() {
|
||||||
|
warn("Launch of the latest release");
|
||||||
|
return Command::new(bin_path)
|
||||||
|
.status()
|
||||||
|
.map(|status| status.code().unwrap_or(1))
|
||||||
|
.unwrap_or(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
1
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ use crate::utils::log::error;
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod platform;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
13
src/platform/linux.rs
Normal file
13
src/platform/linux.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/{name}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/lib{name}.a"),
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/platform/macos.rs
Normal file
13
src/platform/macos.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/{name}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/lib{name}.a"),
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/platform/mod.rs
Normal file
50
src/platform/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub mod linux;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub mod macos;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub mod windows;
|
||||||
|
|
||||||
|
pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
linux::bin_path(profile, name, target_dir)
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
return macos::bin_path(profile, name, target_dir);
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
return windows::bin_path(profile, name, target_dir);
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||||
|
{
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/{name}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
linux::lib_path(profile, name, target_dir)
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
return macos::lib_path(profile, name, target_dir);
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
return windows::lib_path(profile, name, target_dir);
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||||
|
{
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/lib{name}.a"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/platform/windows.rs
Normal file
13
src/platform/windows.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/{}.exe", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/{name}.exe"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||||
|
match target_dir {
|
||||||
|
Some(dir) => format!("{}/{}.lib", dir.trim_end_matches('/'), name),
|
||||||
|
None => format!("./target/{profile}/{name}.lib"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user