mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-22 11:19:41 +00:00
Compare commits
9 Commits
588e4dd13f
...
v0.8.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 689fea7465 | |||
| 1dcc8e614a | |||
| a3f8444c20 | |||
| 1f3fc62cb6 | |||
| f8d8c95912 | |||
| 9e07f70c78 | |||
| 0376eded3f | |||
| 803d5891ce | |||
| 97db82e4f8 |
89
.github/workflows/ci.yml
vendored
89
.github/workflows/ci.yml
vendored
@@ -5,16 +5,10 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- main
|
- main
|
||||||
- dev
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
RUSTFLAGS: -Dwarnings
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
checks:
|
checks:
|
||||||
name: fmt / clippy / check
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -24,90 +18,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
|
|
||||||
- name: Clippy (default features)
|
- name: Lint
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
|
||||||
|
|
||||||
- name: Clippy (all features)
|
|
||||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
- name: Check (all features)
|
- name: Check
|
||||||
run: cargo check --all-targets --all-features
|
run: cargo check --all-targets --all-features
|
||||||
|
|
||||||
test:
|
|
||||||
name: tests (${{ matrix.os }})
|
|
||||||
needs: checks
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Install Linux tools
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
clang \
|
|
||||||
nasm \
|
|
||||||
clang-format \
|
|
||||||
clang-tidy \
|
|
||||||
git
|
|
||||||
|
|
||||||
- name: Install macOS tools
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
brew install nasm llvm || true
|
|
||||||
echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Install Windows tools
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
choco install nasm -y
|
|
||||||
echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
||||||
|
|
||||||
- name: Unit tests (binary crate)
|
|
||||||
run: cargo test --bins -- --nocapture
|
|
||||||
|
|
||||||
- name: Integration tests (default features)
|
|
||||||
run: cargo test --tests -- --nocapture
|
|
||||||
|
|
||||||
- name: Integration tests (archive feature)
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
run: cargo test --tests --features archive -- --nocapture
|
|
||||||
|
|
||||||
test-archive-linux:
|
|
||||||
name: archive feature smoke
|
|
||||||
needs: checks
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Install tools
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y build-essential clang nasm
|
|
||||||
|
|
||||||
- name: Build with archive
|
|
||||||
run: cargo build --features archive
|
|
||||||
|
|
||||||
- name: Flat-bin + unit suite with archive
|
|
||||||
run: cargo test --features archive --test cli_build --test cli_workspace --test cli_errors -- --nocapture
|
|
||||||
|
|||||||
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
@@ -223,9 +223,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
||||||
cargo zigbuild --release --features archive --target ${{ matrix.target }}
|
cargo zigbuild --release --target ${{ matrix.target }}
|
||||||
else
|
else
|
||||||
cargo build --release --features archive --target ${{ matrix.target }}
|
cargo build --release --target ${{ matrix.target }}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Prepare Artifact
|
- name: Prepare Artifact
|
||||||
@@ -298,9 +298,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install cargo-deb
|
cargo install cargo-deb
|
||||||
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
||||||
cargo zigbuild --release --features archive --target ${{ matrix.target }}
|
cargo zigbuild --release --target ${{ matrix.target }}
|
||||||
else
|
else
|
||||||
cargo build --release --features archive --target ${{ matrix.target }}
|
cargo build --release --target ${{ matrix.target }}
|
||||||
fi
|
fi
|
||||||
cargo deb --no-build --target ${{ matrix.target }}
|
cargo deb --no-build --target ${{ matrix.target }}
|
||||||
mkdir -p dist-packages && cp target/${{ matrix.target }}/debian/*.deb dist-packages/
|
mkdir -p dist-packages && cp target/${{ matrix.target }}/debian/*.deb dist-packages/
|
||||||
@@ -311,9 +311,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install cargo-generate-rpm
|
cargo install cargo-generate-rpm
|
||||||
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
|
||||||
cargo zigbuild --release --features archive --target ${{ matrix.target }}
|
cargo zigbuild --release --target ${{ matrix.target }}
|
||||||
else
|
else
|
||||||
cargo build --release --features archive --target ${{ matrix.target }}
|
cargo build --release --target ${{ matrix.target }}
|
||||||
fi
|
fi
|
||||||
cargo generate-rpm --target ${{ matrix.target }}
|
cargo generate-rpm --target ${{ matrix.target }}
|
||||||
mkdir -p dist-packages && cp target/${{ matrix.target }}/generate-rpm/*.rpm dist-packages/
|
mkdir -p dist-packages && cp target/${{ matrix.target }}/generate-rpm/*.rpm dist-packages/
|
||||||
@@ -322,7 +322,7 @@ jobs:
|
|||||||
- name: Build Snap Package
|
- name: Build Snap Package
|
||||||
if: matrix.package_type == 'snap'
|
if: matrix.package_type == 'snap'
|
||||||
run: |
|
run: |
|
||||||
cargo build --release --features archive
|
cargo build --release
|
||||||
mkdir -p packaging/snap/dist/bin
|
mkdir -p packaging/snap/dist/bin
|
||||||
cp target/release/dcr packaging/snap/dist/bin/
|
cp target/release/dcr packaging/snap/dist/bin/
|
||||||
cp -r man packaging/snap/dist/man
|
cp -r man packaging/snap/dist/man
|
||||||
@@ -364,7 +364,7 @@ jobs:
|
|||||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||||
unset CFLAGS CXXFLAGS LDFLAGS
|
unset CFLAGS CXXFLAGS LDFLAGS
|
||||||
export RUSTFLAGS="-C linker=cc"
|
export RUSTFLAGS="-C linker=cc"
|
||||||
cargo build --release --locked --features archive
|
cargo build --release --locked
|
||||||
}
|
}
|
||||||
package() {
|
package() {
|
||||||
cd "\$srcdir/dcr-${PKGVER_ORIG}"
|
cd "\$srcdir/dcr-${PKGVER_ORIG}"
|
||||||
@@ -454,7 +454,7 @@ jobs:
|
|||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --features archive --target ${{ matrix.target }}
|
run: cargo build --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Prepare Artifact
|
- name: Prepare Artifact
|
||||||
run: |
|
run: |
|
||||||
@@ -499,7 +499,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/bin:/usr/local/sbin:$PATH"
|
export PATH="/usr/local/bin:/usr/local/sbin:$PATH"
|
||||||
pkg install -y rust perl5 gmake
|
pkg install -y rust perl5 gmake
|
||||||
cargo build --release --features archive
|
cargo build --release
|
||||||
|
|
||||||
- name: Prepare FreeBSD Artifact
|
- name: Prepare FreeBSD Artifact
|
||||||
if: matrix.system == 'freebsd'
|
if: matrix.system == 'freebsd'
|
||||||
@@ -517,7 +517,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export PATH="/usr/local/bin:$PATH"
|
export PATH="/usr/local/bin:$PATH"
|
||||||
pkg_add rust perl gmake
|
pkg_add rust perl gmake
|
||||||
cargo build --release --features archive
|
cargo build --release
|
||||||
|
|
||||||
- name: Prepare OpenBSD Artifact
|
- name: Prepare OpenBSD Artifact
|
||||||
if: matrix.system == 'openbsd'
|
if: matrix.system == 'openbsd'
|
||||||
@@ -537,7 +537,7 @@ jobs:
|
|||||||
mkdir -p /tmp/bin
|
mkdir -p /tmp/bin
|
||||||
ln -sf /usr/pkg/bin/gmake /tmp/bin/make
|
ln -sf /usr/pkg/bin/gmake /tmp/bin/make
|
||||||
export PATH="/tmp/bin:/usr/pkg/bin:/usr/pkg/sbin:$PATH"
|
export PATH="/tmp/bin:/usr/pkg/bin:/usr/pkg/sbin:$PATH"
|
||||||
cargo build --release --features archive
|
cargo build --release
|
||||||
|
|
||||||
- name: Prepare NetBSD Artifact
|
- name: Prepare NetBSD Artifact
|
||||||
if: matrix.system == 'netbsd'
|
if: matrix.system == 'netbsd'
|
||||||
@@ -585,7 +585,7 @@ jobs:
|
|||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --features archive --target ${{ matrix.target }}
|
run: cargo build --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Prepare Artifact
|
- name: Prepare Artifact
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -654,7 +654,7 @@ jobs:
|
|||||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||||
unset CFLAGS CXXFLAGS LDFLAGS
|
unset CFLAGS CXXFLAGS LDFLAGS
|
||||||
export RUSTFLAGS="-C linker=cc"
|
export RUSTFLAGS="-C linker=cc"
|
||||||
cargo build --release --locked --features archive
|
cargo build --release --locked
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
@@ -919,7 +919,7 @@ jobs:
|
|||||||
cd "\$srcdir/dcr-${PKGVER_ORIG}"
|
cd "\$srcdir/dcr-${PKGVER_ORIG}"
|
||||||
unset CFLAGS CXXFLAGS LDFLAGS
|
unset CFLAGS CXXFLAGS LDFLAGS
|
||||||
export RUSTFLAGS="-C linker=cc"
|
export RUSTFLAGS="-C linker=cc"
|
||||||
cargo build --release --locked --features archive
|
cargo build --release --locked
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
|
|||||||
14
.github/workflows/trigger-site-build.yml
vendored
Normal file
14
.github/workflows/trigger-site-build.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: Trigger site rebuild
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger Cloudflare Pages build
|
||||||
|
run: curl -sS -X POST "${{ secrets.CF_PAGES_DEPLOY_HOOK }}"
|
||||||
56
AGENTS.md
Normal file
56
AGENTS.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release && ./target/release/dcr setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pre-PR (CI) gate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all --check
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
cargo check --all-targets --all-features
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
CI (`.github/workflows/ci.yml`) runs fmt, clippy, check — **not** tests.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Integration tests in `tests/` build the dcr binary and run it in temp dirs under `sandbox/cli-tests/` (gitignored).
|
||||||
|
- Some tests **skip** if no C compiler (`gcc`/`clang`/`cc`) is found at runtime.
|
||||||
|
- Compiler can be overridden via `DCR_COMPILER` or `DCR_CC` env vars.
|
||||||
|
- Tests use `$CARGO_BIN_EXE_dcr` when available, else `target/debug/dcr`.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **Single Rust package** (edition 2024), not a workspace. Only one binary: `dcr`.
|
||||||
|
- **Entrypoint**: `src/main.rs` — matches `args[1]` against command names.
|
||||||
|
- **Compiler backends** in `src/core/builder/`: `unix_cc` (gcc/clang), `msvc` (cl/clang-cl), `gas`, `nasm`. Dispatched by matching compiler name string.
|
||||||
|
- **Config**: `dcr.toml` (per-project), `~/.dcr/config.toml` (registry), `~/.dcr/index.json` (index). Defaults: `language=c`, `standard=c11`, `compiler=clang`, `kind=bin`.
|
||||||
|
- **`build.rs`** embeds `TARGET` as `DCR_TARGET` env var (shown in `--version`).
|
||||||
|
- **CLI output** must be in English. Use `utils::log` for messaging.
|
||||||
|
|
||||||
|
## Key commands
|
||||||
|
|
||||||
|
| Command | Notes |
|
||||||
|
|---|---|
|
||||||
|
| `dcr new <name>` | Creates project dir |
|
||||||
|
| `dcr init` | Init from CWD (must be empty) |
|
||||||
|
| `dcr build [--debug\|--release] [--target <triple>]` | Default profile: `--debug` |
|
||||||
|
| `dcr run [--debug\|--release]` | Only for `kind = "bin"` |
|
||||||
|
| `dcr clean [--debug\|--release] [--all]` | `--all` for workspace roots |
|
||||||
|
| `dcr tree` | Dependency tree viewer |
|
||||||
|
| `dcr test [--init]` | Runs/test project tests |
|
||||||
|
| `dcr gen <vscode\|clion\|compile-commands>` | IDE integration |
|
||||||
|
| `dcr --update` | Self-update from GitHub Releases |
|
||||||
|
|
||||||
|
## Cross-compilation
|
||||||
|
|
||||||
|
`--target` accepts short names (`linux`, `macos`, `windows`) or full triples. In CI, Linux cross-builds use `cargo-zigbuild`. When `build.target` is set in `dcr.toml`, DCR auto-injects `--target=<value>` into cflags (clang). Bare-metal targets (containing `none`/`-elf`/`eabi`/`baremetal`) skip DCR's default compiler flags.
|
||||||
|
|
||||||
|
## Dependencies (Cargo)
|
||||||
|
|
||||||
|
reqwest (blocking+json+rustls-tls), serde, toml, git2, openssl (vendored), self-replace, sha2, ctrlc.
|
||||||
68
CHANGELOG.md
68
CHANGELOG.md
@@ -1,45 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.8.2] - 2026-07-22 "CLI Progress, archive Feature, Workspace Path Fixes / CLI Progress, archive Feature, Workspace Path Fixes"
|
|
||||||
|
|
||||||
### RU
|
|
||||||
|
|
||||||
**Добавлено:**
|
|
||||||
|
|
||||||
- **Прогресс компиляции `[N/M]`** — live-строка `compile pkg v0.1.0 [12/41]` на TTY (stderr), чтобы длинные mono-package сборки не выглядели «зависшими».
|
|
||||||
- **Свой стиль статуса build** — фиксированная колонка глаголов: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run` (не cargo-clone).
|
|
||||||
- **Cargo feature `archive`** — `fatfs` и упаковка FAT-образов опциональны (`cargo build --features archive`). Release CI собирает с feature; без feature `[archive]` даёт явную ошибку.
|
|
||||||
- **Интеграционные тесты** — `flat_bin_nasm_build`, `package_build_target_used_without_cli_flag`, усилен workspace `clean --all`.
|
|
||||||
- **Расширенный CI** — unit + integration на Linux/macOS/Windows, отдельный job с `--features archive`, rust-cache, clippy default + all-features.
|
|
||||||
|
|
||||||
**Изменено:**
|
|
||||||
|
|
||||||
- **CLI host triple больше не форсится** — без `--target` используется `build.target` пакета/member (важно для bare-metal / post_steps ISO).
|
|
||||||
- **Relative `-I`/`-L`/`-T` (и `-isystem`/`-idirafter`)** в cflags/ldflags абсолютизируются относительно корня пакета (workspace CWD = root).
|
|
||||||
- **`dcr clean --all`** — не спамит `target not found` по members без локального `target/` (общий root `target/`).
|
|
||||||
- **`error` / `warn`** — красный / жёлтый, вывод на stderr; `Подсказка:` → `Hint:`.
|
|
||||||
- **`dcr --help` / man** — добавлены `add`, `lint`, `setup`; unknown command → exit code 1.
|
|
||||||
- **Сообщения NotFound** — `linker not found: ld.lld …` / `{tool} not found …` вместо сырого `os error 2`.
|
|
||||||
|
|
||||||
### EN
|
|
||||||
|
|
||||||
**Added:**
|
|
||||||
|
|
||||||
- **Compile progress `[N/M]`** — live TTY line `compile pkg v0.1.0 [12/41]` so long mono-package builds do not look hung.
|
|
||||||
- **DCR status verbs** — fixed-width column: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run`.
|
|
||||||
- **Cargo feature `archive`** — optional `fatfs` / FAT packing (`cargo build --features archive`). Release CI builds with the feature; without it `[archive]` errors clearly.
|
|
||||||
- **Integration tests** — `flat_bin_nasm_build`, package `build.target` without CLI flag, stricter workspace `clean --all`.
|
|
||||||
- **Expanded CI** — unit + integration on Linux/macOS/Windows, archive feature job, rust-cache, clippy default + all-features.
|
|
||||||
|
|
||||||
**Changed:**
|
|
||||||
|
|
||||||
- **CLI no longer forces host triple** — without `--target`, package/member `build.target` is used (bare-metal / ISO post_steps).
|
|
||||||
- **Relative `-I`/`-L`/`-T` (and `-isystem`/`-idirafter`)** in flags are absolutized against the package root when workspace CWD is the root.
|
|
||||||
- **`dcr clean --all`** — no spam for members without a local `target/` (shared root `target/`).
|
|
||||||
- **`error` / `warn`** — red / yellow on stderr; Russian `Подсказка:` → `Hint:`.
|
|
||||||
- **`dcr --help` / man** — list `add`, `lint`, `setup`; unknown command exits 1.
|
|
||||||
- **NotFound messages** — `linker not found: ld.lld …` / `{tool} not found …` instead of raw `os error 2`.
|
|
||||||
|
|
||||||
## [0.8.1] - 2026-07-20 "OS-Dev: flat-bin, FAT-образы / OS-Dev: flat-bin & FAT Images"
|
## [0.8.1] - 2026-07-20 "OS-Dev: flat-bin, FAT-образы / OS-Dev: flat-bin & FAT Images"
|
||||||
|
|
||||||
### RU
|
### RU
|
||||||
@@ -52,25 +12,39 @@
|
|||||||
- **GAS / MASM / LLVM IR (`llc`)** — объект → `objcopy -O binary` → `<stem>.bin`
|
- **GAS / MASM / LLVM IR (`llc`)** — объект → `objcopy -O binary` → `<stem>.bin`
|
||||||
- **C/C++ (gcc/clang/MSVC)** — compile → link (nostdlib/static) → `objcopy -O binary` → `<name>.bin`
|
- **C/C++ (gcc/clang/MSVC)** — compile → link (nostdlib/static) → `objcopy -O binary` → `<name>.bin`
|
||||||
- Требуется `llvm-objcopy` / `objcopy` / `gobjcopy` в PATH (кроме NASM/FASM direct)
|
- Требуется `llvm-objcopy` / `objcopy` / `gobjcopy` в PATH (кроме NASM/FASM direct)
|
||||||
- **Секция `[archive]`** — после успешной сборки DCR собирает образ диска (FAT12/16/32): размер, offset, volume label, опциональный bootsector и `layout`. С 0.8.2 — за feature `archive`.
|
- **Секция `[archive]`** — после успешной сборки DCR собирает образ диска (FAT12/16/32 через `fatfs`): размер, offset, volume label, опциональный bootsector (512 байт в начало при `offset = 0`) и `layout` (копирование файлов/glob в FS). Плейсхолдер `{profile}` в путях.
|
||||||
- **Одиночные файлы в `build.roots`** — корень может быть файлом (`.c`, `.asm`, header), а не только каталогом.
|
- **Одиночные файлы в `build.roots`** — корень может быть файлом (`.c`, `.asm`, header), а не только каталогом; то же для сбора заголовков в cache.
|
||||||
|
|
||||||
**Изменено:**
|
**Изменено:**
|
||||||
|
|
||||||
- **`dcr build --force` / `dcr run --force`** — force также перезапускает `build.steps` и `build.post_steps`.
|
- **`dcr build --force` / `dcr run --force`** — force также перезапускает `build.steps` и `build.post_steps`.
|
||||||
- **`dcr run` на workspace-only root** — если задан `[run].cmd`, сначала build + run по конфигу корня; иначе делегирование члену workspace.
|
- **`dcr run` на workspace-only root** — если задан `[run].cmd`, сначала build + run по конфигу корня; иначе делегирование члену workspace (как в 0.8.0).
|
||||||
|
|
||||||
|
**Зависимости:**
|
||||||
|
|
||||||
|
- `fatfs = "0.3"` для упаковки FAT-образов.
|
||||||
|
|
||||||
### EN
|
### EN
|
||||||
|
|
||||||
**Added:**
|
**Added:**
|
||||||
|
|
||||||
- **`build.kind = "flat-bin"`** — raw binary (default `.bin`) across NASM/FASM/GAS/MASM/LLC and C/C++ (objcopy where needed).
|
- **`build.kind = "flat-bin"`** — raw binary artifact (default extension `.bin`):
|
||||||
- **`[archive]` section** — FAT12/16/32 disk images after build (from 0.8.2 gated by feature `archive`).
|
- **NASM** — `-f bin`, direct `<stem>.bin`
|
||||||
- **Single-file `build.roots`**.
|
- **FASM** — direct output (`format binary` in source)
|
||||||
|
- **GAS / MASM / LLVM IR (`llc`)** — object → `objcopy -O binary` → `<stem>.bin`
|
||||||
|
- **C/C++ (gcc/clang/MSVC)** — compile → link (`-nostdlib -static`) → `objcopy -O binary` → `<name>.bin`
|
||||||
|
- Needs `llvm-objcopy` / `objcopy` / `gobjcopy` in PATH (except NASM/FASM direct)
|
||||||
|
- **`[archive]` section** — after a successful build, pack a disk image (FAT12/16/32 via `fatfs`): size, offset, volume label, optional bootsector (512 bytes at start when `offset = 0`), and `layout` (file/glob copy into the FS). `{profile}` substitution in paths.
|
||||||
|
- **Single-file `build.roots`** — a root may be a file (`.c`, `.asm`, header), not only a directory; header collection in the cache follows the same rule.
|
||||||
|
|
||||||
**Changed:**
|
**Changed:**
|
||||||
|
|
||||||
- **`--force`** re-runs steps/post_steps; workspace-only **`dcr run`** prefers root `[run].cmd` when set.
|
- **`dcr build --force` / `dcr run --force`** — force also re-runs `build.steps` and `build.post_steps`.
|
||||||
|
- **`dcr run` on a workspace-only root** — if `[run].cmd` is set, build + run that command first; otherwise fall back to workspace member delegation (0.8.0 behavior).
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
|
||||||
|
- `fatfs = "0.3"` for FAT image packing.
|
||||||
|
|
||||||
## [0.8.0] - 2026-07-18 "Модульный Движок Сборки, Новая Архитектура Ядра и Улучшение Workspaces / Modular Build Engine, New Core Architecture, & Workspace Improvements"
|
## [0.8.0] - 2026-07-18 "Модульный Движок Сборки, Новая Архитектура Ядра и Улучшение Workspaces / Modular Build Engine, New Core Architecture, & Workspace Improvements"
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -154,7 +154,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.8.2"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"fatfs",
|
"fatfs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.8.2"
|
version = "0.8.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
||||||
@@ -59,10 +59,6 @@ assets = [
|
|||||||
{ source = "man/man1/dcr-tree.1", dest = "/usr/share/man/man1/dcr-tree.1", mode = "644" },
|
{ source = "man/man1/dcr-tree.1", dest = "/usr/share/man/man1/dcr-tree.1", mode = "644" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
archive = ["dep:fatfs"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
ureq = { version = "2.10", default-features = false, features = ["json", "native-certs"] }
|
ureq = { version = "2.10", default-features = false, features = ["json", "native-certs"] }
|
||||||
@@ -73,7 +69,7 @@ toml = "1.0.3"
|
|||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
ctrlc = "3"
|
ctrlc = "3"
|
||||||
fatfs = { version = "0.3", optional = true }
|
fatfs = "0.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|||||||
65
TODO.md
Normal file
65
TODO.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# DCR TODO.md
|
||||||
|
|
||||||
|
Priority list of tasks to turn DCR into a true Cargo killer for C/C++ developers.
|
||||||
|
|
||||||
|
**Legend:** `[ ]` — not implemented | `[-]` — partially implemented | `[x]` — implemented
|
||||||
|
|
||||||
|
## 🔥 High Priority
|
||||||
|
|
||||||
|
- [ ] **CLI Output Refactoring & Cleanup**
|
||||||
|
- [ ] Move all terminal strings to a dedicated module (`src/ui/messages.rs`)
|
||||||
|
- [ ] Standardize logging formats (`error:`, `warning:`, `status:`)
|
||||||
|
- [x] Add build execution time statistics to the final output (e.g., `Finished in 4.23s`)
|
||||||
|
- [ ] **Internationalization (i18n) / Localization**
|
||||||
|
- [ ] Integrate a lightweight localization engine (`fluent` or `gettext` pure-Rust port)
|
||||||
|
- [ ] Create initial translation catalogs (`en.ftl`, `ru.ftl`)
|
||||||
|
- [x] **Self-Update Stability Validation**
|
||||||
|
- [x] Self-update implemented via `self-replace` + `ureq` in `flag_update.rs`
|
||||||
|
- [-] **Freestanding / Embedded Automation**
|
||||||
|
- *`-ffreestanding` injected at compile time, `-nostdlib -static` at link time for bare-metal targets or `build.freestanding = true`. Config option `build.freestanding` (bool) added.*
|
||||||
|
- [ ] **Conan and vcpkg integration** Automatic download, installation and linking of packages
|
||||||
|
|
||||||
|
## ✅ Medium Priority
|
||||||
|
|
||||||
|
- [ ] **Static analysis** (`dcr check`)
|
||||||
|
Integration with clang-tidy, cppcheck, include-what-you-use
|
||||||
|
- [x] **Linter + Formatter** (`dcr fmt`, `dcr lint`)
|
||||||
|
*`dcr fmt` (clang-format) and `dcr lint` (clang-tidy) are implemented*
|
||||||
|
- [ ] **Hot reload / Live reload** for applications (raylib, SFML, SDL, etc.)
|
||||||
|
- [-] **Workspaces improvements** Per-member include directories, selective builds, better dependency graph
|
||||||
|
*Basic workspaces with dep ordering + topological sort work; advanced features missing*
|
||||||
|
- [-] **Build-time code generation** Support for protobuf, flatbuffers, Qt moc, custom generators
|
||||||
|
*Generic `build.steps` machinery + Qt toolchain (uic/moc/rcc) work; no dedicated protobuf/flatbuffers generators*
|
||||||
|
- [-] **dcr publish + official package registry** *Registry consumption (fetch, resolve, link) works; `dcr publish` command / local packing missing*
|
||||||
|
|
||||||
|
## 📌 Low Priority / Nice to Have
|
||||||
|
|
||||||
|
- [ ] Support for Meson and Ninja as alternative backends
|
||||||
|
- [ ] Dockerfile generation (`dcr docker init`)
|
||||||
|
- [ ] First-class Windows MSVC support (currently works but rough)
|
||||||
|
- [ ] Documentation generation (`dcr doc`)
|
||||||
|
- [ ] Sanitizers integration (`dcr sanitize`)
|
||||||
|
- [ ] Benchmarks and comparison with CMake/Meson
|
||||||
|
- [ ] C++20 modules support (when it becomes stable)
|
||||||
|
|
||||||
|
## 🧠 Future Ideas
|
||||||
|
|
||||||
|
- Official DCR package registry (dcrhub)
|
||||||
|
- GUI wrapper (dcr-gui)
|
||||||
|
- Plugin system
|
||||||
|
- Toolchain manager (like rustup)
|
||||||
|
- Zig as alternative compiler
|
||||||
|
- deb/rpm/flatpak/AppImage packaging support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Currently in progress:**
|
||||||
|
- UI/UX terminal output unification and benchmarking metrics.
|
||||||
|
|
||||||
|
**Completed in recent releases:**
|
||||||
|
- Extreme binary size optimization (-50% weight reduction via `ureq` + `native-certs`)
|
||||||
|
- Complete removal of `git2` and `openssl` in favor of system Git execution
|
||||||
|
- Workspaces & Cross-compilation (`--target <triple>`)
|
||||||
|
- IDE config generation (VS Code, CLion, compile_commands.json)
|
||||||
|
- EFI support & Linker script support (`build.ldscript`)
|
||||||
|
- Mixed C/C++/ASM compilation with custom `std::thread::scope` work-stealing engine
|
||||||
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
files:
|
||||||
|
- source: /docs/**/*.mdx
|
||||||
|
translation: /docs-crowdin-export/%locale%/%original_path%/%original_file_name%.%file_extension%
|
||||||
@@ -4,25 +4,6 @@ sidebar_label: Changelog
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.8.2 (2026-07-22)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- **Compile progress `[N/M]`** — live TTY status line (`compile pkg v0.1.0 [12/41]`) so long mono-package builds do not look hung.
|
|
||||||
- **DCR status style** — fixed-width verbs: `project`, `compile`, `dep`, `ready`, `pack`, `done`, `run`.
|
|
||||||
- **Cargo feature `archive`** — optional FAT packing via `fatfs` (`cargo build --features archive`). Release CI builds with the feature; without it `[archive]` fails with a clear message.
|
|
||||||
- **Tests** — `flat_bin_nasm_build`, package `build.target` without CLI `--target`, stricter workspace `clean --all`.
|
|
||||||
- **CI expansion** — unit + integration on Linux/macOS/Windows, archive feature job, rust-cache, clippy default + all-features (see `.github/workflows/ci.yml`).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- **No forced host triple from CLI** — without `--target`, package/member `build.target` is honored (bare-metal / ISO `post_steps` paths).
|
|
||||||
- **Relative include/lib flags** (`-I`, `-i`, `-isystem`, `-idirafter`, `-L`, `-T`) absolutized against the package root under workspace builds.
|
|
||||||
- **`dcr clean --all`** — no spam for members without a local `target/` (shared root `target/`).
|
|
||||||
- **`error` / `warn`** — red / yellow on stderr; CLI fully English (`Hint:`).
|
|
||||||
- **Help / man** — list `add`, `lint`, `setup`; unknown command exits with code 1.
|
|
||||||
- **Tool NotFound** — `linker not found: ld.lld …` / `{tool} not found …` instead of raw `os error 2`.
|
|
||||||
|
|
||||||
## 0.8.1 (2026-07-20)
|
## 0.8.1 (2026-07-20)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -33,7 +14,7 @@ sidebar_label: Changelog
|
|||||||
- **GAS / MASM / LLC** — object → `objcopy -O binary` → `<stem>.bin`
|
- **GAS / MASM / LLC** — object → `objcopy -O binary` → `<stem>.bin`
|
||||||
- **C/C++** — objects → freestanding link → `objcopy -O binary` → `<name>.bin`
|
- **C/C++** — objects → freestanding link → `objcopy -O binary` → `<name>.bin`
|
||||||
- Requires `llvm-objcopy` / `objcopy` / `gobjcopy` except for NASM/FASM direct emit
|
- Requires `llvm-objcopy` / `objcopy` / `gobjcopy` except for NASM/FASM direct emit
|
||||||
- **`[archive]` section** — FAT12/16/32 disk images after build (`output`, `format`, `size`, `offset`, `label`, `bootsector`, `layout`). From 0.8.2 requires feature `archive`.
|
- **`[archive]` section** — after a successful build, pack a FAT disk image (`fatfs`): `output`, `format` (`fat12`/`fat16`/`fat32`), optional `size` (default 1.44 MiB), `offset`, `label`, `bootsector`, and `layout` (`from` → `to`, supports globs). `{profile}` works in paths.
|
||||||
- **Single-file `build.roots`** — a root may be a single source or header file, not only a directory.
|
- **Single-file `build.roots`** — a root may be a single source or header file, not only a directory.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -41,6 +22,10 @@ sidebar_label: Changelog
|
|||||||
- **`--force`** also re-runs `build.steps` and `build.post_steps`.
|
- **`--force`** also re-runs `build.steps` and `build.post_steps`.
|
||||||
- **`dcr run` on workspace-only root** — if `[run].cmd` is set, run that after build; otherwise delegate to a workspace member.
|
- **`dcr run` on workspace-only root** — if `[run].cmd` is set, run that after build; otherwise delegate to a workspace member.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- `fatfs = "0.3"` for FAT image packing.
|
||||||
|
|
||||||
## 0.8.0 (2026-07-18)
|
## 0.8.0 (2026-07-18)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -42,10 +42,9 @@ cargo test --all-targets
|
|||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
CI runs (see `.github/workflows/ci.yml`):
|
CI runs:
|
||||||
- `cargo fmt --check`
|
- `cargo fmt --check`
|
||||||
- `cargo clippy` (default + `--all-features`)
|
- `cargo clippy --all-targets -- -D warnings`
|
||||||
- `cargo check --all-targets --all-features`
|
- `cargo check --all-targets --all-features`
|
||||||
- Unit + integration tests on Linux, macOS, Windows
|
- Build on Linux, macOS, Windows
|
||||||
- Extra Linux job with `--features archive` (FAT images)
|
- Integration tests (compilation check)
|
||||||
- Tools on runners: clang, nasm, clang-format/tidy (Linux)
|
|
||||||
|
|||||||
@@ -183,8 +183,6 @@ See [workspaces](/docs/reference/workspaces).
|
|||||||
|
|
||||||
Optional post-build step: format a FAT volume and copy built artifacts into a disk image. Runs after a successful package build (and after workspace member builds that define `[archive]`).
|
Optional post-build step: format a FAT volume and copy built artifacts into a disk image. Runs after a successful package build (and after workspace member builds that define `[archive]`).
|
||||||
|
|
||||||
Requires DCR built with the `archive` Cargo feature (`cargo build --features archive`). Release binaries include this feature.
|
|
||||||
|
|
||||||
| Field | Required | Description |
|
| Field | Required | Description |
|
||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| `output` | yes | Image path relative to project root (`{profile}` allowed) |
|
| `output` | yes | Image path relative to project root (`{profile}` allowed) |
|
||||||
|
|||||||
@@ -38,14 +38,11 @@ Display the dependency tree of the current project.
|
|||||||
.B fmt
|
.B fmt
|
||||||
Format all C/C++ source files using clang-format.
|
Format all C/C++ source files using clang-format.
|
||||||
.TP
|
.TP
|
||||||
.B lint [\fI\-\-fix\fR]
|
|
||||||
Run clang-tidy static analysis on C/C++ sources.
|
|
||||||
.TP
|
|
||||||
.B gen \fI<subcommand>\fR
|
.B gen \fI<subcommand>\fR
|
||||||
Generate IDE integration files (vscode, clion, compile-commands, project-info).
|
Generate IDE integration files (vscode, clion, compile-commands, project-info).
|
||||||
.TP
|
.TP
|
||||||
.B setup
|
.B setup
|
||||||
Show configured package registries.
|
Set up DCR registries for dependency resolution.
|
||||||
.SH GLOBAL FLAGS
|
.SH GLOBAL FLAGS
|
||||||
.TP
|
.TP
|
||||||
.B \-\-help
|
.B \-\-help
|
||||||
|
|||||||
@@ -17,29 +17,18 @@
|
|||||||
|
|
||||||
use crate::cli::clean::clean;
|
use crate::cli::clean::clean;
|
||||||
use crate::cli::flags::parse_build_run_flags;
|
use crate::cli::flags::parse_build_run_flags;
|
||||||
use crate::core::build::common;
|
|
||||||
use crate::core::build::{BuildEvent, BuildReporter, BuildRequest, run_build};
|
use crate::core::build::{BuildEvent, BuildReporter, BuildRequest, run_build};
|
||||||
use crate::core::build_config::Config;
|
use crate::core::build_config::Config;
|
||||||
|
use crate::utils::build::default_target_triple;
|
||||||
use crate::utils::fs::find_project_root;
|
use crate::utils::fs::find_project_root;
|
||||||
use crate::utils::log::error;
|
use crate::utils::log::error;
|
||||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, BOLD_YELLOW, colored, printc};
|
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||||
use std::io::IsTerminal;
|
|
||||||
use std::sync::{Arc, atomic::AtomicBool};
|
use std::sync::{Arc, atomic::AtomicBool};
|
||||||
|
|
||||||
pub use crate::core::build::get_build_string_with_profile;
|
pub use crate::core::build::get_build_string_with_profile;
|
||||||
|
|
||||||
fn status(verb: &str, style: &str) -> String {
|
|
||||||
colored(&format!("{verb:<9}"), style)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CliReporter;
|
struct CliReporter;
|
||||||
|
|
||||||
impl CliReporter {
|
|
||||||
fn line(&self, verb: &str, style: &str, rest: &str) {
|
|
||||||
eprintln!(" {} {}", status(verb, style), rest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuildReporter for CliReporter {
|
impl BuildReporter for CliReporter {
|
||||||
fn on_event(&mut self, event: BuildEvent<'_>) {
|
fn on_event(&mut self, event: BuildEvent<'_>) {
|
||||||
match event {
|
match event {
|
||||||
@@ -48,57 +37,73 @@ impl BuildReporter for CliReporter {
|
|||||||
total,
|
total,
|
||||||
target,
|
target,
|
||||||
} => {
|
} => {
|
||||||
common::finish_progress_line();
|
println!(" Building target {} of {}: {}", index, total, target);
|
||||||
self.line("target", BOLD_CYAN, &format!("{index}/{total} {target}"));
|
|
||||||
}
|
}
|
||||||
BuildEvent::ProjectStart {
|
BuildEvent::ProjectStart {
|
||||||
name,
|
name,
|
||||||
profile,
|
profile,
|
||||||
target,
|
target,
|
||||||
} => {
|
} => {
|
||||||
common::finish_progress_line();
|
println!(
|
||||||
self.line(
|
" Building project `{}`\n Profile: {}\n Target: {}",
|
||||||
"project",
|
colored(name, BOLD_GREEN),
|
||||||
BOLD_CYAN,
|
colored(profile, BOLD_GREEN),
|
||||||
&format!("{name} ({profile}, {target})"),
|
colored(target, BOLD_GREEN)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
BuildEvent::DepBuilding { name, version } => {
|
BuildEvent::DepBuilding { name, version } => {
|
||||||
common::finish_progress_line();
|
print!(
|
||||||
self.line("dep", BOLD_YELLOW, &format!("{name} v{version}"));
|
"\r{:100}\r {} {} v{}",
|
||||||
|
"",
|
||||||
|
colored("Building", BOLD_GREEN),
|
||||||
|
name,
|
||||||
|
version
|
||||||
|
);
|
||||||
|
std::io::Write::flush(&mut std::io::stdout()).unwrap();
|
||||||
}
|
}
|
||||||
BuildEvent::DepReady {
|
BuildEvent::DepReady {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
rebuilt,
|
rebuilt,
|
||||||
} => {
|
} => {
|
||||||
common::finish_progress_line();
|
|
||||||
if rebuilt {
|
if rebuilt {
|
||||||
self.line("ready", BOLD_GREEN, &format!("{name} v{version}"));
|
print!(
|
||||||
|
"\r{:100}\r {} {} v{}",
|
||||||
|
"",
|
||||||
|
colored("Ready", BOLD_GREEN),
|
||||||
|
name,
|
||||||
|
version
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
" {} {} v{}",
|
||||||
|
colored("Ready", BOLD_GREEN),
|
||||||
|
name,
|
||||||
|
version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuildEvent::Compiling { name, version } => {
|
BuildEvent::Compiling { name, version } => {
|
||||||
common::finish_progress_line();
|
println!(
|
||||||
let label = format!(" {} {} v{}", status("compile", BOLD_GREEN), name, version);
|
" {} {} v{}",
|
||||||
common::set_progress_label(Some(label.clone()));
|
colored("Compiling", BOLD_GREEN),
|
||||||
if !std::io::stderr().is_terminal() {
|
name,
|
||||||
eprintln!("{label}");
|
version
|
||||||
}
|
);
|
||||||
}
|
|
||||||
BuildEvent::Packing { path } => {
|
|
||||||
common::finish_progress_line();
|
|
||||||
self.line("pack", BOLD_CYAN, path);
|
|
||||||
}
|
}
|
||||||
BuildEvent::Finished { secs } => {
|
BuildEvent::Finished { secs } => {
|
||||||
common::finish_progress_line();
|
println!(
|
||||||
self.line("done", BOLD_GREEN, &format!("in {secs}s"));
|
" {} Build completed successfully in {} seconds",
|
||||||
|
colored("✔", BOLD_GREEN),
|
||||||
|
colored(&secs.to_string(), BOLD_GREEN)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
BuildEvent::CompilerOutput { stream, text } => {
|
BuildEvent::CompilerOutput { stream, text } => {
|
||||||
common::interrupt_progress_for_output();
|
|
||||||
if stream == "stderr" {
|
if stream == "stderr" {
|
||||||
eprint!("{text}");
|
eprintln!("{}", text);
|
||||||
} else {
|
} else {
|
||||||
print!("{text}");
|
println!("{}", text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,12 +166,15 @@ pub fn build(args: &[String]) -> i32 {
|
|||||||
if let Ok(config) = Config::open(config_path.to_str().unwrap())
|
if let Ok(config) = Config::open(config_path.to_str().unwrap())
|
||||||
&& !config.is_workspace_only()
|
&& !config.is_workspace_only()
|
||||||
{
|
{
|
||||||
let bt = get_build_string_with_profile(&config, "target", &flags.profile);
|
let bt = get_build_string_with_profile(&config, "target", "debug");
|
||||||
if !bt.is_empty() {
|
if !bt.is_empty() {
|
||||||
flags.target = Some(bt);
|
flags.target = Some(bt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if flags.target.is_none() {
|
||||||
|
flags.target = Some(default_target_triple());
|
||||||
|
}
|
||||||
if flags.clean {
|
if flags.clean {
|
||||||
let mut clean_args = Vec::new();
|
let mut clean_args = Vec::new();
|
||||||
clean_args.push(format!("--{}", flags.profile));
|
clean_args.push(format!("--{}", flags.profile));
|
||||||
@@ -185,7 +193,6 @@ pub fn build(args: &[String]) -> i32 {
|
|||||||
match run_build(&root, &req, &mut reporter) {
|
match run_build(&root, &req, &mut reporter) {
|
||||||
Ok(_) => 0,
|
Ok(_) => 0,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
common::finish_progress_line();
|
|
||||||
error(&err.message);
|
error(&err.message);
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,12 +146,9 @@ fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
|
|||||||
)?
|
)?
|
||||||
{
|
{
|
||||||
for member in &workspace.members {
|
for member in &workspace.members {
|
||||||
let member_target = member.path.join("target");
|
|
||||||
if member_target.is_dir() {
|
|
||||||
clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?;
|
clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
clean_project_at(root, flags.profile.as_deref(), target.as_deref())
|
clean_project_at(root, flags.profile.as_deref(), target.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,9 @@ pub fn help() -> i32 {
|
|||||||
println!(" init Initialize the current directory as a project");
|
println!(" init Initialize the current directory as a project");
|
||||||
println!(" build [--profile] Build the project (default: --debug)");
|
println!(" build [--profile] Build the project (default: --debug)");
|
||||||
println!(" run [--profile] Build and run the project (default: --debug)");
|
println!(" run [--profile] Build and run the project (default: --debug)");
|
||||||
println!(" add <name> ... Add a dependency to dcr.toml");
|
|
||||||
println!(" tree Display the dependency tree");
|
println!(" tree Display the dependency tree");
|
||||||
println!(" test Run project tests (alias: tests)");
|
println!(" test Run project tests (alias: tests)");
|
||||||
println!(" fmt Format all C/C++ source files using clang-format");
|
println!(" fmt Format all C/C++ source files using clang-format");
|
||||||
println!(" lint Run clang-tidy on C/C++ sources");
|
|
||||||
println!(" setup Show configured package registries");
|
|
||||||
println!(" clean Remove the target directory");
|
println!(" clean Remove the target directory");
|
||||||
println!(" gen <subcommand> Generate IDE integration files");
|
println!(" gen <subcommand> Generate IDE integration files");
|
||||||
printc("FLAGS:", BOLD_GREEN);
|
printc("FLAGS:", BOLD_GREEN);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ pub fn new(args: &[String]) -> i32 {
|
|||||||
"Directory `{}` already exists\n",
|
"Directory `{}` already exists\n",
|
||||||
colored(project_name, BOLD_CYAN)
|
colored(project_name, BOLD_CYAN)
|
||||||
));
|
));
|
||||||
printc("Hint:", BOLD_CYAN);
|
printc("Подсказка:", BOLD_CYAN);
|
||||||
println!(
|
println!(
|
||||||
" Use `{}` to initialize an existing project\n or specify a different project name",
|
" Use `{}` to initialize an existing project\n or specify a different project name",
|
||||||
colored("dcr init", BOLD_CYAN)
|
colored("dcr init", BOLD_CYAN)
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ use crate::cli::build::build;
|
|||||||
use crate::cli::flags::parse_build_run_flags;
|
use crate::cli::flags::parse_build_run_flags;
|
||||||
use crate::core::build_config::Config;
|
use crate::core::build_config::Config;
|
||||||
use crate::core::runner::run_binary;
|
use crate::core::runner::run_binary;
|
||||||
use crate::utils::build::{normalize_target_os, parse_version_info, substitute_vars};
|
use crate::utils::build::{
|
||||||
|
default_target_triple, normalize_target_os, parse_version_info, substitute_vars,
|
||||||
|
};
|
||||||
use crate::utils::fs::find_project_root;
|
use crate::utils::fs::find_project_root;
|
||||||
use crate::utils::fs::with_dir;
|
use crate::utils::fs::with_dir;
|
||||||
use crate::utils::log::error;
|
use crate::utils::log::error;
|
||||||
@@ -112,11 +114,8 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
if let Some(cmd) = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), "") {
|
if let Some(cmd) = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), "") {
|
||||||
let build_status = build(&args_for_build(&flags));
|
let build_status = build(&args_for_build(&flags));
|
||||||
if build_status == 0 {
|
if build_status == 0 {
|
||||||
println!(
|
println!("\n {} {}", colored("Running", BOLD_GREEN), cmd);
|
||||||
" {} {}",
|
println!("--------------------------------");
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
|
||||||
cmd
|
|
||||||
);
|
|
||||||
return run_shell(&cmd);
|
return run_shell(&cmd);
|
||||||
}
|
}
|
||||||
return build_status;
|
return build_status;
|
||||||
@@ -187,13 +186,10 @@ fn run_project(
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
|
// If no target specified, use default host target for target-specific config
|
||||||
let mut target = flags.target.clone();
|
let mut target = flags.target.clone();
|
||||||
if target.is_none() {
|
if target.is_none() {
|
||||||
let bt =
|
target = Some(default_target_triple());
|
||||||
crate::cli::build::get_build_string_with_profile(&config, "target", &flags.profile);
|
|
||||||
if !bt.is_empty() {
|
|
||||||
target = Some(bt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let build_kind = config
|
let build_kind = config
|
||||||
@@ -250,18 +246,12 @@ fn run_project(
|
|||||||
);
|
);
|
||||||
if build_status == 0 {
|
if build_status == 0 {
|
||||||
if let Some(cmd) = run_cmd {
|
if let Some(cmd) = run_cmd {
|
||||||
println!(
|
println!("\n {} {}", colored("Running", BOLD_GREEN), cmd);
|
||||||
" {} {}",
|
println!("--------------------------------");
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
|
||||||
cmd
|
|
||||||
);
|
|
||||||
return Ok(run_shell(&cmd));
|
return Ok(run_shell(&cmd));
|
||||||
}
|
}
|
||||||
println!(
|
println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path);
|
||||||
" {} {}",
|
println!("--------------------------------");
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
|
||||||
bin_path
|
|
||||||
);
|
|
||||||
return Ok(run_binary(
|
return Ok(run_binary(
|
||||||
project_name,
|
project_name,
|
||||||
&flags.profile,
|
&flags.profile,
|
||||||
|
|||||||
@@ -130,14 +130,7 @@ pub(crate) fn link_flat_binary(
|
|||||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||||
eprintln!("[dcr] {:?}", cmd);
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
}
|
}
|
||||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||||
let output = cmd.output().map_err(|err| {
|
|
||||||
if err.kind() == std::io::ErrorKind::NotFound {
|
|
||||||
format!("linker not found: {program} (check [toolchain].ld / PATH)")
|
|
||||||
} else {
|
|
||||||
format!("flat-bin link failed: {err}")
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
@@ -247,15 +240,7 @@ fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64,
|
|||||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||||
eprintln!("[dcr] {:?}", cmd);
|
eprintln!("[dcr] {:?}", cmd);
|
||||||
}
|
}
|
||||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||||
let output = cmd.output().map_err(|err| {
|
|
||||||
let msg = err.to_string();
|
|
||||||
if err.kind() == std::io::ErrorKind::NotFound {
|
|
||||||
format!("linker not found: {program} (check [toolchain].ld / PATH)")
|
|
||||||
} else {
|
|
||||||
format!("Build failed: {msg}")
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Ok(common::elapsed_secs(start_time))
|
Ok(common::elapsed_secs(start_time))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -16,72 +16,17 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, IsTerminal, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
static OUTPUT_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
static OUTPUT_MUTEX: OnceLock<Mutex<()>> = OnceLock::new();
|
||||||
static PROGRESS_LABEL: OnceLock<Mutex<Option<String>>> = OnceLock::new();
|
|
||||||
static PROGRESS_DIRTY: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
pub fn get_output_lock() -> &'static Mutex<()> {
|
pub fn get_output_lock() -> &'static Mutex<()> {
|
||||||
OUTPUT_MUTEX.get_or_init(|| Mutex::new(()))
|
OUTPUT_MUTEX.get_or_init(|| Mutex::new(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress_label_lock() -> &'static Mutex<Option<String>> {
|
|
||||||
PROGRESS_LABEL.get_or_init(|| Mutex::new(None))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_progress_label(label: Option<String>) {
|
|
||||||
*progress_label_lock().lock().unwrap() = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish_progress_line() {
|
|
||||||
let was_dirty = PROGRESS_DIRTY.swap(false, Ordering::SeqCst);
|
|
||||||
*progress_label_lock().lock().unwrap() = None;
|
|
||||||
if !was_dirty {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if io::stderr().is_terminal() {
|
|
||||||
let _guard = get_output_lock().lock().unwrap();
|
|
||||||
let _ = writeln!(io::stderr());
|
|
||||||
let _ = io::stderr().flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interrupt_progress_for_output() {
|
|
||||||
if !PROGRESS_DIRTY.swap(false, Ordering::SeqCst) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if io::stderr().is_terminal() {
|
|
||||||
let _guard = get_output_lock().lock().unwrap();
|
|
||||||
let _ = writeln!(io::stderr());
|
|
||||||
let _ = io::stderr().flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_compile_progress(done: usize, total: usize) {
|
|
||||||
let label = progress_label_lock().lock().unwrap().clone();
|
|
||||||
let Some(label) = label else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if !io::stderr().is_terminal() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let _guard = get_output_lock().lock().unwrap();
|
|
||||||
let msg = format!("{label} [{done}/{total}]");
|
|
||||||
if PROGRESS_DIRTY.load(Ordering::SeqCst) {
|
|
||||||
eprint!("\r{msg:<80}");
|
|
||||||
} else {
|
|
||||||
eprint!("{msg}");
|
|
||||||
}
|
|
||||||
let _ = io::stderr().flush();
|
|
||||||
PROGRESS_DIRTY.store(true, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collect_sources(
|
pub fn collect_sources(
|
||||||
roots: &[PathBuf],
|
roots: &[PathBuf],
|
||||||
extensions: &[&str],
|
extensions: &[&str],
|
||||||
@@ -269,13 +214,8 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let counter = std::sync::atomic::AtomicUsize::new(0);
|
let counter = std::sync::atomic::AtomicUsize::new(0);
|
||||||
let completed = std::sync::atomic::AtomicUsize::new(0);
|
|
||||||
let err_msg = std::sync::Mutex::new(None);
|
let err_msg = std::sync::Mutex::new(None);
|
||||||
|
|
||||||
if total_tasks > 0 {
|
|
||||||
report_compile_progress(0, total_tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::scope(|s| {
|
std::thread::scope(|s| {
|
||||||
for _ in 0..num_threads {
|
for _ in 0..num_threads {
|
||||||
s.spawn(|| {
|
s.spawn(|| {
|
||||||
@@ -296,9 +236,6 @@ where
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let done = completed.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
|
|
||||||
report_compile_progress(done, total_tasks);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -312,21 +249,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_command_sync_output(cmd: &mut Command) -> Result<(), String> {
|
pub fn run_command_sync_output(cmd: &mut Command) -> Result<(), String> {
|
||||||
let program = cmd.get_program().to_string_lossy().into_owned();
|
let output = cmd.output().map_err(|err| format!("Build failed: {err}"))?;
|
||||||
let output = cmd.output().map_err(|err| {
|
let _guard = OUTPUT_MUTEX.get_or_init(|| Mutex::new(())).lock().unwrap();
|
||||||
if err.kind() == std::io::ErrorKind::NotFound {
|
|
||||||
format!("{program} not found (check PATH / [toolchain])")
|
|
||||||
} else {
|
|
||||||
format!("Build failed: {err}")
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let has_out = !output.stdout.is_empty() || !output.stderr.is_empty();
|
|
||||||
if has_out {
|
|
||||||
interrupt_progress_for_output();
|
|
||||||
}
|
|
||||||
let _guard = get_output_lock().lock().unwrap();
|
|
||||||
if !output.stdout.is_empty() {
|
if !output.stdout.is_empty() {
|
||||||
eprint!("{}", String::from_utf8_lossy(&output.stdout));
|
print!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
}
|
}
|
||||||
if !output.stderr.is_empty() {
|
if !output.stderr.is_empty() {
|
||||||
eprint!("{}", String::from_utf8_lossy(&output.stderr));
|
eprint!("{}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ fn run_compile(
|
|||||||
_rep: &mut dyn BuildReporter,
|
_rep: &mut dyn BuildReporter,
|
||||||
) -> Result<f64, String> {
|
) -> Result<f64, String> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let result = build_project(ctx);
|
match build_project(ctx) {
|
||||||
common::finish_progress_line();
|
|
||||||
match result {
|
|
||||||
Ok(times) => {
|
Ok(times) => {
|
||||||
let times = if times == 0.0 {
|
let times = if times == 0.0 {
|
||||||
((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0
|
((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0
|
||||||
@@ -168,18 +166,8 @@ fn build_all(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if let Some(archive) = &config.typed().archive {
|
if let Some(archive) = &config.typed().archive {
|
||||||
#[cfg(feature = "archive")]
|
|
||||||
{
|
|
||||||
super::archive::pack_archive(root, archive, profile)?;
|
super::archive::pack_archive(root, archive, profile)?;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "archive"))]
|
|
||||||
{
|
|
||||||
let _ = (archive, profile);
|
|
||||||
return Err(
|
|
||||||
"disk image packing requires building dcr with --features archive".to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(0.0);
|
return Ok(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,12 +777,10 @@ fn build_project_at(
|
|||||||
let resolved_cflags: Vec<String> = resolved_cflags
|
let resolved_cflags: Vec<String> = resolved_cflags
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
||||||
.map(|f| absolutize_flag_path(&f, project_root, &["-isystem", "-idirafter", "-I", "-i"]))
|
|
||||||
.collect();
|
.collect();
|
||||||
let modified_ldflags: Vec<String> = resolved_ldflags
|
let modified_ldflags: Vec<String> = resolved_ldflags
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
.map(|f| substitute_vars(f, &version_info, profile, &project_name))
|
||||||
.map(|f| absolutize_flag_path(&f, project_root, &["-L", "-T"]))
|
|
||||||
.collect();
|
.collect();
|
||||||
let merged_include_dirs: Vec<String> = merged_include_dirs
|
let merged_include_dirs: Vec<String> = merged_include_dirs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -890,11 +876,11 @@ fn build_project_at(
|
|||||||
if skip && !debug_enabled {
|
if skip && !debug_enabled {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if !skip {
|
|
||||||
rep.on_event(BuildEvent::Compiling {
|
rep.on_event(BuildEvent::Compiling {
|
||||||
name: &project_name,
|
name: &project_name,
|
||||||
version: &project_version,
|
version: &project_version,
|
||||||
});
|
});
|
||||||
|
if !skip {
|
||||||
run_compile(&ctx, cancel, rep)?;
|
run_compile(&ctx, cancel, rep)?;
|
||||||
if cancel.load(Ordering::SeqCst) {
|
if cancel.load(Ordering::SeqCst) {
|
||||||
return Err("Build interrupted".to_string());
|
return Err("Build interrupted".to_string());
|
||||||
@@ -920,42 +906,10 @@ fn build_project_at(
|
|||||||
}
|
}
|
||||||
verify_expectations(&build_expects, &step_vars)?;
|
verify_expectations(&build_expects, &step_vars)?;
|
||||||
if let Some(archive) = &config.typed().archive {
|
if let Some(archive) = &config.typed().archive {
|
||||||
let out = archive.output.replace("{profile}", profile);
|
|
||||||
rep.on_event(BuildEvent::Packing { path: &out });
|
|
||||||
#[cfg(feature = "archive")]
|
|
||||||
{
|
|
||||||
super::archive::pack_archive(project_root, archive, profile)?;
|
super::archive::pack_archive(project_root, archive, profile)?;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "archive"))]
|
|
||||||
{
|
|
||||||
let _ = (project_root, archive, profile, out);
|
|
||||||
return Err(
|
|
||||||
"disk image packing requires building dcr with --features archive".to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> String {
|
|
||||||
for prefix in prefixes {
|
|
||||||
if let Some(rest) = flag.strip_prefix(prefix) {
|
|
||||||
if rest.is_empty() {
|
|
||||||
return flag.to_string();
|
|
||||||
}
|
|
||||||
if rest.starts_with('/') || rest.starts_with('@') {
|
|
||||||
return flag.to_string();
|
|
||||||
}
|
|
||||||
if rest.len() >= 2 && rest.as_bytes()[1] == b':' {
|
|
||||||
return flag.to_string();
|
|
||||||
}
|
|
||||||
let abs = project_root.join(rest);
|
|
||||||
return format!("{prefix}{}", abs.to_string_lossy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flag.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
fn find_target_root(dir: &str, fallback: &Path) -> PathBuf {
|
||||||
let p = Path::new(dir);
|
let p = Path::new(dir);
|
||||||
for ancestor in p.ancestors() {
|
for ancestor in p.ancestors() {
|
||||||
@@ -1140,35 +1094,3 @@ fn is_on_path(cmd: &str) -> bool {
|
|||||||
std::env::var_os("PATH")
|
std::env::var_os("PATH")
|
||||||
.is_some_and(|paths| std::env::split_paths(&paths).any(|dir| dir.join(cmd).is_file()))
|
.is_some_and(|paths| std::env::split_paths(&paths).any(|dir| dir.join(cmd).is_file()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::absolutize_flag_path;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn absolutize_prefers_long_include_prefixes() {
|
|
||||||
let root = Path::new("/pkg");
|
|
||||||
let prefs = ["-isystem", "-idirafter", "-I", "-i"];
|
|
||||||
assert_eq!(
|
|
||||||
absolutize_flag_path("-isystemvendor/inc", root, &prefs),
|
|
||||||
"-isystem/pkg/vendor/inc"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
absolutize_flag_path("-idiraftervendor/old", root, &prefs),
|
|
||||||
"-idirafter/pkg/vendor/old"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
absolutize_flag_path("-I../inc", root, &prefs),
|
|
||||||
"-I/pkg/../inc"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
absolutize_flag_path("-i../inc", root, &prefs),
|
|
||||||
"-i/pkg/../inc"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
absolutize_flag_path("-I/usr/include", root, &prefs),
|
|
||||||
"-I/usr/include"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#[cfg(feature = "archive")]
|
|
||||||
pub mod archive;
|
pub mod archive;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|||||||
@@ -69,9 +69,6 @@ pub enum BuildEvent<'a> {
|
|||||||
name: &'a str,
|
name: &'a str,
|
||||||
version: &'a str,
|
version: &'a str,
|
||||||
},
|
},
|
||||||
Packing {
|
|
||||||
path: &'a str,
|
|
||||||
},
|
|
||||||
CompilerOutput {
|
CompilerOutput {
|
||||||
stream: &'a str,
|
stream: &'a str,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ pub struct ArchiveConfig {
|
|||||||
pub layout: Vec<ArchiveLayout>,
|
pub layout: Vec<ArchiveLayout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct ArchiveLayout {
|
pub struct ArchiveLayout {
|
||||||
pub from: String,
|
pub from: String,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn main() {
|
|||||||
"--update" => cli::flag_update::flag_update(rest),
|
"--update" => cli::flag_update::flag_update(rest),
|
||||||
_ => {
|
_ => {
|
||||||
error("Unknown command or argument");
|
error("Unknown command or argument");
|
||||||
1
|
0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,27 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::utils::text::{BOLD_RED, BOLD_YELLOW, colored};
|
use crate::utils::text::{BOLD_CYAN, colored};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn error(msg: &str) {
|
pub fn error(msg: &str) {
|
||||||
eprintln!("{}: {msg}", colored("error", BOLD_RED));
|
println!("{}: {msg}", colored("error", BOLD_CYAN),);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn warn(msg: &str) {
|
pub fn warn(msg: &str) {
|
||||||
eprintln!("{}: {msg}", colored("warn", BOLD_YELLOW));
|
println!("{}: {msg}", colored("warn", BOLD_CYAN),);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// pub fn info(msg: &str) {
|
||||||
|
// println!("{}: {msg}", colored("info", BOLD));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// pub fn ok(msg: &str) {
|
||||||
|
// println!(
|
||||||
|
// "{}: {msg}",
|
||||||
|
// colored("ok", &(BRIGHT_GREEN.to_owned() + BOLD))
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|||||||
@@ -77,15 +77,8 @@ fn build_run_clean_flags_normal_project() {
|
|||||||
assert!(out.status.success(), "dcr build --release should succeed");
|
assert!(out.status.success(), "dcr build --release should succeed");
|
||||||
|
|
||||||
let out = run_dcr_env(&["run"], &dir, &envs);
|
let out = run_dcr_env(&["run"], &dir, &envs);
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
assert!(stdout.contains("Running"), "dcr run should start");
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
combined.contains("run") || combined.contains("Running"),
|
|
||||||
"dcr run should start"
|
|
||||||
);
|
|
||||||
|
|
||||||
let out = run_dcr_env(&["clean", "--release"], &dir, &envs);
|
let out = run_dcr_env(&["clean", "--release"], &dir, &envs);
|
||||||
assert!(out.status.success(), "dcr clean --release should succeed");
|
assert!(out.status.success(), "dcr clean --release should succeed");
|
||||||
@@ -177,92 +170,3 @@ fn build_with_out_dir() {
|
|||||||
"artifact should NOT be at default path when out_dir is set"
|
"artifact should NOT be at default path when out_dir is set"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn flat_bin_nasm_build() {
|
|
||||||
let nasm_ok = std::process::Command::new("nasm")
|
|
||||||
.arg("-v")
|
|
||||||
.output()
|
|
||||||
.map(|o| o.status.success())
|
|
||||||
.unwrap_or(false);
|
|
||||||
if !nasm_ok {
|
|
||||||
eprintln!("nasm not found; skipping flat-bin test");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dir = unique_sandbox_dir("flat_bin");
|
|
||||||
let out = run_dcr(&["init"], &dir);
|
|
||||||
assert!(out.status.success(), "dcr init should succeed");
|
|
||||||
|
|
||||||
std::fs::write(
|
|
||||||
dir.join("dcr.toml"),
|
|
||||||
r#"[package]
|
|
||||||
name = "flatdemo"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
language = "asm"
|
|
||||||
compiler = "nasm"
|
|
||||||
kind = "flat-bin"
|
|
||||||
extension = "bin"
|
|
||||||
roots = ["src"]
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.expect("write dcr.toml");
|
|
||||||
|
|
||||||
std::fs::create_dir_all(dir.join("src")).expect("src");
|
|
||||||
std::fs::write(
|
|
||||||
dir.join("src/boot.asm"),
|
|
||||||
"bits 16\norg 0x7c00\ncli\nhlt\ntimes 510-($-$$) db 0\ndw 0xaa55\n",
|
|
||||||
)
|
|
||||||
.expect("write boot.asm");
|
|
||||||
|
|
||||||
let out = run_dcr(&["build"], &dir);
|
|
||||||
if !out.status.success() {
|
|
||||||
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
|
||||||
eprintln!("stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
||||||
}
|
|
||||||
assert!(out.status.success(), "flat-bin nasm build should succeed");
|
|
||||||
|
|
||||||
let candidates = [
|
|
||||||
dir.join("target/debug/boot.bin"),
|
|
||||||
dir.join("target").join(format!(
|
|
||||||
"{}-unknown-linux-gnu/debug/boot.bin",
|
|
||||||
std::env::consts::ARCH
|
|
||||||
)),
|
|
||||||
];
|
|
||||||
assert!(
|
|
||||||
candidates.iter().any(|p| p.is_file()),
|
|
||||||
"expected boot.bin under target/…/debug, candidates: {candidates:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn package_build_target_used_without_cli_flag() {
|
|
||||||
let Some(compiler) = available_compiler() else {
|
|
||||||
eprintln!("no compiler found; skipping package target test");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let dir = unique_sandbox_dir("pkg_target");
|
|
||||||
let out = run_dcr(&["init"], &dir);
|
|
||||||
assert!(out.status.success());
|
|
||||||
|
|
||||||
let toml_path = dir.join("dcr.toml");
|
|
||||||
let toml = std::fs::read_to_string(&toml_path).unwrap();
|
|
||||||
let project_name = parse_project_name(&toml);
|
|
||||||
let updated = toml.replace("[build]", "[build]\ntarget = \"x86_64-unknown-linux-gnu\"");
|
|
||||||
std::fs::write(&toml_path, updated).unwrap();
|
|
||||||
|
|
||||||
let envs = [("DCR_COMPILER", compiler)];
|
|
||||||
let out = run_dcr_env(&["build"], &dir, &envs);
|
|
||||||
assert!(out.status.success(), "build should use package target");
|
|
||||||
|
|
||||||
let artifact = dir
|
|
||||||
.join("target/x86_64-unknown-linux-gnu/debug")
|
|
||||||
.join(&project_name);
|
|
||||||
assert!(
|
|
||||||
artifact.is_file(),
|
|
||||||
"artifact should be under package build.target path"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -92,17 +92,9 @@ fn no_args_shows_help() {
|
|||||||
fn unknown_command_fails() {
|
fn unknown_command_fails() {
|
||||||
let dir = unique_sandbox_dir("unknown_cmd");
|
let dir = unique_sandbox_dir("unknown_cmd");
|
||||||
let out = run_dcr(&["foobar"], &dir);
|
let out = run_dcr(&["foobar"], &dir);
|
||||||
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
assert!(
|
assert!(
|
||||||
!out.status.success(),
|
stdout.contains("error") || stdout.contains("Unknown"),
|
||||||
"unknown command should exit non-zero"
|
|
||||||
);
|
|
||||||
let combined = format!(
|
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
combined.contains("error") || combined.contains("Unknown"),
|
|
||||||
"unknown command should print error"
|
"unknown command should print error"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -112,13 +104,9 @@ fn build_without_toml_fails() {
|
|||||||
let dir = unique_sandbox_dir("build_no_toml");
|
let dir = unique_sandbox_dir("build_no_toml");
|
||||||
let out = run_dcr(&["build"], &dir);
|
let out = run_dcr(&["build"], &dir);
|
||||||
assert!(!out.status.success(), "build without dcr.toml should fail");
|
assert!(!out.status.success(), "build without dcr.toml should fail");
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
combined.contains("dcr.toml") || combined.contains("not found"),
|
stdout.contains("dcr.toml") || stdout.contains("not found"),
|
||||||
"should mention missing dcr.toml"
|
"should mention missing dcr.toml"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -144,13 +132,9 @@ fn new_existing_dir_fails() {
|
|||||||
std::fs::create_dir_all(dir.join("hello")).expect("failed to create dir");
|
std::fs::create_dir_all(dir.join("hello")).expect("failed to create dir");
|
||||||
let out = run_dcr(&["new", "hello"], &dir);
|
let out = run_dcr(&["new", "hello"], &dir);
|
||||||
assert!(!out.status.success(), "dcr new on existing dir should fail");
|
assert!(!out.status.success(), "dcr new on existing dir should fail");
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
combined.contains("already exists") || combined.contains("error"),
|
stdout.contains("already exists") || stdout.contains("error"),
|
||||||
"should report dir already exists"
|
"should report dir already exists"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,13 +149,9 @@ fn init_nonempty_dir_fails() {
|
|||||||
!out.status.success(),
|
!out.status.success(),
|
||||||
"dcr init in non-empty dir should fail"
|
"dcr init in non-empty dir should fail"
|
||||||
);
|
);
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
combined.contains("not empty") || combined.contains("error"),
|
stdout.contains("not empty") || stdout.contains("error"),
|
||||||
"should report dir not empty"
|
"should report dir not empty"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -206,13 +186,9 @@ fn run_library_project_fails() {
|
|||||||
let envs = [("DCR_COMPILER", compiler)];
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
let out = run_dcr_env(&["run"], &dir, &envs);
|
let out = run_dcr_env(&["run"], &dir, &envs);
|
||||||
assert!(!out.status.success(), "dcr run on staticlib should fail");
|
assert!(!out.status.success(), "dcr run on staticlib should fail");
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
combined.contains("library") || combined.contains("Cannot run"),
|
stdout.contains("library") || stdout.contains("Cannot run"),
|
||||||
"should report cannot run library"
|
"should report cannot run library"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -250,13 +226,9 @@ fn new_no_name_fails() {
|
|||||||
let dir = unique_sandbox_dir("new_noname");
|
let dir = unique_sandbox_dir("new_noname");
|
||||||
let out = run_dcr(&["new"], &dir);
|
let out = run_dcr(&["new"], &dir);
|
||||||
assert!(!out.status.success(), "dcr new without name should fail");
|
assert!(!out.status.success(), "dcr new without name should fail");
|
||||||
let combined = format!(
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
combined.contains("not specified") || combined.contains("error"),
|
stdout.contains("not specified") || stdout.contains("error"),
|
||||||
"should report name not specified"
|
"should report name not specified"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,16 +45,6 @@ fn workspace_build_and_clean_all() {
|
|||||||
out.status.success(),
|
out.status.success(),
|
||||||
"workspace clean --all --release should succeed"
|
"workspace clean --all --release should succeed"
|
||||||
);
|
);
|
||||||
let combined = format!(
|
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&out.stdout),
|
|
||||||
String::from_utf8_lossy(&out.stderr)
|
|
||||||
);
|
|
||||||
let spam = combined.matches("Directory target not found").count();
|
|
||||||
assert!(
|
|
||||||
spam <= 1,
|
|
||||||
"clean --all should not spam missing member target/: {combined}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let target_dir = "target/x86_64-unknown-linux-gnu";
|
let target_dir = "target/x86_64-unknown-linux-gnu";
|
||||||
assert!(
|
assert!(
|
||||||
@@ -65,8 +55,4 @@ fn workspace_build_and_clean_all() {
|
|||||||
root.join(target_dir).join("debug").exists(),
|
root.join(target_dir).join("debug").exists(),
|
||||||
"root target/x86_64-unknown-linux-gnu/debug should remain"
|
"root target/x86_64-unknown-linux-gnu/debug should remain"
|
||||||
);
|
);
|
||||||
|
|
||||||
let out = run_dcr_env(&["clean", "--all"], &root, &envs);
|
|
||||||
assert!(out.status.success(), "workspace clean --all should succeed");
|
|
||||||
assert!(!root.join("target").exists(), "root target/ should be gone");
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user