mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-22 13:19:37 +00:00
Compare commits
22 Commits
588e4dd13f
...
v0.8.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bf1346d1b | |||
| 794d571d58 | |||
| d2df85a064 | |||
| 5e8037da97 | |||
| f29322a547 | |||
| 1a6a261cac | |||
| f92cc6ae91 | |||
| 5aefbc6498 | |||
| 594007f022 | |||
| d10ec84004 | |||
| 574ba8b6d0 | |||
| 75eace9d73 | |||
| 2446cb7970 | |||
| 689fea7465 | |||
| 1dcc8e614a | |||
| a3f8444c20 | |||
| 1f3fc62cb6 | |||
| f8d8c95912 | |||
| 9e07f70c78 | |||
| 0376eded3f | |||
| 803d5891ce | |||
| 97db82e4f8 |
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@@ -11,6 +11,8 @@ on:
|
|||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUSTFLAGS: -Dwarnings
|
RUSTFLAGS: -Dwarnings
|
||||||
|
CARGO_INCREMENTAL: "0"
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
checks:
|
checks:
|
||||||
@@ -24,7 +26,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
shared-key: dcr-ci
|
||||||
|
cache-all-crates: true
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
@@ -52,7 +60,22 @@ jobs:
|
|||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
shared-key: dcr-ci
|
||||||
|
cache-all-crates: true
|
||||||
|
cache-on-failure: true
|
||||||
|
|
||||||
|
- name: Cache apt packages
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-${{ runner.os }}-${{ hashFiles('.github/workflows/ci.yml') }}
|
||||||
|
restore-keys: apt-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install Linux tools
|
- name: Install Linux tools
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@@ -64,7 +87,8 @@ jobs:
|
|||||||
nasm \
|
nasm \
|
||||||
clang-format \
|
clang-format \
|
||||||
clang-tidy \
|
clang-tidy \
|
||||||
git
|
git \
|
||||||
|
llvm
|
||||||
|
|
||||||
- name: Install macOS tools
|
- name: Install macOS tools
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
@@ -76,8 +100,11 @@ jobs:
|
|||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
choco install nasm -y
|
choco install nasm llvm -y
|
||||||
echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
if (Test-Path "C:\Program Files\LLVM\bin") {
|
||||||
|
echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
}
|
||||||
|
|
||||||
- name: Unit tests (binary crate)
|
- name: Unit tests (binary crate)
|
||||||
run: cargo test --bins -- --nocapture
|
run: cargo test --bins -- --nocapture
|
||||||
@@ -89,6 +116,10 @@ jobs:
|
|||||||
if: runner.os != 'Windows'
|
if: runner.os != 'Windows'
|
||||||
run: cargo test --tests --features archive -- --nocapture
|
run: cargo test --tests --features archive -- --nocapture
|
||||||
|
|
||||||
|
- name: sccache stats
|
||||||
|
if: always()
|
||||||
|
run: sccache --show-stats || true
|
||||||
|
|
||||||
test-archive-linux:
|
test-archive-linux:
|
||||||
name: archive feature smoke
|
name: archive feature smoke
|
||||||
needs: checks
|
needs: checks
|
||||||
@@ -99,15 +130,25 @@ jobs:
|
|||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
shared-key: dcr-ci
|
||||||
|
cache-all-crates: true
|
||||||
|
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential clang nasm
|
sudo apt-get install -y build-essential clang nasm llvm
|
||||||
|
|
||||||
- name: Build with archive
|
- name: Build with archive
|
||||||
run: cargo build --features archive
|
run: cargo build --features archive
|
||||||
|
|
||||||
- name: Flat-bin + unit suite with archive
|
- name: Flat-bin + unit suite with archive
|
||||||
run: cargo test --features archive --test cli_build --test cli_workspace --test cli_errors -- --nocapture
|
run: cargo test --features archive --test cli_build --test cli_workspace --test cli_errors -- --nocapture
|
||||||
|
|
||||||
|
- name: sccache stats
|
||||||
|
if: always()
|
||||||
|
run: sccache --show-stats || true
|
||||||
|
|||||||
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 }}"
|
||||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,5 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.8.3] - 2026-08-03 "Конфигурация host target, аргументы run и нативные пути артефактов / Host Target Config, Run Args & Native Artifact Paths"
|
||||||
|
|
||||||
|
### RU
|
||||||
|
|
||||||
|
**Добавлено:**
|
||||||
|
|
||||||
|
- **`dcr run -- <args>...`** — cargo-style: всё после bare `--` передаётся собранному бинарнику
|
||||||
|
(`dcr run --release -- --test_help`). Работает и для прямого запуска, и для `[run].cmd`.
|
||||||
|
- **Интеграционные / unit-тесты** — host target sections применяют `ldflags` без `--target`; проброс аргументов после `--`; парсер `split_double_dash`.
|
||||||
|
|
||||||
|
**Изменено:**
|
||||||
|
|
||||||
|
- **Native-сборка резолвит host triple** — без `--target` / без `build.target` конфиг-секции вида
|
||||||
|
`[build.x86_64-unknown-linux-gnu]` (и аналоги host) снова применяются (`cflags` / `ldflags` / toolchain).
|
||||||
|
Раньше target был «пустым», секции молча игнорировались, а артефакты уже лежали под host triple — линковка без `-L`/`-l`.
|
||||||
|
- **Пустой target** — `normalize_target_os("")` больше не warn'ит `Unknown target ''`.
|
||||||
|
- **`dcr run --help` / man `dcr-run`** — документирован разделитель `--` и forwarding args.
|
||||||
|
|
||||||
|
**Исправлено:**
|
||||||
|
|
||||||
|
- **Нативные пути артефактов на Windows и macOS** — без явного `--target` / `build.target` артефакты снова сохраняются в `target/<profile>`, поэтому `dcr run` и flat-bin находят результат сборки.
|
||||||
|
- Target-specific `ldflags` на native Linux/macOS/Windows (host triple) не доходили до линкера.
|
||||||
|
- Ложный warning на пустой target string.
|
||||||
|
|
||||||
|
### EN
|
||||||
|
|
||||||
|
**Added:**
|
||||||
|
|
||||||
|
- **`dcr run -- <args>...`** — cargo-style: arguments after a bare `--` are forwarded to the built binary
|
||||||
|
(`dcr run --release -- --test_help`). Works for direct binary runs and `[run].cmd`.
|
||||||
|
- **Tests** — host target sections apply `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests.
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
|
||||||
|
- **Native builds resolve the host triple** — without `--target` / without package `build.target`, host sections such as
|
||||||
|
`[build.x86_64-unknown-linux-gnu]` (and host equivalents) apply again (`cflags` / `ldflags` / toolchain).
|
||||||
|
Previously the target was empty, those sections were silently ignored while artifacts already used the host triple path — link failed without `-L`/`-l` (e.g. sandbox micro-lang + `libsct-elf`).
|
||||||
|
- **Empty target** — `normalize_target_os("")` no longer warns `Unknown target ''`.
|
||||||
|
- **`dcr run --help` / man `dcr-run`** — document `--` and argument forwarding.
|
||||||
|
|
||||||
|
**Fixed:**
|
||||||
|
|
||||||
|
- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts again use `target/<profile>`, so `dcr run` and flat-bin find the build output.
|
||||||
|
- Target-specific `ldflags` on native host builds not reaching the linker.
|
||||||
|
- Spurious warning for an empty target string.
|
||||||
|
|
||||||
## [0.8.2] - 2026-07-22 "CLI Progress, archive Feature, Workspace Path Fixes / CLI Progress, archive Feature, Workspace Path Fixes"
|
## [0.8.2] - 2026-07-22 "CLI Progress, archive Feature, Workspace Path Fixes / CLI Progress, archive Feature, Workspace Path Fixes"
|
||||||
|
|
||||||
### RU
|
### RU
|
||||||
@@ -215,25 +261,31 @@
|
|||||||
### RU
|
### RU
|
||||||
|
|
||||||
**Добавлено:**
|
**Добавлено:**
|
||||||
|
|
||||||
- Учтён `target_env` в `default_target_triple()` — Linux (gnu/musl) и Windows (msvc/gnu) теперь используют правильное окружение, а не хардкод.
|
- Учтён `target_env` в `default_target_triple()` — Linux (gnu/musl) и Windows (msvc/gnu) теперь используют правильное окружение, а не хардкод.
|
||||||
|
|
||||||
**Исправлено:**
|
**Исправлено:**
|
||||||
|
|
||||||
- **Сборка на macOS Apple Silicon падала с `_main` undefined** — `--target=` теперь передаётся и в ldflags, не только в cflags. `default_target_triple()` на macOS использует `std::env::consts::ARCH` вместо хардкода `x86_64`.
|
- **Сборка на macOS Apple Silicon падала с `_main` undefined** — `--target=` теперь передаётся и в ldflags, не только в cflags. `default_target_triple()` на macOS использует `std::env::consts::ARCH` вместо хардкода `x86_64`.
|
||||||
- **Бейджи GitHub Stars и GPL-3.0 не рендерились в README** — блок бейджей переведён на чистый HTML.
|
- **Бейджи GitHub Stars и GPL-3.0 не рендерились в README** — блок бейджей переведён на чистый HTML.
|
||||||
|
|
||||||
**Изменено:**
|
**Изменено:**
|
||||||
|
|
||||||
- Вычисление таргета по умолчанию вынесено в общую функцию `default_target_triple()` — устранено дублирование в build, run, clean.
|
- Вычисление таргета по умолчанию вынесено в общую функцию `default_target_triple()` — устранено дублирование в build, run, clean.
|
||||||
|
|
||||||
### EN
|
### EN
|
||||||
|
|
||||||
**Added:**
|
**Added:**
|
||||||
|
|
||||||
- `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values.
|
- `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values.
|
||||||
|
|
||||||
**Fixed:**
|
**Fixed:**
|
||||||
|
|
||||||
- **macOS Apple Silicon builds failed with `_main` undefined** — `--target=` is now passed to ldflags, not just cflags. `default_target_triple()` uses `std::env::consts::ARCH` on macOS instead of hardcoded `x86_64`.
|
- **macOS Apple Silicon builds failed with `_main` undefined** — `--target=` is now passed to ldflags, not just cflags. `default_target_triple()` uses `std::env::consts::ARCH` on macOS instead of hardcoded `x86_64`.
|
||||||
- **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML.
|
- **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML.
|
||||||
|
|
||||||
**Changed:**
|
**Changed:**
|
||||||
|
|
||||||
- Default target resolution extracted into shared `default_target_triple()` — removed duplication across build, run, clean.
|
- Default target resolution extracted into shared `default_target_triple()` — removed duplication across build, run, clean.
|
||||||
|
|
||||||
## [0.7.1] - 2026-06-02 "Мультиархитектурное расширение / The Multi-Arch Expansion"
|
## [0.7.1] - 2026-06-02 "Мультиархитектурное расширение / The Multi-Arch Expansion"
|
||||||
@@ -241,6 +293,7 @@
|
|||||||
### RU
|
### RU
|
||||||
|
|
||||||
**Добавлено:**
|
**Добавлено:**
|
||||||
|
|
||||||
- Вывод `--help` для всех команд с цветным форматированием.
|
- Вывод `--help` для всех команд с цветным форматированием.
|
||||||
- man-страницы (12 штук в `man/man1/`) и их установка во всех пакетах.
|
- man-страницы (12 штук в `man/man1/`) и их установка во всех пакетах.
|
||||||
- Валидация имен проектов в `dcr init` и `dcr new` (только ASCII буквы, цифры, `_` и `-`).
|
- Валидация имен проектов в `dcr init` и `dcr new` (только ASCII буквы, цифры, `_` и `-`).
|
||||||
@@ -251,6 +304,7 @@
|
|||||||
- Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и сборка Snap в CI.
|
- Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и сборка Snap в CI.
|
||||||
|
|
||||||
**Изменено:**
|
**Изменено:**
|
||||||
|
|
||||||
- Функция `validate_package_name` стала публичной.
|
- Функция `validate_package_name` стала публичной.
|
||||||
- Команды `gen`, `tree`, `fmt`, `setup` больше не игнорируют аргументы и используют цветной вывод.
|
- Команды `gen`, `tree`, `fmt`, `setup` больше не игнорируют аргументы и используют цветной вывод.
|
||||||
- Инструкция в README изменена с `| bash` на `| sh` для POSIX.
|
- Инструкция в README изменена с `| bash` на `| sh` для POSIX.
|
||||||
@@ -268,6 +322,7 @@
|
|||||||
### EN
|
### EN
|
||||||
|
|
||||||
**Added:**
|
**Added:**
|
||||||
|
|
||||||
- `--help` for all commands with styled headers and usage lines.
|
- `--help` for all commands with styled headers and usage lines.
|
||||||
- Man pages (12 troff man pages in `man/man1/`) and their installation across all packaging formats.
|
- Man pages (12 troff man pages in `man/man1/`) and their installation across all packaging formats.
|
||||||
- Project name validation in `dcr init` and `dcr new` (ASCII letters, digits, `_`, `-` only).
|
- Project name validation in `dcr init` and `dcr new` (ASCII letters, digits, `_`, `-` only).
|
||||||
@@ -278,6 +333,7 @@
|
|||||||
- AUR packages (`dcr-dev`, `dcr-dev-bin`) and Snap build in CI.
|
- AUR packages (`dcr-dev`, `dcr-dev-bin`) and Snap build in CI.
|
||||||
|
|
||||||
**Changed:**
|
**Changed:**
|
||||||
|
|
||||||
- `validate_package_name` made public and callable from CLI commands directly.
|
- `validate_package_name` made public and callable from CLI commands directly.
|
||||||
- `gen.rs`, `tree.rs`, `fmt.rs`, `setup.rs` no longer ignore arguments and use styled output.
|
- `gen.rs`, `tree.rs`, `fmt.rs`, `setup.rs` no longer ignore arguments and use styled output.
|
||||||
- README install instructions changed from `| bash` to `| sh` for POSIX compatibility.
|
- README install instructions changed from `| bash` to `| sh` for POSIX compatibility.
|
||||||
@@ -454,6 +510,7 @@ Added:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`).
|
Produces `KERNEL.EFI` (works for `bin`, `staticlib`, and `sharedlib`).
|
||||||
|
|
||||||
- Automatic injection of `--target=<build.target>` into compiler flags when `build.target` is set in `dcr.toml`. This greatly simplifies clang-based cross-compilation (especially for bare-metal targets like `aarch64-none-elf`).
|
- Automatic injection of `--target=<build.target>` into compiler flags when `build.target` is set in `dcr.toml`. This greatly simplifies clang-based cross-compilation (especially for bare-metal targets like `aarch64-none-elf`).
|
||||||
- Bare-metal targets (containing `none`, `-elf`, `eabi`, `baremetal`) no longer receive DCR's internal default flags (`-g`, `-Wall`, `-Wextra`, `-fno-omit-frame-pointer`, `-DDCR_DEBUG`, etc.). Prevents unwanted sections (`.comment`, debug info, etc.) that break custom linker scripts when `inherit = true`.
|
- Bare-metal targets (containing `none`, `-elf`, `eabi`, `baremetal`) no longer receive DCR's internal default flags (`-g`, `-Wall`, `-Wextra`, `-fno-omit-frame-pointer`, `-DDCR_DEBUG`, etc.). Prevents unwanted sections (`.comment`, debug info, etc.) that break custom linker scripts when `inherit = true`.
|
||||||
- `build.ldscript` in `dcr.toml` — linker script path passed as `-T <path>` to the linker. Essential for bare-metal/embedded/freestanding targets.
|
- `build.ldscript` in `dcr.toml` — linker script path passed as `-T <path>` to the linker. Essential for bare-metal/embedded/freestanding targets.
|
||||||
|
|||||||
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.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"fatfs",
|
"fatfs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dcr"
|
name = "dcr"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
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."
|
||||||
|
|||||||
55
README.md
55
README.md
@@ -34,32 +34,6 @@
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>Linux (Dexoron Packages)</b></td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
See https://pkg.dexoron.su for settings youre packages manager
|
|
||||||
|
|
||||||
**Arch Linux:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo pacman -Sy dcr
|
|
||||||
```
|
|
||||||
|
|
||||||
**Debian/Ubuntu:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt update && sudo apt install dcr
|
|
||||||
```
|
|
||||||
|
|
||||||
**Fedora/RHEL:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo dnf install dcr
|
|
||||||
```
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Arch Linux (AUR)</b></td>
|
<td><b>Arch Linux (AUR)</b></td>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
@@ -103,21 +77,28 @@ cargo install dcr
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>Linux / macOS (script)</b></td>
|
<td><b>dcrup (Linux / macOS / BSD / Windows(Bash))</b></td>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -fsSL https://dcr.dexoron.su/install.sh | sh
|
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
|
||||||
|
export PATH="$HOME/.dcr/bin:$PATH"
|
||||||
|
dcrup install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>BSD (script)</b></td>
|
<td><b>dcrup (Windows PowerShell)</b></td>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
```sh
|
```powershell
|
||||||
curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
|
irm https://ps1.dcr-tool.ru | iex
|
||||||
|
# or:
|
||||||
|
# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
|
||||||
|
# powershell -File .\dcrup.ps1 self-install
|
||||||
|
# $env:Path += ";$env:USERPROFILE\.dcr\bin"
|
||||||
|
dcrup install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
@@ -133,16 +114,6 @@ curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
|
|||||||
sudo snap install --classic --dangerous dcrup_*.snap
|
sudo snap install --classic --dangerous dcrup_*.snap
|
||||||
```
|
```
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Windows (PowerShell)</b></td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://dcr.dexoron.su/install.ps1 | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -151,7 +122,7 @@ irm https://dcr.dexoron.su/install.ps1 | iex
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/dexoron/dcr.git
|
git clone https://github.com/dexoron/dcr.git
|
||||||
cd dcr && cargo build --release
|
cd dcr && cargo build --release --features archive
|
||||||
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
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%
|
||||||
13
dcrup.cmd
Executable file
13
dcrup.cmd
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
if exist "%SCRIPT_DIR%dcrup.ps1" (
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%dcrup.ps1" %*
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
if exist "%USERPROFILE%\.dcr\bin\dcrup.ps1" (
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\.dcr\bin\dcrup.ps1" %*
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
)
|
||||||
|
echo [error] dcrup.ps1 not found near dcrup.cmd >&2
|
||||||
|
exit /b 1
|
||||||
367
dcrup.ps1
Normal file
367
dcrup.ps1
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$DcrupHome = if ($env:DCRUP_HOME) { $env:DCRUP_HOME } else { Join-Path $env:USERPROFILE '.dcr' }
|
||||||
|
$DcrupBin = if ($env:DCRUP_BIN) { $env:DCRUP_BIN } else { Join-Path $DcrupHome 'bin' }
|
||||||
|
$DcrupTc = Join-Path $DcrupHome 'toolchains'
|
||||||
|
$DcrupMeta = Join-Path $DcrupHome 'meta'
|
||||||
|
$RepoUrl = if ($env:DCRUP_REPO) { $env:DCRUP_REPO } else { 'https://github.com/dexoron/dcr' }
|
||||||
|
$ApiLatest = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
|
||||||
|
$ApiAll = 'https://api.github.com/repos/dexoron/dcr/releases'
|
||||||
|
$Features = if ($env:DCR_FEATURES) { $env:DCR_FEATURES } else { 'archive' }
|
||||||
|
|
||||||
|
function Info($m) { Write-Host "[dcrup] $m" -ForegroundColor Cyan }
|
||||||
|
function Ok($m) { Write-Host "[ok] $m" -ForegroundColor Green }
|
||||||
|
function Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
|
||||||
|
function Die($m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 }
|
||||||
|
|
||||||
|
function Show-Usage {
|
||||||
|
@'
|
||||||
|
dcrup — install and switch DCR versions (Windows)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
dcrup install <spec> [--build|--release] [--force]
|
||||||
|
dcrup default <spec>
|
||||||
|
dcrup update
|
||||||
|
dcrup list
|
||||||
|
dcrup show
|
||||||
|
dcrup which
|
||||||
|
dcrup uninstall [<spec>|--all]
|
||||||
|
dcrup self-install [--to DIR]
|
||||||
|
dcrup help
|
||||||
|
|
||||||
|
Spec:
|
||||||
|
stable | dev | night
|
||||||
|
VERSION → VERSION@stable (e.g. 0.8.2)
|
||||||
|
VERSION@stable
|
||||||
|
VERSION@dev
|
||||||
|
night → always build from branch "dev" HEAD
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
--release download GitHub Release binary (default for stable/dev)
|
||||||
|
--build cargo build --release --features archive
|
||||||
|
night always --build (prebuilt not available)
|
||||||
|
|
||||||
|
Env:
|
||||||
|
DCRUP_HOME default ~/.dcr
|
||||||
|
DCRUP_BIN default $DCRUP_HOME/bin
|
||||||
|
DCR_FEATURES default archive
|
||||||
|
DCRUP_REPO git/GitHub repo URL
|
||||||
|
'@ | Write-Host
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-Dirs {
|
||||||
|
New-Item -ItemType Directory -Force -Path $DcrupTc, $DcrupBin, $DcrupMeta | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Target {
|
||||||
|
$arch = $env:PROCESSOR_ARCHITECTURE
|
||||||
|
if ($env:PROCESSOR_ARCHITEW6432) { $arch = $env:PROCESSOR_ARCHITEW6432 }
|
||||||
|
switch ($arch) {
|
||||||
|
'AMD64' { return 'x86_64-pc-windows-msvc' }
|
||||||
|
'ARM64' { return 'aarch64-pc-windows-msvc' }
|
||||||
|
'x86' { return 'i686-pc-windows-msvc' }
|
||||||
|
default { Die "unsupported arch: $arch" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Normalize-Version([string]$v) {
|
||||||
|
if ($v.StartsWith('v')) { return $v.Substring(1) }
|
||||||
|
return $v
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parse-Spec([string]$raw) {
|
||||||
|
$script:SpecRaw = $raw
|
||||||
|
$script:SpecChannel = $null
|
||||||
|
$script:SpecVersion = $null
|
||||||
|
$script:SpecFloating = $false
|
||||||
|
|
||||||
|
if ($raw -in @('stable', 'dev', 'night')) {
|
||||||
|
$script:SpecChannel = $raw
|
||||||
|
$script:SpecFloating = $true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($raw -match '^(.*)@(.*)$') {
|
||||||
|
$script:SpecVersion = Normalize-Version $Matches[1]
|
||||||
|
$script:SpecChannel = $Matches[2]
|
||||||
|
if ($script:SpecChannel -eq 'night') { Die 'cannot pin version on night' }
|
||||||
|
if ($script:SpecChannel -notin @('stable', 'dev')) { Die "unknown channel: $($script:SpecChannel)" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$script:SpecVersion = Normalize-Version $raw
|
||||||
|
$script:SpecChannel = 'stable'
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toolchain-Id([string]$extra = '') {
|
||||||
|
if ($script:SpecChannel -eq 'night') {
|
||||||
|
return "night-$extra"
|
||||||
|
}
|
||||||
|
if ($script:SpecFloating) {
|
||||||
|
return "$($script:SpecChannel)-$extra"
|
||||||
|
}
|
||||||
|
return "$($script:SpecVersion)-$($script:SpecChannel)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Active-Path { Join-Path $DcrupMeta 'active' }
|
||||||
|
|
||||||
|
function Read-Active {
|
||||||
|
$p = Active-Path
|
||||||
|
if (Test-Path $p) { return (Get-Content $p -Raw).Trim() }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Active([string]$id) {
|
||||||
|
Set-Content -Path (Active-Path) -Value $id -NoNewline
|
||||||
|
}
|
||||||
|
|
||||||
|
function Link-Default([string]$id) {
|
||||||
|
$src = Join-Path (Join-Path $DcrupTc $id) 'dcr.exe'
|
||||||
|
if (-not (Test-Path $src)) { Die "toolchain not installed: $id" }
|
||||||
|
$dst = Join-Path $DcrupBin 'dcr.exe'
|
||||||
|
Copy-Item $src $dst -Force
|
||||||
|
Write-Active $id
|
||||||
|
Ok "default → $id ($dst)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Fetch-Release($channel, $version) {
|
||||||
|
if ($version) {
|
||||||
|
return Invoke-RestMethod -Uri "https://api.github.com/repos/dexoron/dcr/releases/tags/v$version"
|
||||||
|
}
|
||||||
|
if ($channel -eq 'dev') {
|
||||||
|
$all = Invoke-RestMethod -Uri $ApiAll
|
||||||
|
$pre = $all | Where-Object { $_.prerelease } | Select-Object -First 1
|
||||||
|
if (-not $pre) { Die 'no prerelease (dev) found' }
|
||||||
|
return $pre
|
||||||
|
}
|
||||||
|
return Invoke-RestMethod -Uri $ApiLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Prebuilt([switch]$Force) {
|
||||||
|
$triple = Get-Target
|
||||||
|
$rel = Fetch-Release $script:SpecChannel $script:SpecVersion
|
||||||
|
$tag = $rel.tag_name
|
||||||
|
$ver = Normalize-Version $tag
|
||||||
|
$assetName = "dcr-$triple-$ver.exe"
|
||||||
|
$asset = $rel.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
|
||||||
|
if (-not $asset) { Die "asset not found: $assetName" }
|
||||||
|
|
||||||
|
$id = if ($script:SpecFloating) { Toolchain-Id $ver } else { Toolchain-Id }
|
||||||
|
$dest = Join-Path $DcrupTc $id
|
||||||
|
$bin = Join-Path $dest 'dcr.exe'
|
||||||
|
if ((Test-Path $bin) -and -not $Force) {
|
||||||
|
Ok "already installed: $id"
|
||||||
|
Link-Default $id
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Ensure-Dirs
|
||||||
|
New-Item -ItemType Directory -Force -Path $dest | Out-Null
|
||||||
|
Info "downloading $assetName …"
|
||||||
|
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $bin
|
||||||
|
@"
|
||||||
|
channel=$($script:SpecChannel)
|
||||||
|
version=$ver
|
||||||
|
tag=$tag
|
||||||
|
source=release
|
||||||
|
triple=$triple
|
||||||
|
"@ | Set-Content (Join-Path $dest 'dcrup-meta')
|
||||||
|
Ok "installed prebuilt $id"
|
||||||
|
Link-Default $id
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Build([switch]$Force) {
|
||||||
|
if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Die 'git not found' }
|
||||||
|
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { Die 'cargo not found' }
|
||||||
|
|
||||||
|
$tmp = Join-Path $env:TEMP ("dcrup-" + [guid]::NewGuid().ToString('N'))
|
||||||
|
New-Item -ItemType Directory -Path $tmp | Out-Null
|
||||||
|
try {
|
||||||
|
$src = Join-Path $tmp 'src'
|
||||||
|
if ($script:SpecChannel -eq 'night') {
|
||||||
|
Info "cloning dev for night …"
|
||||||
|
git clone --depth 1 --branch dev $RepoUrl $src 2>$null
|
||||||
|
if ($LASTEXITCODE -ne 0) { git clone --depth 1 $RepoUrl $src | Out-Null }
|
||||||
|
$sha = (git -C $src rev-parse --short HEAD).Trim()
|
||||||
|
$id = Toolchain-Id $sha
|
||||||
|
$ver = 'night'
|
||||||
|
} elseif ($script:SpecFloating) {
|
||||||
|
$rel = Fetch-Release $script:SpecChannel $null
|
||||||
|
$tag = $rel.tag_name
|
||||||
|
$ver = Normalize-Version $tag
|
||||||
|
$id = Toolchain-Id $ver
|
||||||
|
Info "cloning $tag …"
|
||||||
|
git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
git clone --depth 1 $RepoUrl $src | Out-Null
|
||||||
|
git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
|
||||||
|
git -C $src checkout $tag
|
||||||
|
}
|
||||||
|
$sha = (git -C $src rev-parse --short HEAD).Trim()
|
||||||
|
} else {
|
||||||
|
$tag = "v$($script:SpecVersion)"
|
||||||
|
$ver = $script:SpecVersion
|
||||||
|
$id = Toolchain-Id
|
||||||
|
Info "cloning $tag …"
|
||||||
|
git clone --depth 1 --branch $tag $RepoUrl $src 2>$null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
git clone --depth 1 $RepoUrl $src | Out-Null
|
||||||
|
git -C $src fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
|
||||||
|
git -C $src checkout $tag
|
||||||
|
}
|
||||||
|
$sha = (git -C $src rev-parse --short HEAD).Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
$dest = Join-Path $DcrupTc $id
|
||||||
|
$bin = Join-Path $dest 'dcr.exe'
|
||||||
|
if ((Test-Path $bin) -and -not $Force) {
|
||||||
|
Ok "already installed: $id"
|
||||||
|
Link-Default $id
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$triple = Get-Target
|
||||||
|
Info "building release (features: $Features) …"
|
||||||
|
Push-Location $src
|
||||||
|
cargo build --release --features $Features --target $triple
|
||||||
|
if ($LASTEXITCODE -ne 0) { Die 'cargo build failed' }
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path $dest | Out-Null
|
||||||
|
Copy-Item (Join-Path $src "target\$triple\release\dcr.exe") $bin -Force
|
||||||
|
@"
|
||||||
|
channel=$($script:SpecChannel)
|
||||||
|
version=$ver
|
||||||
|
git=$sha
|
||||||
|
source=build
|
||||||
|
features=$Features
|
||||||
|
"@ | Set-Content (Join-Path $dest 'dcrup-meta')
|
||||||
|
Ok "installed build $id"
|
||||||
|
Link-Default $id
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-UserPath {
|
||||||
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
|
if (-not $userPath) { $userPath = '' }
|
||||||
|
if (-not ($userPath -split ';' | Where-Object { $_ -eq $DcrupBin })) {
|
||||||
|
$new = if ($userPath) { "$userPath;$DcrupBin" } else { $DcrupBin }
|
||||||
|
[Environment]::SetEnvironmentVariable('Path', $new, 'User')
|
||||||
|
$env:Path = "$env:Path;$DcrupBin"
|
||||||
|
Warn "added to user PATH: $DcrupBin (restart shell if needed)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-CmdShim {
|
||||||
|
$cmd = Join-Path $DcrupBin 'dcrup.cmd'
|
||||||
|
$ps1 = Join-Path $DcrupBin 'dcrup.ps1'
|
||||||
|
@"
|
||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0dcrup.ps1" %*
|
||||||
|
"@ | Set-Content -Path $cmd -Encoding ASCII
|
||||||
|
if (-not (Test-Path $ps1)) {
|
||||||
|
Copy-Item $PSCommandPath $ps1 -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-Install {
|
||||||
|
param([string[]]$Args)
|
||||||
|
$mode = 'release'
|
||||||
|
$force = $false
|
||||||
|
$spec = $null
|
||||||
|
foreach ($a in $Args) {
|
||||||
|
switch -Regex ($a) {
|
||||||
|
'^--build$' { $mode = 'build'; continue }
|
||||||
|
'^--release$' { $mode = 'release'; continue }
|
||||||
|
'^--force$' { $force = $true; continue }
|
||||||
|
'^-' { Die "unknown flag: $a" }
|
||||||
|
default {
|
||||||
|
if ($spec) { Die "unexpected: $a" }
|
||||||
|
$spec = $a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $spec) { Die 'usage: dcrup install <spec> [--build|--release] [--force]' }
|
||||||
|
Parse-Spec $spec
|
||||||
|
Ensure-Dirs
|
||||||
|
if ($script:SpecChannel -eq 'night') { $mode = 'build' }
|
||||||
|
Info "install spec=$spec channel=$($script:SpecChannel) mode=$mode"
|
||||||
|
if ($mode -eq 'build') { Install-Build -Force:$force } else { Install-Prebuilt -Force:$force }
|
||||||
|
Ensure-UserPath
|
||||||
|
Install-CmdShim
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-List {
|
||||||
|
Ensure-Dirs
|
||||||
|
$active = Read-Active
|
||||||
|
Get-ChildItem $DcrupTc -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$mark = if ($_.Name -eq $active) { ' *' } else { ' ' }
|
||||||
|
Write-Host "$mark $($_.Name)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-Show {
|
||||||
|
Ensure-Dirs
|
||||||
|
Write-Host "DCRUP_HOME=$DcrupHome"
|
||||||
|
Write-Host "DCRUP_BIN=$DcrupBin"
|
||||||
|
Write-Host "active=$(Read-Active)"
|
||||||
|
$exe = Join-Path $DcrupBin 'dcr.exe'
|
||||||
|
if (Test-Path $exe) { & $exe --version }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cmd-SelfInstall {
|
||||||
|
Ensure-Dirs
|
||||||
|
Copy-Item $PSCommandPath (Join-Path $DcrupBin 'dcrup.ps1') -Force
|
||||||
|
Install-CmdShim
|
||||||
|
Ensure-UserPath
|
||||||
|
Ok "dcrup installed to $DcrupBin (use: dcrup …)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = if ($args.Count -ge 1) { $args[0] } else { 'help' }
|
||||||
|
$rest = @()
|
||||||
|
if ($args.Count -gt 1) { $rest = $args[1..($args.Count - 1)] }
|
||||||
|
|
||||||
|
switch ($cmd) {
|
||||||
|
{ $_ -in @('help', '-h', '--help') } { Show-Usage }
|
||||||
|
'install' { Cmd-Install $rest }
|
||||||
|
'list' { Cmd-List }
|
||||||
|
'show' { Cmd-Show }
|
||||||
|
'which' {
|
||||||
|
$p = Join-Path $DcrupBin 'dcr.exe'
|
||||||
|
if (Test-Path $p) { $p } else { Die 'dcr not installed' }
|
||||||
|
}
|
||||||
|
'default' {
|
||||||
|
if ($rest.Count -ne 1) { Die 'usage: dcrup default <spec>' }
|
||||||
|
Parse-Spec $rest[0]
|
||||||
|
if ($script:SpecFloating -or $script:SpecChannel -eq 'night') {
|
||||||
|
$id = Get-ChildItem $DcrupTc -Directory |
|
||||||
|
Where-Object { $_.Name -like "$($script:SpecChannel)-*" } |
|
||||||
|
Sort-Object Name | Select-Object -Last 1
|
||||||
|
if (-not $id) { Die "not installed: $($rest[0])" }
|
||||||
|
Link-Default $id.Name
|
||||||
|
} else {
|
||||||
|
Link-Default (Toolchain-Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'update' {
|
||||||
|
$a = Read-Active
|
||||||
|
if (-not $a) { Die 'no active toolchain' }
|
||||||
|
if ($a -like 'night-*') { Cmd-Install @('night', '--build', '--force') }
|
||||||
|
elseif ($a -like 'stable-*') { Cmd-Install @('stable', '--force') }
|
||||||
|
elseif ($a -like 'dev-*') { Cmd-Install @('dev', '--force') }
|
||||||
|
else { Ok "pinned $a — install a newer version explicitly" }
|
||||||
|
}
|
||||||
|
'uninstall' {
|
||||||
|
if ($rest -contains '--all') {
|
||||||
|
Remove-Item -Recurse -Force $DcrupTc -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item (Join-Path $DcrupBin 'dcr.exe') -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item (Active-Path) -ErrorAction SilentlyContinue
|
||||||
|
Ok 'removed all'
|
||||||
|
} else {
|
||||||
|
Die 'use: dcrup uninstall --all (or install a replacement first)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'self-install' { Cmd-SelfInstall }
|
||||||
|
default { Die "unknown command: $cmd" }
|
||||||
|
}
|
||||||
599
dcrup.sh
Executable file
599
dcrup.sh
Executable file
@@ -0,0 +1,599 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
DCRUP_HOME="${DCRUP_HOME:-$HOME/.dcr}"
|
||||||
|
DCRUP_BIN="${DCRUP_BIN:-$DCRUP_HOME/bin}"
|
||||||
|
DCRUP_TC="${DCRUP_HOME}/toolchains"
|
||||||
|
DCRUP_META="${DCRUP_HOME}/meta"
|
||||||
|
REPO_URL="${DCRUP_REPO:-https://github.com/dexoron/dcr}"
|
||||||
|
API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
|
||||||
|
API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
|
||||||
|
FEATURES="${DCR_FEATURES:-archive}"
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log() { printf "${CYAN}[dcrup]${NC} %s\n" "$*"; }
|
||||||
|
ok() { printf "${GREEN}[ok]${NC} %s\n" "$*"; }
|
||||||
|
warn() { printf "${YELLOW}[warn]${NC} %s\n" "$*"; }
|
||||||
|
die() { printf "${RED}[error]${NC} %s\n" "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
dcrup — install and switch DCR versions
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
dcrup install <spec> [--build|--release] [--force] [--libc gnu|musl|auto]
|
||||||
|
dcrup default <spec>
|
||||||
|
dcrup update
|
||||||
|
dcrup list
|
||||||
|
dcrup show
|
||||||
|
dcrup which
|
||||||
|
dcrup uninstall [<spec>|--all]
|
||||||
|
dcrup self-install [--to DIR]
|
||||||
|
dcrup help
|
||||||
|
|
||||||
|
Spec:
|
||||||
|
stable | dev | night
|
||||||
|
VERSION → VERSION@stable (e.g. 0.8.2)
|
||||||
|
VERSION@stable
|
||||||
|
VERSION@dev
|
||||||
|
night → always build from branch "dev" HEAD
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
--release download GitHub Release binary (default for stable/dev)
|
||||||
|
--build cargo build --release --features archive
|
||||||
|
night always --build (prebuilt not available)
|
||||||
|
|
||||||
|
Linux libc (prebuilt triple only):
|
||||||
|
--libc gnu x86_64-unknown-linux-gnu (default)
|
||||||
|
--libc musl x86_64-unknown-linux-musl
|
||||||
|
--libc auto musl if host ldd/libc is musl, else gnu
|
||||||
|
|
||||||
|
Env:
|
||||||
|
DCRUP_HOME default ~/.dcr
|
||||||
|
DCRUP_BIN default $DCRUP_HOME/bin
|
||||||
|
DCR_FEATURES default archive
|
||||||
|
DCRUP_REPO git/GitHub repo URL
|
||||||
|
DCRUP_LIBC gnu|musl|auto (default: gnu)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
need_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_linux_libc() {
|
||||||
|
local pref="${DCRUP_LIBC:-gnu}"
|
||||||
|
case "$pref" in
|
||||||
|
gnu|glibc) echo "gnu" ;;
|
||||||
|
musl) echo "musl" ;;
|
||||||
|
auto)
|
||||||
|
if command -v ldd >/dev/null 2>&1; then
|
||||||
|
if ldd --version 2>&1 | grep -qi musl; then
|
||||||
|
echo "musl"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -e /lib/ld-musl-x86_64.so.1 || -e /lib/ld-musl-aarch64.so.1 ]]; then
|
||||||
|
echo "musl"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then
|
||||||
|
echo "gnu"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "gnu"
|
||||||
|
;;
|
||||||
|
*) die "invalid DCRUP_LIBC / --libc: $pref (use gnu|musl|auto)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_triple() {
|
||||||
|
local os arch libc
|
||||||
|
os="$(uname -s)"
|
||||||
|
arch="$(uname -m)"
|
||||||
|
case "$os:$arch" in
|
||||||
|
Linux:x86_64)
|
||||||
|
libc="$(detect_linux_libc)"
|
||||||
|
if [[ "$libc" == "musl" ]]; then
|
||||||
|
echo "x86_64-unknown-linux-musl"
|
||||||
|
else
|
||||||
|
echo "x86_64-unknown-linux-gnu"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Linux:aarch64|Linux:arm64)
|
||||||
|
libc="$(detect_linux_libc)"
|
||||||
|
if [[ "$libc" == "musl" ]]; then
|
||||||
|
echo "aarch64-unknown-linux-musl"
|
||||||
|
else
|
||||||
|
echo "aarch64-unknown-linux-gnu"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Linux:i686|Linux:i386)
|
||||||
|
libc="$(detect_linux_libc)"
|
||||||
|
if [[ "$libc" == "musl" ]]; then
|
||||||
|
echo "i686-unknown-linux-musl"
|
||||||
|
else
|
||||||
|
echo "i686-unknown-linux-gnu"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Linux:armv7l|Linux:armv7)
|
||||||
|
libc="$(detect_linux_libc)"
|
||||||
|
if [[ "$libc" == "musl" ]]; then
|
||||||
|
echo "armv7-unknown-linux-musleabihf"
|
||||||
|
else
|
||||||
|
echo "armv7-unknown-linux-gnueabihf"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Linux:riscv64)
|
||||||
|
libc="$(detect_linux_libc)"
|
||||||
|
if [[ "$libc" == "musl" ]]; then
|
||||||
|
echo "riscv64gc-unknown-linux-musl"
|
||||||
|
else
|
||||||
|
echo "riscv64gc-unknown-linux-gnu"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Darwin:x86_64) echo "x86_64-apple-darwin" ;;
|
||||||
|
Darwin:arm64|Darwin:aarch64) echo "aarch64-apple-darwin" ;;
|
||||||
|
FreeBSD:x86_64|FreeBSD:amd64) echo "x86_64-unknown-freebsd" ;;
|
||||||
|
FreeBSD:aarch64|FreeBSD:arm64) echo "aarch64-unknown-freebsd" ;;
|
||||||
|
OpenBSD:x86_64|OpenBSD:amd64) echo "x86_64-unknown-openbsd" ;;
|
||||||
|
NetBSD:x86_64|NetBSD:amd64) echo "x86_64-unknown-netbsd" ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
|
case "$arch" in
|
||||||
|
x86_64|amd64) echo "x86_64-pc-windows-gnu" ;;
|
||||||
|
aarch64|arm64) echo "aarch64-pc-windows-gnullvm" ;;
|
||||||
|
i686|i386) echo "i686-pc-windows-gnu" ;;
|
||||||
|
*) die "unsupported Windows arch under MSYS: $arch" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) die "unsupported platform: $os/$arch" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_version() {
|
||||||
|
local v="$1"
|
||||||
|
v="${v#v}"
|
||||||
|
printf '%s' "$v"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_spec() {
|
||||||
|
local raw="$1"
|
||||||
|
SPEC_RAW="$raw"
|
||||||
|
SPEC_CHANNEL=""
|
||||||
|
SPEC_VERSION=""
|
||||||
|
SPEC_FLOATING=0
|
||||||
|
|
||||||
|
case "$raw" in
|
||||||
|
"") die "missing version/channel spec" ;;
|
||||||
|
stable|dev|night)
|
||||||
|
SPEC_CHANNEL="$raw"
|
||||||
|
SPEC_VERSION=""
|
||||||
|
SPEC_FLOATING=1
|
||||||
|
;;
|
||||||
|
*@*)
|
||||||
|
SPEC_VERSION="$(normalize_version "${raw%@*}")"
|
||||||
|
SPEC_CHANNEL="${raw#*@}"
|
||||||
|
case "$SPEC_CHANNEL" in
|
||||||
|
stable|dev) ;;
|
||||||
|
night) die "cannot pin a version on night (use: dcrup install night)" ;;
|
||||||
|
*) die "unknown channel in spec: $SPEC_CHANNEL (use stable|dev)" ;;
|
||||||
|
esac
|
||||||
|
[[ -n "$SPEC_VERSION" ]] || die "empty version in spec: $raw"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
SPEC_VERSION="$(normalize_version "$raw")"
|
||||||
|
SPEC_CHANNEL="stable"
|
||||||
|
[[ -n "$SPEC_VERSION" ]] || die "invalid spec: $raw"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
toolchain_id() {
|
||||||
|
if [[ "$SPEC_CHANNEL" == "night" ]]; then
|
||||||
|
local sha="${1:-unknown}"
|
||||||
|
echo "night-${sha}"
|
||||||
|
elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
|
||||||
|
local ver="${1:-latest}"
|
||||||
|
echo "${SPEC_CHANNEL}-${ver}"
|
||||||
|
else
|
||||||
|
echo "${SPEC_VERSION}-${SPEC_CHANNEL}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_dirs() {
|
||||||
|
mkdir -p "$DCRUP_TC" "$DCRUP_BIN" "$DCRUP_META"
|
||||||
|
}
|
||||||
|
|
||||||
|
active_file() { echo "$DCRUP_META/active"; }
|
||||||
|
|
||||||
|
read_active() {
|
||||||
|
if [[ -f "$(active_file)" ]]; then
|
||||||
|
cat "$(active_file)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_active() {
|
||||||
|
printf '%s\n' "$1" >"$(active_file)"
|
||||||
|
}
|
||||||
|
|
||||||
|
link_default() {
|
||||||
|
local id="$1"
|
||||||
|
local src="$DCRUP_TC/$id/dcr"
|
||||||
|
[[ -x "$src" ]] || die "toolchain not installed: $id"
|
||||||
|
ln -sfn "$src" "$DCRUP_BIN/dcr"
|
||||||
|
write_active "$id"
|
||||||
|
ok "default → $id ($DCRUP_BIN/dcr)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_json() {
|
||||||
|
need_cmd curl
|
||||||
|
curl -fsSL "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_tag() {
|
||||||
|
printf '%s' "$1" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
json_asset_url() {
|
||||||
|
local json="$1" asset="$2"
|
||||||
|
printf '%s' "$json" | sed -n "s#.*\"browser_download_url\"[[:space:]]*:[[:space:]]*\"\([^\"]*/${asset}\)\"#\1#p" | head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_stable_json() {
|
||||||
|
fetch_json "$API_LATEST"
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_dev_json() {
|
||||||
|
need_cmd curl
|
||||||
|
local json result
|
||||||
|
json="$(fetch_json "$API_ALL")"
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
result="$(printf '%s' "$json" | jq -e -c '[.[] | select(.prerelease == true)][0]')" || true
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
result="$(printf '%s' "$json" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
rels = json.load(sys.stdin)
|
||||||
|
pre = [r for r in rels if r.get("prerelease")]
|
||||||
|
print(json.dumps(pre[0]) if pre else "", end="")
|
||||||
|
')"
|
||||||
|
else
|
||||||
|
die "dev channel needs jq or python3 to parse GitHub API"
|
||||||
|
fi
|
||||||
|
[[ -n "$result" && "$result" != "null" ]] || die "no prerelease (dev) found"
|
||||||
|
printf '%s' "$result"
|
||||||
|
}
|
||||||
|
|
||||||
|
release_json_for_spec() {
|
||||||
|
if [[ "$SPEC_FLOATING" -eq 1 ]]; then
|
||||||
|
case "$SPEC_CHANNEL" in
|
||||||
|
stable) latest_stable_json ;;
|
||||||
|
dev) latest_dev_json ;;
|
||||||
|
night) die "night has no GitHub release binary" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
fetch_json "https://api.github.com/repos/dexoron/dcr/releases/tags/v${SPEC_VERSION}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_prebuilt() {
|
||||||
|
local triple asset tag version url json id dest
|
||||||
|
need_cmd curl
|
||||||
|
triple="$(detect_triple)"
|
||||||
|
json="$(release_json_for_spec)"
|
||||||
|
tag="$(json_tag "$json")"
|
||||||
|
[[ -n "$tag" ]] || die "failed to parse release tag"
|
||||||
|
version="$(normalize_version "$tag")"
|
||||||
|
asset="dcr-${triple}-${version}"
|
||||||
|
case "$triple" in
|
||||||
|
*windows*) asset="${asset}.exe" ;;
|
||||||
|
esac
|
||||||
|
url="$(json_asset_url "$json" "$asset")"
|
||||||
|
[[ -n "$url" ]] || die "asset not found: $asset (tag $tag)"
|
||||||
|
|
||||||
|
id="$(toolchain_id "$version")"
|
||||||
|
dest="$DCRUP_TC/$id"
|
||||||
|
if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
|
||||||
|
ok "already installed: $id"
|
||||||
|
link_default "$id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "downloading $asset …"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
curl -fL "$url" -o "$dest/dcr"
|
||||||
|
chmod +x "$dest/dcr"
|
||||||
|
printf '%s\n' \
|
||||||
|
"channel=$SPEC_CHANNEL" \
|
||||||
|
"version=$version" \
|
||||||
|
"tag=$tag" \
|
||||||
|
"source=release" \
|
||||||
|
"triple=$triple" \
|
||||||
|
"libc=${DCRUP_LIBC:-gnu}" \
|
||||||
|
>"$dest/dcrup-meta"
|
||||||
|
ok "installed prebuilt $id ($triple)"
|
||||||
|
link_default "$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_build() {
|
||||||
|
local tmp id dest sha version branch_or_tag
|
||||||
|
need_cmd git
|
||||||
|
need_cmd cargo
|
||||||
|
|
||||||
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/dcrup.XXXXXX")"
|
||||||
|
cleanup_tmp() { rm -rf "$tmp"; }
|
||||||
|
trap cleanup_tmp EXIT
|
||||||
|
|
||||||
|
if [[ "$SPEC_CHANNEL" == "night" ]]; then
|
||||||
|
log "cloning $REPO_URL (branch dev) for night …"
|
||||||
|
git clone --depth 1 --branch dev "$REPO_URL" "$tmp/src" 2>/dev/null \
|
||||||
|
|| git clone --depth 1 "$REPO_URL" "$tmp/src"
|
||||||
|
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
|
||||||
|
version="night"
|
||||||
|
id="$(toolchain_id "$sha")"
|
||||||
|
elif [[ "$SPEC_FLOATING" -eq 1 ]]; then
|
||||||
|
local json tag
|
||||||
|
json="$(release_json_for_spec)"
|
||||||
|
tag="$(json_tag "$json")"
|
||||||
|
[[ -n "$tag" ]] || die "failed to resolve floating channel tag"
|
||||||
|
version="$(normalize_version "$tag")"
|
||||||
|
id="$(toolchain_id "$version")"
|
||||||
|
log "cloning $REPO_URL @ $tag …"
|
||||||
|
git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
|
||||||
|
|| {
|
||||||
|
git clone --depth 1 "$REPO_URL" "$tmp/src"
|
||||||
|
git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
|
||||||
|
git -C "$tmp/src" checkout "$tag"
|
||||||
|
}
|
||||||
|
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
|
||||||
|
else
|
||||||
|
version="$SPEC_VERSION"
|
||||||
|
id="$(toolchain_id)"
|
||||||
|
local tag="v${SPEC_VERSION}"
|
||||||
|
log "cloning $REPO_URL @ $tag …"
|
||||||
|
git clone --depth 1 --branch "$tag" "$REPO_URL" "$tmp/src" 2>/dev/null \
|
||||||
|
|| {
|
||||||
|
git clone --depth 1 "$REPO_URL" "$tmp/src"
|
||||||
|
git -C "$tmp/src" fetch --depth 1 origin "refs/tags/${tag}:refs/tags/${tag}"
|
||||||
|
git -C "$tmp/src" checkout "$tag"
|
||||||
|
}
|
||||||
|
sha="$(git -C "$tmp/src" rev-parse --short HEAD)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dest="$DCRUP_TC/$id"
|
||||||
|
if [[ -x "$dest/dcr" && "${FORCE:-0}" != "1" ]]; then
|
||||||
|
ok "already installed: $id"
|
||||||
|
link_default "$id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "building release (features: $FEATURES) …"
|
||||||
|
(
|
||||||
|
cd "$tmp/src"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
cargo build --release --features $FEATURES
|
||||||
|
)
|
||||||
|
mkdir -p "$dest"
|
||||||
|
cp "$tmp/src/target/release/dcr" "$dest/dcr"
|
||||||
|
chmod +x "$dest/dcr"
|
||||||
|
printf '%s\n' "channel=$SPEC_CHANNEL" "version=$version" "git=$sha" "source=build" "features=$FEATURES" >"$dest/dcrup-meta"
|
||||||
|
ok "installed build $id"
|
||||||
|
link_default "$id"
|
||||||
|
trap - EXIT
|
||||||
|
cleanup_tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_install() {
|
||||||
|
local mode="release" FORCE=0
|
||||||
|
local spec=""
|
||||||
|
local libc_cli=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--build) mode="build"; shift ;;
|
||||||
|
--release) mode="release"; shift ;;
|
||||||
|
--force) FORCE=1; shift ;;
|
||||||
|
--libc)
|
||||||
|
[[ $# -ge 2 ]] || die "--libc needs gnu|musl|auto"
|
||||||
|
libc_cli="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--libc=*)
|
||||||
|
libc_cli="${1#--libc=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
-*) die "unknown flag: $1" ;;
|
||||||
|
*)
|
||||||
|
[[ -z "$spec" ]] || die "unexpected argument: $1"
|
||||||
|
spec="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[[ -n "$spec" ]] || die "usage: dcrup install <spec> [--build|--release] [--force] [--libc gnu|musl|auto]"
|
||||||
|
parse_spec "$spec"
|
||||||
|
ensure_dirs
|
||||||
|
|
||||||
|
if [[ -n "$libc_cli" ]]; then
|
||||||
|
case "$libc_cli" in
|
||||||
|
gnu|glibc|musl|auto) DCRUP_LIBC="$libc_cli" ;;
|
||||||
|
*) die "invalid --libc: $libc_cli (use gnu|musl|auto)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
export DCRUP_LIBC="${DCRUP_LIBC:-gnu}"
|
||||||
|
|
||||||
|
if [[ "$SPEC_CHANNEL" == "night" ]]; then
|
||||||
|
mode="build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export FORCE
|
||||||
|
log "install spec=$SPEC_RAW channel=$SPEC_CHANNEL version=${SPEC_VERSION:-∅} mode=$mode libc=$DCRUP_LIBC"
|
||||||
|
if [[ "$mode" == "build" ]]; then
|
||||||
|
install_build
|
||||||
|
else
|
||||||
|
install_prebuilt
|
||||||
|
fi
|
||||||
|
check_path
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_default() {
|
||||||
|
[[ $# -eq 1 ]] || die "usage: dcrup default <spec>"
|
||||||
|
parse_spec "$1"
|
||||||
|
ensure_dirs
|
||||||
|
local id
|
||||||
|
if [[ "$SPEC_CHANNEL" == "night" || "$SPEC_FLOATING" -eq 1 ]]; then
|
||||||
|
id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
|
||||||
|
[[ -n "$id" ]] || die "no installed toolchain for channel $SPEC_CHANNEL (run: dcrup install $SPEC_CHANNEL)"
|
||||||
|
else
|
||||||
|
id="$(toolchain_id)"
|
||||||
|
[[ -d "$DCRUP_TC/$id" ]] || die "not installed: $id (run: dcrup install $1)"
|
||||||
|
fi
|
||||||
|
link_default "$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_update() {
|
||||||
|
ensure_dirs
|
||||||
|
local active
|
||||||
|
active="$(read_active)"
|
||||||
|
[[ -n "$active" ]] || die "no active toolchain (run: dcrup install stable)"
|
||||||
|
case "$active" in
|
||||||
|
night-*)
|
||||||
|
log "updating night (rebuild HEAD) …"
|
||||||
|
FORCE=1 cmd_install night --build --force
|
||||||
|
;;
|
||||||
|
stable-*|dev-*)
|
||||||
|
local ch="${active%%-*}"
|
||||||
|
log "updating floating channel $ch …"
|
||||||
|
FORCE=1 cmd_install "$ch" --force
|
||||||
|
;;
|
||||||
|
*-stable|*-dev)
|
||||||
|
ok "pinned toolchain $active — not floating; install a newer version explicitly"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
warn "unknown active id $active; try: dcrup install stable"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_list() {
|
||||||
|
ensure_dirs
|
||||||
|
local active
|
||||||
|
active="$(read_active)"
|
||||||
|
if [[ ! -d "$DCRUP_TC" ]] || [[ -z "$(ls -A "$DCRUP_TC" 2>/dev/null || true)" ]]; then
|
||||||
|
warn "no toolchains installed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local d base
|
||||||
|
for d in "$DCRUP_TC"/*; do
|
||||||
|
[[ -d "$d" ]] || continue
|
||||||
|
base="$(basename "$d")"
|
||||||
|
if [[ "$base" == "$active" ]]; then
|
||||||
|
printf ' * %s\n' "$base"
|
||||||
|
else
|
||||||
|
printf ' %s\n' "$base"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_show() {
|
||||||
|
ensure_dirs
|
||||||
|
local active
|
||||||
|
active="$(read_active)"
|
||||||
|
echo "DCRUP_HOME=$DCRUP_HOME"
|
||||||
|
echo "DCRUP_BIN=$DCRUP_BIN"
|
||||||
|
echo "active=${active:-none}"
|
||||||
|
if [[ -n "$active" && -f "$DCRUP_TC/$active/dcrup-meta" ]]; then
|
||||||
|
echo "--- meta ---"
|
||||||
|
cat "$DCRUP_TC/$active/dcrup-meta"
|
||||||
|
fi
|
||||||
|
if [[ -x "$DCRUP_BIN/dcr" ]]; then
|
||||||
|
echo "--- dcr --version ---"
|
||||||
|
"$DCRUP_BIN/dcr" --version 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_which() {
|
||||||
|
if [[ -L "$DCRUP_BIN/dcr" || -e "$DCRUP_BIN/dcr" ]]; then
|
||||||
|
readlink -f "$DCRUP_BIN/dcr" 2>/dev/null || readlink "$DCRUP_BIN/dcr" 2>/dev/null || echo "$DCRUP_BIN/dcr"
|
||||||
|
else
|
||||||
|
die "dcr not linked (run: dcrup install stable)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_uninstall() {
|
||||||
|
ensure_dirs
|
||||||
|
if [[ "${1:-}" == "--all" ]]; then
|
||||||
|
rm -rf "$DCRUP_TC"
|
||||||
|
rm -f "$DCRUP_BIN/dcr" "$(active_file)"
|
||||||
|
ok "removed all toolchains"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[[ $# -eq 1 ]] || die "usage: dcrup uninstall <spec>|--all"
|
||||||
|
parse_spec "$1"
|
||||||
|
local id
|
||||||
|
if [[ "$SPEC_FLOATING" -eq 1 || "$SPEC_CHANNEL" == "night" ]]; then
|
||||||
|
id="$(ls -1 "$DCRUP_TC" 2>/dev/null | grep -E "^${SPEC_CHANNEL}-" | sort | tail -n1 || true)"
|
||||||
|
else
|
||||||
|
id="$(toolchain_id)"
|
||||||
|
fi
|
||||||
|
[[ -n "$id" && -d "$DCRUP_TC/$id" ]] || die "not installed: $1"
|
||||||
|
rm -rf "$DCRUP_TC/$id"
|
||||||
|
if [[ "$(read_active)" == "$id" ]]; then
|
||||||
|
rm -f "$DCRUP_BIN/dcr" "$(active_file)"
|
||||||
|
warn "removed active toolchain; run: dcrup install stable"
|
||||||
|
fi
|
||||||
|
ok "removed $id"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_path() {
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":$DCRUP_BIN:"*) ;;
|
||||||
|
*)
|
||||||
|
warn "$DCRUP_BIN is not in PATH"
|
||||||
|
echo "Add to ~/.bashrc / ~/.zshrc / ~/.profile:"
|
||||||
|
echo " export PATH=\"$DCRUP_BIN:\$PATH\""
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_self_install() {
|
||||||
|
local dest="$DCRUP_BIN"
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--to) dest="$2"; shift 2 ;;
|
||||||
|
*) die "usage: dcrup self-install [--to DIR]" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
ensure_dirs
|
||||||
|
mkdir -p "$dest"
|
||||||
|
local self
|
||||||
|
self="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
||||||
|
cp "$self" "$dest/dcrup"
|
||||||
|
chmod +x "$dest/dcrup"
|
||||||
|
ok "dcrup installed to $dest/dcrup"
|
||||||
|
check_path
|
||||||
|
echo "Then: dcrup install stable"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd="${1:-}"
|
||||||
|
shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
""|-h|--help|help) usage ;;
|
||||||
|
install) cmd_install "$@" ;;
|
||||||
|
default) cmd_default "$@" ;;
|
||||||
|
update) cmd_update "$@" ;;
|
||||||
|
list) cmd_list "$@" ;;
|
||||||
|
show) cmd_show "$@" ;;
|
||||||
|
which) cmd_which "$@" ;;
|
||||||
|
uninstall) cmd_uninstall "$@" ;;
|
||||||
|
self-install) cmd_self_install "$@" ;;
|
||||||
|
*) die "unknown command: $cmd (try: dcrup help)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -8,8 +8,10 @@ sidebar_label: Introduction
|
|||||||
<summary>Quick reference</summary>
|
<summary>Quick reference</summary>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Installation
|
# Installation (dcrup)
|
||||||
curl -fsSL https://dcr.dexoron.su/install.sh | sh
|
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
|
||||||
|
export PATH="$HOME/.dcr/bin:$PATH"
|
||||||
|
dcrup install stable
|
||||||
|
|
||||||
# Create a project
|
# Create a project
|
||||||
dcr new my-app
|
dcr new my-app
|
||||||
|
|||||||
@@ -4,6 +4,28 @@ sidebar_label: Changelog
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.8.3 (2026-08-03)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`dcr run -- <args>...`** — cargo-style argument forwarding after a bare `--`
|
||||||
|
(`dcr run --release -- --test_help`). Applies to direct binary runs and `[run].cmd`.
|
||||||
|
- **Tests** — host target section `ldflags` without CLI `--target`; run-arg forwarding; `split_double_dash` unit tests.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Native builds resolve the host triple** — without `--target` / package `build.target`, host sections such as
|
||||||
|
`[build.x86_64-unknown-linux-gnu]` apply again (`cflags` / `ldflags` / toolchain).
|
||||||
|
Previously the target was empty and those sections were ignored while artifacts already used the host triple path.
|
||||||
|
- **Empty target** — no more `Unknown target ''` warning.
|
||||||
|
- **`dcr run --help` / man `dcr-run`** — document `--` forwarding.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Native artifact paths on Windows and macOS** — without an explicit `--target` / `build.target`, artifacts use `target/<profile>`, so `dcr run` and flat-bin find the build output.
|
||||||
|
- Target-specific `ldflags` missing on native host builds (e.g. micro-lang + `libsct-elf`).
|
||||||
|
- Spurious empty-target warning.
|
||||||
|
|
||||||
## 0.8.2 (2026-07-22)
|
## 0.8.2 (2026-07-22)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -4,25 +4,64 @@ sidebar_label: Installation
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
## Linux (Dexoron Packages)
|
## dcrup (recommended)
|
||||||
|
|
||||||
Configure your package manager at https://pkg.dexoron.su and install:
|
Install and switch DCR versions (stable / dev / night, pin `0.8.2`, optional build from source).
|
||||||
|
|
||||||
|
### Linux / macOS / BSD / Windows (bash)
|
||||||
|
|
||||||
**Arch Linux**
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -Sy dcr
|
curl -fsSL https://sh.dcr-tool.ru | sh -s -- self-install
|
||||||
|
export PATH="$HOME/.dcr/bin:$PATH"
|
||||||
|
dcrup install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
**Debian/Ubuntu**
|
Non-interactive install of DCR in one shot:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt update && sudo apt install dcr
|
curl -fsSL https://sh.dcr-tool.ru | sh -s -- install stable
|
||||||
|
export PATH="$HOME/.dcr/bin:$PATH"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fedora/RHEL**
|
### Windows (PowerShell)
|
||||||
```sh
|
|
||||||
sudo dnf install dcr
|
```powershell
|
||||||
|
irm https://ps1.dcr-tool.ru | iex
|
||||||
|
# or download then run:
|
||||||
|
# irm https://ps1.dcr-tool.ru -OutFile dcrup.ps1
|
||||||
|
# powershell -File .\dcrup.ps1 self-install
|
||||||
|
# $env:Path += ";$env:USERPROFILE\.dcr\bin"
|
||||||
|
dcrup install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optional cmd bootstrap (if you prefer `curl` of the cmd shim):
|
||||||
|
|
||||||
|
```bat
|
||||||
|
curl -fsSL -o dcrup.cmd https://cmd.dcr-tool.ru
|
||||||
|
```
|
||||||
|
|
||||||
|
After `self-install`, the command is **`dcrup`** (no `.sh` / `.ps1`): shims live in `~/.dcr/bin` (Unix) or `%USERPROFILE%\.dcr\bin` (Windows `dcrup.cmd`).
|
||||||
|
|
||||||
|
### Common dcrup commands
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dcrup install stable # latest stable prebuilt
|
||||||
|
dcrup install 0.8.2 # pin → 0.8.2@stable
|
||||||
|
dcrup install 0.8.2@dev
|
||||||
|
dcrup install stable --libc musl # Linux: musl asset (default: gnu)
|
||||||
|
dcrup install stable --build # cargo build --features archive
|
||||||
|
dcrup install night # always build from branch dev HEAD
|
||||||
|
dcrup default 0.8.2
|
||||||
|
dcrup update
|
||||||
|
dcrup list
|
||||||
|
dcrup show
|
||||||
|
dcrup which
|
||||||
|
```
|
||||||
|
|
||||||
|
Layout: `~/.dcr/toolchains/<id>/dcr` and `~/.dcr/bin/dcr` → active version.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Arch Linux (AUR)
|
## Arch Linux (AUR)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -42,11 +81,12 @@ brew install dcr
|
|||||||
sudo snap install dcrup
|
sudo snap install dcrup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> If classic Snap Store publishing is unavailable, install the `.snap` from [GitHub Releases](https://github.com/dexoron/dcr/releases/latest) with `--dangerous`.
|
||||||
|
|
||||||
## Nix (flake)
|
## Nix (flake)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix run github:dexoron/dcr
|
nix run github:dexoron/dcr
|
||||||
# or install to profile:
|
|
||||||
nix profile install github:dexoron/dcr
|
nix profile install github:dexoron/dcr
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,48 +96,37 @@ nix profile install github:dexoron/dcr
|
|||||||
cargo install dcr
|
cargo install dcr
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linux / macOS (script)
|
Note: crates.io builds may omit optional features. For FAT disk images (`[archive]`), prefer release binaries or:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -fsSL https://dcr.dexoron.su/install.sh | sh
|
cargo install dcr --features archive
|
||||||
```
|
|
||||||
|
|
||||||
## BSD (script)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Windows (PowerShell)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://dcr.dexoron.su/install.ps1 | iex
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## From source
|
## From source
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/dexoron/dcr.git
|
git clone https://github.com/dexoron/dcr.git
|
||||||
cd dcr && cargo build --release
|
cd dcr
|
||||||
|
cargo build --release --features archive
|
||||||
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
|
||||||
|
# or manage versions with dcrup install night / --build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Post-install
|
## Post-install
|
||||||
|
|
||||||
Verify the installation:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dcr --version
|
dcr --version
|
||||||
|
dcrup show # if installed via dcrup
|
||||||
```
|
```
|
||||||
|
|
||||||
Man pages are available after installation:
|
Man pages (package installs / release assets):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
man dcr
|
man dcr
|
||||||
man dcr-build
|
man dcr-build
|
||||||
```
|
```
|
||||||
|
|
||||||
To use the registry, create `~/.dcr/config.toml`:
|
Registry (optional) — `~/.dcr/config.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[registry.default]
|
[registry.default]
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ dcr-run \- Build and run a DCR project
|
|||||||
[\fB\-\-force\fR]
|
[\fB\-\-force\fR]
|
||||||
[\fB\-\-clean\fR]
|
[\fB\-\-clean\fR]
|
||||||
[\fB\-\-verbose\fR]
|
[\fB\-\-verbose\fR]
|
||||||
|
[\fB\-\-\fR \fI<args>...\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Builds and runs the project. Only available for projects with
|
Builds and runs the project. Only available for projects with
|
||||||
\fBkind = "bin"\fR.
|
\fBkind = "bin"\fR.
|
||||||
|
Arguments after a bare
|
||||||
|
.B \-\-
|
||||||
|
are forwarded to the built binary (cargo-style).
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
.B \-\-debug
|
.B \-\-debug
|
||||||
@@ -30,6 +34,14 @@ Clean before building.
|
|||||||
.TP
|
.TP
|
||||||
.B \-\-verbose
|
.B \-\-verbose
|
||||||
Print detailed build output.
|
Print detailed build output.
|
||||||
|
.TP
|
||||||
|
.B \-\- \fI<args>...\fR
|
||||||
|
Arguments passed to the built binary.
|
||||||
|
.SH EXAMPLES
|
||||||
|
.nf
|
||||||
|
dcr run --release -- --test_help
|
||||||
|
dcr run -- --foo bar
|
||||||
|
.fi
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR dcr (1),
|
.BR dcr (1),
|
||||||
.BR dcr-build (1)
|
.BR dcr-build (1)
|
||||||
|
|||||||
@@ -125,17 +125,27 @@ fn parse_clean_flags(args: &[String]) -> Result<CleanFlags, String> {
|
|||||||
fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
|
fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
|
||||||
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
|
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let target = flags
|
let target = flags.target.clone().or_else(|| {
|
||||||
.target
|
|
||||||
.clone()
|
|
||||||
.or_else(|| {
|
|
||||||
config
|
config
|
||||||
.get("build.target")
|
.get("build.target")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|s| s.trim().to_string())
|
.map(|s| s.trim().to_string())
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
})
|
});
|
||||||
.or_else(|| Some(default_target_triple()));
|
let target = match target {
|
||||||
|
Some(t) => Some(crate::utils::build::normalize_target_os(&t).to_string()),
|
||||||
|
None if cfg!(target_os = "linux")
|
||||||
|
|| cfg!(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
)) =>
|
||||||
|
{
|
||||||
|
Some(default_target_triple())
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
if flags.all
|
if flags.all
|
||||||
&& let Some(workspace) = parse_workspace(
|
&& let Some(workspace) = parse_workspace(
|
||||||
|
|||||||
@@ -25,16 +25,25 @@ pub struct BuildRunFlags {
|
|||||||
pub force: bool,
|
pub force: bool,
|
||||||
pub clean: bool,
|
pub clean: bool,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
pub bin_args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_double_dash(args: &[String]) -> (&[String], &[String]) {
|
||||||
|
match args.iter().position(|a| a == "--") {
|
||||||
|
Some(i) => (&args[..i], &args[i + 1..]),
|
||||||
|
None => (args, &[]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
||||||
|
let (dcr_args, bin_args_slice) = split_double_dash(args);
|
||||||
let mut profile = PROFILE.to_string();
|
let mut profile = PROFILE.to_string();
|
||||||
let mut target = None;
|
let mut target = None;
|
||||||
let mut workspace = None;
|
let mut workspace = None;
|
||||||
let mut force = false;
|
let mut force = false;
|
||||||
let mut clean = false;
|
let mut clean = false;
|
||||||
let mut verbose = false;
|
let mut verbose = false;
|
||||||
let mut iter = args.iter();
|
let mut iter = dcr_args.iter();
|
||||||
|
|
||||||
while let Some(arg) = iter.next() {
|
while let Some(arg) = iter.next() {
|
||||||
if !arg.starts_with("--") {
|
if !arg.starts_with("--") {
|
||||||
@@ -91,5 +100,38 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
|
|||||||
force,
|
force,
|
||||||
clean,
|
clean,
|
||||||
verbose,
|
verbose,
|
||||||
|
bin_args: bin_args_slice.to_vec(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn s(args: &[&str]) -> Vec<String> {
|
||||||
|
args.iter().map(|a| a.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_double_dash_empty_tail() {
|
||||||
|
let args = s(&["--release", "--force"]);
|
||||||
|
let (left, right) = split_double_dash(&args);
|
||||||
|
assert_eq!(left, &s(&["--release", "--force"]));
|
||||||
|
assert!(right.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_double_dash_with_bin_args() {
|
||||||
|
let args = s(&["--release", "--", "--test_help", "x"]);
|
||||||
|
let (left, right) = split_double_dash(&args);
|
||||||
|
assert_eq!(left, &s(&["--release"]));
|
||||||
|
assert_eq!(right, &s(&["--test_help", "x"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_run_flags_forwards_after_double_dash() {
|
||||||
|
let flags = parse_build_run_flags(&s(&["--release", "--", "--test_help"])).unwrap();
|
||||||
|
assert_eq!(flags.profile, "release");
|
||||||
|
assert_eq!(flags.bin_args, s(&["--test_help"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,12 +59,13 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
if args.first().is_some_and(|a| a == "--help") {
|
if args.first().is_some_and(|a| a == "--help") {
|
||||||
printc("USAGE:", BOLD_GREEN);
|
printc("USAGE:", BOLD_GREEN);
|
||||||
printc(
|
printc(
|
||||||
" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose]",
|
" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose] [-- <args>...]",
|
||||||
BOLD_CYAN,
|
BOLD_CYAN,
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
printc("DESCRIPTION:", BOLD_GREEN);
|
printc("DESCRIPTION:", BOLD_GREEN);
|
||||||
println!(" Builds and runs the project. Only available for kind = \"bin\".");
|
println!(" Builds and runs the project. Only available for kind = \"bin\".");
|
||||||
|
println!(" Arguments after `--` are passed to the built binary (cargo-style).");
|
||||||
println!();
|
println!();
|
||||||
printc("OPTIONS:", BOLD_GREEN);
|
printc("OPTIONS:", BOLD_GREEN);
|
||||||
println!(" --debug Run with debug profile (default)");
|
println!(" --debug Run with debug profile (default)");
|
||||||
@@ -73,6 +74,7 @@ pub fn run(args: &[String]) -> i32 {
|
|||||||
println!(" --force Force a full rebuild");
|
println!(" --force Force a full rebuild");
|
||||||
println!(" --clean Clean before building");
|
println!(" --clean Clean before building");
|
||||||
println!(" --verbose Print detailed build output");
|
println!(" --verbose Print detailed build output");
|
||||||
|
println!(" -- <args>... Arguments forwarded to the binary");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,12 +114,13 @@ 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 {
|
||||||
|
let display = display_run_cmd(&cmd, &flags.bin_args);
|
||||||
println!(
|
println!(
|
||||||
" {} {}",
|
" {} {}",
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
||||||
cmd
|
display
|
||||||
);
|
);
|
||||||
return run_shell(&cmd);
|
return run_shell_with_args(&cmd, &flags.bin_args);
|
||||||
}
|
}
|
||||||
return build_status;
|
return build_status;
|
||||||
}
|
}
|
||||||
@@ -250,32 +253,36 @@ fn run_project(
|
|||||||
);
|
);
|
||||||
if build_status == 0 {
|
if build_status == 0 {
|
||||||
if let Some(cmd) = run_cmd {
|
if let Some(cmd) = run_cmd {
|
||||||
|
let display = display_run_cmd(&cmd, &flags.bin_args);
|
||||||
println!(
|
println!(
|
||||||
" {} {}",
|
" {} {}",
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
||||||
cmd
|
display
|
||||||
);
|
);
|
||||||
return Ok(run_shell(&cmd));
|
return Ok(run_shell_with_args(&cmd, &flags.bin_args));
|
||||||
}
|
}
|
||||||
|
let display = display_bin_run(&bin_path, &flags.bin_args);
|
||||||
println!(
|
println!(
|
||||||
" {} {}",
|
" {} {}",
|
||||||
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
colored(&format!("{:<9}", "run"), BOLD_GREEN),
|
||||||
bin_path
|
display
|
||||||
);
|
);
|
||||||
return Ok(run_binary(
|
return Ok(run_binary(
|
||||||
project_name,
|
project_name,
|
||||||
&flags.profile,
|
&flags.profile,
|
||||||
normalized_target_dir.as_deref(),
|
normalized_target_dir.as_deref(),
|
||||||
|
&flags.bin_args,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallback_code = if let Some(cmd) = run_cmd {
|
let fallback_code = if let Some(cmd) = run_cmd {
|
||||||
run_shell(&cmd)
|
run_shell_with_args(&cmd, &flags.bin_args)
|
||||||
} else {
|
} else {
|
||||||
run_binary(
|
run_binary(
|
||||||
project_name,
|
project_name,
|
||||||
&flags.profile,
|
&flags.profile,
|
||||||
normalized_target_dir.as_deref(),
|
normalized_target_dir.as_deref(),
|
||||||
|
&flags.bin_args,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
if fallback_code != 1 {
|
if fallback_code != 1 {
|
||||||
@@ -345,6 +352,62 @@ fn run_shell(cmd: &str) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_shell_with_args(cmd: &str, bin_args: &[String]) -> i32 {
|
||||||
|
if bin_args.is_empty() {
|
||||||
|
return run_shell(cmd);
|
||||||
|
}
|
||||||
|
let mut full = cmd.to_string();
|
||||||
|
for arg in bin_args {
|
||||||
|
full.push(' ');
|
||||||
|
full.push_str(&shell_escape(arg));
|
||||||
|
}
|
||||||
|
run_shell(&full)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_run_cmd(cmd: &str, bin_args: &[String]) -> String {
|
||||||
|
if bin_args.is_empty() {
|
||||||
|
return cmd.to_string();
|
||||||
|
}
|
||||||
|
let mut display = cmd.to_string();
|
||||||
|
for arg in bin_args {
|
||||||
|
display.push(' ');
|
||||||
|
display.push_str(arg);
|
||||||
|
}
|
||||||
|
display
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_bin_run(bin_path: &str, bin_args: &[String]) -> String {
|
||||||
|
if bin_args.is_empty() {
|
||||||
|
return bin_path.to_string();
|
||||||
|
}
|
||||||
|
let mut display = bin_path.to_string();
|
||||||
|
for arg in bin_args {
|
||||||
|
display.push(' ');
|
||||||
|
display.push_str(arg);
|
||||||
|
}
|
||||||
|
display
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shell_escape(arg: &str) -> String {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
// cmd.exe: wrap in double quotes and escape embedded quotes.
|
||||||
|
let escaped = arg.replace('"', "\\\"");
|
||||||
|
format!("\"{escaped}\"")
|
||||||
|
} else {
|
||||||
|
// POSIX sh: single-quote and escape embedded single quotes as '\''.
|
||||||
|
let mut out = String::from("'");
|
||||||
|
for ch in arg.chars() {
|
||||||
|
if ch == '\'' {
|
||||||
|
out.push_str("'\\''");
|
||||||
|
} else {
|
||||||
|
out.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push('\'');
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String {
|
fn substitute_run_vars(cmd: &str, profile: &str, version: &str) -> String {
|
||||||
let info = parse_version_info(version);
|
let info = parse_version_info(version);
|
||||||
substitute_vars(cmd, &info, profile, "")
|
substitute_vars(cmd, &info, profile, "")
|
||||||
|
|||||||
@@ -160,14 +160,12 @@ pub(crate) fn archive_static(
|
|||||||
return Ok(common::elapsed_secs(start_time));
|
return Ok(common::elapsed_secs(start_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
|
let archiver = ctx
|
||||||
"lib"
|
.archiver
|
||||||
} else {
|
.unwrap_or_else(|| default_archiver(ctx.compiler));
|
||||||
"ar"
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut cmd = Command::new(archiver);
|
let mut cmd = Command::new(archiver);
|
||||||
if cfg!(target_os = "windows") && archiver == "lib" {
|
if cfg!(target_os = "windows") && is_msvc_lib(archiver) {
|
||||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||||
} else {
|
} else {
|
||||||
cmd.arg("rcs").arg(&lib_path);
|
cmd.arg("rcs").arg(&lib_path);
|
||||||
@@ -243,6 +241,26 @@ pub(crate) fn link_binary(
|
|||||||
run(ctx, cmd, start_time)
|
run(ctx, cmd, start_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_archiver(compiler: &str) -> &'static str {
|
||||||
|
let c = compiler.to_lowercase();
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
if c.contains("cl") && !c.contains("clang") {
|
||||||
|
"lib"
|
||||||
|
} else if c.contains("clang") {
|
||||||
|
"llvm-ar"
|
||||||
|
} else {
|
||||||
|
"ar"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"ar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_msvc_lib(archiver: &str) -> bool {
|
||||||
|
let a = archiver.to_lowercase();
|
||||||
|
a == "lib" || a == "lib.exe" || a.ends_with("\\lib.exe") || a.ends_with("/lib.exe")
|
||||||
|
}
|
||||||
|
|
||||||
fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64, String> {
|
fn run(ctx: &BuildContext, mut cmd: Command, start_time: Instant) -> Result<f64, String> {
|
||||||
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);
|
||||||
|
|||||||
@@ -60,8 +60,13 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
|||||||
let elapsed = common::elapsed_secs(start_time);
|
let elapsed = common::elapsed_secs(start_time);
|
||||||
return Ok(elapsed);
|
return Ok(elapsed);
|
||||||
}
|
}
|
||||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("lib"));
|
let archiver = ctx.archiver.unwrap_or("lib");
|
||||||
|
let mut cmd = Command::new(archiver);
|
||||||
|
if archiver == "lib" || archiver.eq_ignore_ascii_case("lib.exe") {
|
||||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||||
|
} else {
|
||||||
|
cmd.arg("rcs").arg(&lib_path);
|
||||||
|
}
|
||||||
for obj in &objects {
|
for obj in &objects {
|
||||||
cmd.arg(obj);
|
cmd.arg(obj);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,26 @@ pub fn is_excluded(path: &Path, exclude_dirs: &[PathBuf], include_paths: &[Strin
|
|||||||
if matches_patterns(path, include_paths, false) {
|
if matches_patterns(path, include_paths, false) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
exclude_dirs.iter().any(|dir| path.starts_with(dir))
|
exclude_dirs.iter().any(|dir| path_is_under(path, dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_is_under(path: &Path, prefix: &Path) -> bool {
|
||||||
|
if path.starts_with(prefix) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match (fs::canonicalize(path), fs::canonicalize(prefix)) {
|
||||||
|
(Ok(p), Ok(pref)) => p.starts_with(pref),
|
||||||
|
_ => {
|
||||||
|
let p = path.to_string_lossy().replace('\\', "/");
|
||||||
|
let pref = prefix.to_string_lossy().replace('\\', "/");
|
||||||
|
if p == pref || p.starts_with(&format!("{pref}/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let p = p.strip_prefix("/private").unwrap_or(&p);
|
||||||
|
let pref = pref.strip_prefix("/private").unwrap_or(&pref);
|
||||||
|
p == pref || p.starts_with(&format!("{pref}/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_patterns(path: &Path, patterns: &[String], positive: bool) -> bool {
|
fn matches_patterns(path: &Path, patterns: &[String], positive: bool) -> bool {
|
||||||
@@ -379,7 +398,7 @@ pub fn object_path(obj_dir: &Path, source: &str, obj_ext: &str) -> String {
|
|||||||
} else {
|
} else {
|
||||||
out.set_extension(obj_ext);
|
out.set_extension(obj_ext);
|
||||||
}
|
}
|
||||||
out.to_string_lossy().to_string()
|
out.to_string_lossy().replace('\\', "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn needs_rebuild(source: &str, object: &str) -> bool {
|
pub fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||||
@@ -482,7 +501,11 @@ fn parse_d_file(content: &str) -> Vec<String> {
|
|||||||
}
|
}
|
||||||
in_escape = false;
|
in_escape = false;
|
||||||
} else if c == '\\' {
|
} else if c == '\\' {
|
||||||
in_escape = true;
|
match chars.peek() {
|
||||||
|
Some('\n' | '\r' | ' ' | '\t' | '\\') => in_escape = true,
|
||||||
|
Some(_) => current_path.push('\\'),
|
||||||
|
None => current_path.push('\\'),
|
||||||
|
}
|
||||||
} else if c.is_whitespace() {
|
} else if c.is_whitespace() {
|
||||||
if !current_path.is_empty() {
|
if !current_path.is_empty() {
|
||||||
deps.push(current_path.clone());
|
deps.push(current_path.clone());
|
||||||
@@ -509,12 +532,18 @@ mod tests {
|
|||||||
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
static CWD_LOCK: Mutex<()> = Mutex::new(());
|
static CWD_LOCK: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
|
fn cwd_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||||
|
CWD_LOCK
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
fn temp_dir(prefix: &str) -> PathBuf {
|
fn temp_dir(prefix: &str) -> PathBuf {
|
||||||
let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
let n = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
let dir = std::env::temp_dir().join(format!("dcr_test_{prefix}_{n}"));
|
let dir = std::env::temp_dir().join(format!("dcr_test_{prefix}_{n}"));
|
||||||
let _ = fs::remove_dir_all(&dir);
|
let _ = fs::remove_dir_all(&dir);
|
||||||
fs::create_dir_all(&dir).unwrap();
|
fs::create_dir_all(&dir).unwrap();
|
||||||
dir
|
fs::canonicalize(&dir).unwrap_or(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_roots() -> Vec<PathBuf> {
|
fn default_roots() -> Vec<PathBuf> {
|
||||||
@@ -679,7 +708,7 @@ mod tests {
|
|||||||
fs::write(src.join("utils.c"), "").unwrap();
|
fs::write(src.join("utils.c"), "").unwrap();
|
||||||
fs::write(src.join("README.md"), "").unwrap(); // should be ignored
|
fs::write(src.join("README.md"), "").unwrap(); // should be ignored
|
||||||
|
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let include: Vec<String> = Vec::new();
|
let include: Vec<String> = Vec::new();
|
||||||
@@ -703,7 +732,7 @@ mod tests {
|
|||||||
fs::write(src.join("other.cc"), "").unwrap();
|
fs::write(src.join("other.cc"), "").unwrap();
|
||||||
fs::write(src.join("skip.c"), "").unwrap(); // should be ignored
|
fs::write(src.join("skip.c"), "").unwrap(); // should be ignored
|
||||||
|
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let include: Vec<String> = Vec::new();
|
let include: Vec<String> = Vec::new();
|
||||||
@@ -721,7 +750,7 @@ mod tests {
|
|||||||
let src = dir.join("src");
|
let src = dir.join("src");
|
||||||
fs::create_dir_all(&src).unwrap();
|
fs::create_dir_all(&src).unwrap();
|
||||||
|
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let include: Vec<String> = Vec::new();
|
let include: Vec<String> = Vec::new();
|
||||||
@@ -743,7 +772,7 @@ mod tests {
|
|||||||
fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded
|
fs::write(vendor.join("lib.c"), "").unwrap(); // should be excluded
|
||||||
|
|
||||||
let exclude = vec![vendor.clone()];
|
let exclude = vec![vendor.clone()];
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let include: Vec<String> = Vec::new();
|
let include: Vec<String> = Vec::new();
|
||||||
@@ -765,7 +794,7 @@ mod tests {
|
|||||||
fs::write(src.join("main.c"), "").unwrap();
|
fs::write(src.join("main.c"), "").unwrap();
|
||||||
fs::write(sub.join("nested.c"), "").unwrap();
|
fs::write(sub.join("nested.c"), "").unwrap();
|
||||||
|
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let include: Vec<String> = Vec::new();
|
let include: Vec<String> = Vec::new();
|
||||||
@@ -789,7 +818,7 @@ mod tests {
|
|||||||
|
|
||||||
let exclude = vec![boot.clone()];
|
let exclude = vec![boot.clone()];
|
||||||
let include = vec!["src/boot/arch/**".to_string()];
|
let include = vec!["src/boot/arch/**".to_string()];
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let roots = default_roots();
|
let roots = default_roots();
|
||||||
@@ -816,7 +845,7 @@ mod tests {
|
|||||||
"!src/legacy/**".to_string(),
|
"!src/legacy/**".to_string(),
|
||||||
"src/legacy/allow/**".to_string(),
|
"src/legacy/allow/**".to_string(),
|
||||||
];
|
];
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let roots = default_roots();
|
let roots = default_roots();
|
||||||
@@ -839,7 +868,7 @@ mod tests {
|
|||||||
|
|
||||||
let exclude: Vec<PathBuf> = Vec::new();
|
let exclude: Vec<PathBuf> = Vec::new();
|
||||||
let include = vec!["!src/gen/**".to_string()];
|
let include = vec!["!src/gen/**".to_string()];
|
||||||
let _guard = CWD_LOCK.lock().unwrap();
|
let _guard = cwd_lock();
|
||||||
let prev = std::env::current_dir().unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::env::set_current_dir(&dir).unwrap();
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
let roots = default_roots();
|
let roots = default_roots();
|
||||||
@@ -874,36 +903,44 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn needs_rebuild_header_modified() {
|
fn needs_rebuild_header_modified() {
|
||||||
let dir = temp_dir("rebuild_header");
|
let dir = temp_dir("rebuild_header");
|
||||||
let src = dir.join("test.c");
|
fs::write(dir.join("test.c"), "int main() {}").unwrap();
|
||||||
let header = dir.join("test.h");
|
fs::write(dir.join("test.h"), "#define A 1").unwrap();
|
||||||
let obj = dir.join("test.o");
|
fs::write(dir.join("test.o"), "").unwrap();
|
||||||
let d_file = dir.join("test.d");
|
|
||||||
|
|
||||||
fs::write(&src, "int main() {}").unwrap();
|
let _guard = cwd_lock();
|
||||||
fs::write(&header, "#define A 1").unwrap();
|
let prev = std::env::current_dir().unwrap();
|
||||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
std::env::set_current_dir(&dir).unwrap();
|
||||||
fs::write(&obj, "").unwrap();
|
|
||||||
|
|
||||||
let d_content = format!(
|
|
||||||
"{}: {} {}",
|
|
||||||
obj.to_string_lossy(),
|
|
||||||
src.to_string_lossy(),
|
|
||||||
header.to_string_lossy()
|
|
||||||
);
|
|
||||||
fs::write(&d_file, d_content).unwrap();
|
|
||||||
|
|
||||||
|
fs::write("test.d", "test.o: test.c missing.h").unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
!needs_rebuild(&src.to_string_lossy(), &obj.to_string_lossy()),
|
needs_rebuild("test.c", "test.o"),
|
||||||
"should be fresh"
|
"missing dep in .d must force rebuild"
|
||||||
);
|
);
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
fs::write("test.h", "#define A 1").unwrap();
|
||||||
fs::write(&header, "#define A 2").unwrap(); // modify header
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
fs::write("test.o", "obj").unwrap();
|
||||||
|
fs::write("test.d", "test.o: test.c test.h").unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
needs_rebuild(&src.to_string_lossy(), &obj.to_string_lossy()),
|
!needs_rebuild("test.c", "test.o"),
|
||||||
|
"object newer than source and headers should be fresh"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||||
|
fs::write("test.h", "#define A 2").unwrap();
|
||||||
|
assert!(
|
||||||
|
needs_rebuild("test.c", "test.o"),
|
||||||
"changed header should trigger rebuild"
|
"changed header should trigger rebuild"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
std::env::set_current_dir(prev).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_d_file_keeps_windows_path_separators() {
|
||||||
|
let content = r"target\obj\main.obj: src\main.c src\utils.h";
|
||||||
|
let deps = parse_d_file(content);
|
||||||
|
assert_eq!(deps, vec![r"src\main.c", r"src\utils.h"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ use crate::core::build_config::Config;
|
|||||||
use crate::core::deps::{register, resolve_deps};
|
use crate::core::deps::{register, resolve_deps};
|
||||||
use crate::core::workspace::parse_workspace;
|
use crate::core::workspace::parse_workspace;
|
||||||
use crate::utils::build::{
|
use crate::utils::build::{
|
||||||
get_bool_with_profile, get_config_opt, get_config_str, get_language_with_profile,
|
default_target_triple, get_bool_with_profile, get_config_opt, get_config_str,
|
||||||
is_bare_metal_target, normalize_kind, normalize_platform, normalize_target,
|
get_language_with_profile, is_bare_metal_target, normalize_kind, normalize_platform,
|
||||||
normalize_target_os, parse_version_info, prepend_clang_target_flag, primary_language,
|
normalize_target, normalize_target_os, parse_version_info, prepend_clang_target_flag,
|
||||||
resolve_compiler, resolve_pkg_config_flags, resolve_tool, substitute_vars,
|
primary_language, resolve_compiler, resolve_pkg_config_flags, resolve_tool, substitute_vars,
|
||||||
};
|
};
|
||||||
use crate::utils::fs::check_dir;
|
use crate::utils::fs::check_dir;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -159,6 +159,7 @@ fn build_all(
|
|||||||
&member.path,
|
&member.path,
|
||||||
profile,
|
profile,
|
||||||
target,
|
target,
|
||||||
|
target.is_some_and(|t| !t.trim().is_empty()),
|
||||||
&[],
|
&[],
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -186,22 +187,34 @@ fn build_all(
|
|||||||
let project_name = get_config_str(&config, "package.name");
|
let project_name = get_config_str(&config, "package.name");
|
||||||
let project_version = get_config_str(&config, "package.version");
|
let project_version = get_config_str(&config, "package.version");
|
||||||
|
|
||||||
let targets_to_build: Vec<Option<String>> = if let Some(t) = target {
|
let targets_to_build: Vec<(Option<String>, bool)> = if let Some(t) = target {
|
||||||
vec![Some(normalize_target_os(t).to_string())]
|
let normalized = normalize_target_os(t);
|
||||||
|
if normalized.is_empty() {
|
||||||
|
vec![(Some(default_target_triple()), true)]
|
||||||
|
} else {
|
||||||
|
vec![(Some(normalized.to_string()), true)]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let config_targets = get_targets(&config, profile)?;
|
let config_targets = get_targets(&config, profile)?;
|
||||||
if config_targets.is_empty() {
|
if config_targets.is_empty() {
|
||||||
vec![None]
|
vec![(Some(default_target_triple()), false)]
|
||||||
} else {
|
} else {
|
||||||
config_targets
|
config_targets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| Some(normalize_target_os(&t).to_string()))
|
.map(|t| {
|
||||||
|
let normalized = normalize_target_os(&t);
|
||||||
|
if normalized.is_empty() {
|
||||||
|
(Some(default_target_triple()), true)
|
||||||
|
} else {
|
||||||
|
(Some(normalized.to_string()), true)
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
for (i, build_target) in targets_to_build.iter().enumerate() {
|
for (i, (build_target, explicit_target)) in targets_to_build.iter().enumerate() {
|
||||||
if cancel.load(Ordering::SeqCst) {
|
if cancel.load(Ordering::SeqCst) {
|
||||||
return Err("Build interrupted".to_string());
|
return Err("Build interrupted".to_string());
|
||||||
}
|
}
|
||||||
@@ -226,6 +239,7 @@ fn build_all(
|
|||||||
&member.path,
|
&member.path,
|
||||||
profile,
|
profile,
|
||||||
build_target.as_deref(),
|
build_target.as_deref(),
|
||||||
|
*explicit_target,
|
||||||
&[],
|
&[],
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -239,6 +253,7 @@ fn build_all(
|
|||||||
&ws,
|
&ws,
|
||||||
profile,
|
profile,
|
||||||
build_target.as_deref(),
|
build_target.as_deref(),
|
||||||
|
*explicit_target,
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
Some(root),
|
Some(root),
|
||||||
@@ -251,6 +266,7 @@ fn build_all(
|
|||||||
root,
|
root,
|
||||||
profile,
|
profile,
|
||||||
build_target.as_deref(),
|
build_target.as_deref(),
|
||||||
|
*explicit_target,
|
||||||
&excludes,
|
&excludes,
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -264,6 +280,7 @@ fn build_all(
|
|||||||
root,
|
root,
|
||||||
profile,
|
profile,
|
||||||
build_target.as_deref(),
|
build_target.as_deref(),
|
||||||
|
*explicit_target,
|
||||||
&[],
|
&[],
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -283,6 +300,7 @@ fn build_workspace(
|
|||||||
workspace: &crate::core::workspace::Workspace,
|
workspace: &crate::core::workspace::Workspace,
|
||||||
profile: &str,
|
profile: &str,
|
||||||
target: Option<&str>,
|
target: Option<&str>,
|
||||||
|
explicit_target: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
workspace_root: Option<&Path>,
|
workspace_root: Option<&Path>,
|
||||||
@@ -294,6 +312,7 @@ fn build_workspace(
|
|||||||
&member.path,
|
&member.path,
|
||||||
profile,
|
profile,
|
||||||
target,
|
target,
|
||||||
|
explicit_target,
|
||||||
&[],
|
&[],
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -310,6 +329,7 @@ fn build_project_at(
|
|||||||
project_root: &Path,
|
project_root: &Path,
|
||||||
profile: &str,
|
profile: &str,
|
||||||
target: Option<&str>,
|
target: Option<&str>,
|
||||||
|
explicit_target: bool,
|
||||||
exclude_dirs: &[std::path::PathBuf],
|
exclude_dirs: &[std::path::PathBuf],
|
||||||
force: bool,
|
force: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
@@ -344,12 +364,43 @@ fn build_project_at(
|
|||||||
}
|
}
|
||||||
let project_name = get_config_str(&config, "package.name");
|
let project_name = get_config_str(&config, "package.name");
|
||||||
let project_version = get_config_str(&config, "package.version");
|
let project_version = get_config_str(&config, "package.version");
|
||||||
|
let host_target = default_target_triple();
|
||||||
let build_target_config = get_build_string_with_profile(&config, "target", profile);
|
let build_target_config = get_build_string_with_profile(&config, "target", profile);
|
||||||
let build_target = target.or(if build_target_config.is_empty() {
|
let has_explicit_target = explicit_target || !build_target_config.is_empty();
|
||||||
|
let explicit_build_target = explicit_target
|
||||||
|
.then_some(target)
|
||||||
|
.flatten()
|
||||||
|
.filter(|t| !t.trim().is_empty())
|
||||||
|
.map(|t| {
|
||||||
|
let normalized = normalize_target_os(t);
|
||||||
|
if normalized.is_empty() {
|
||||||
|
host_target.clone()
|
||||||
|
} else {
|
||||||
|
normalized.to_string()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let configured_build_target = || {
|
||||||
|
if build_target_config.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(build_target_config.as_str())
|
let normalized = normalize_target_os(&build_target_config);
|
||||||
});
|
if normalized.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(normalized.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let implicit_build_target = || {
|
||||||
|
target
|
||||||
|
.filter(|t| !t.trim().is_empty())
|
||||||
|
.map(|t| normalize_target_os(t).to_string())
|
||||||
|
};
|
||||||
|
let build_target_owned = explicit_build_target
|
||||||
|
.or_else(configured_build_target)
|
||||||
|
.or_else(implicit_build_target)
|
||||||
|
.unwrap_or(host_target);
|
||||||
|
let build_target = Some(build_target_owned.as_str());
|
||||||
let build_language = get_language_with_profile(&config, profile)?;
|
let build_language = get_language_with_profile(&config, profile)?;
|
||||||
let build_qt = get_bool_with_profile(&config, "qt", profile, false);
|
let build_qt = get_bool_with_profile(&config, "qt", profile, false);
|
||||||
let lang_key = primary_language(&build_language);
|
let lang_key = primary_language(&build_language);
|
||||||
@@ -530,9 +581,11 @@ fn build_project_at(
|
|||||||
root.join(&rel).to_string_lossy().to_string()
|
root.join(&rel).to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let base_dir =
|
let base_dir = if has_explicit_target {
|
||||||
if let Some(rel) = normalize_target(build_target.unwrap_or(""), profile) {
|
PathBuf::from(
|
||||||
PathBuf::from(rel)
|
normalize_target(&build_target_owned, profile)
|
||||||
|
.expect("explicit build target must be non-empty"),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let default_dir = if cfg!(target_os = "linux") {
|
let default_dir = if cfg!(target_os = "linux") {
|
||||||
let arch = std::env::consts::ARCH;
|
let arch = std::env::consts::ARCH;
|
||||||
@@ -694,6 +747,7 @@ fn build_project_at(
|
|||||||
&dep_root,
|
&dep_root,
|
||||||
profile,
|
profile,
|
||||||
build_target,
|
build_target,
|
||||||
|
has_explicit_target,
|
||||||
&[],
|
&[],
|
||||||
force,
|
force,
|
||||||
verbose,
|
verbose,
|
||||||
@@ -950,7 +1004,8 @@ fn absolutize_flag_path(flag: &str, project_root: &Path, prefixes: &[&str]) -> S
|
|||||||
return flag.to_string();
|
return flag.to_string();
|
||||||
}
|
}
|
||||||
let abs = project_root.join(rest);
|
let abs = project_root.join(rest);
|
||||||
return format!("{prefix}{}", abs.to_string_lossy());
|
let path = abs.to_string_lossy().replace('\\', "/");
|
||||||
|
return format!("{prefix}{path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag.to_string()
|
flag.to_string()
|
||||||
|
|||||||
@@ -242,10 +242,13 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_nanos()
|
.as_nanos()
|
||||||
));
|
));
|
||||||
std::fs::create_dir_all(root.join("target/lib")).unwrap();
|
std::fs::create_dir_all(root.join("target").join("lib")).unwrap();
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
push_default_lib_dirs(&mut paths, &root);
|
push_default_lib_dirs(&mut paths, &root);
|
||||||
assert!(paths.iter().any(|p| p.ends_with("target/lib")));
|
assert!(paths.iter().any(|p| {
|
||||||
|
let n = p.replace('\\', "/");
|
||||||
|
n.ends_with("target/lib")
|
||||||
|
}));
|
||||||
let _ = std::fs::remove_dir_all(root);
|
let _ = std::fs::remove_dir_all(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,12 +85,7 @@ pub fn package_root_from_registry_info(pkg_info: &JsonValue) -> Result<PathBuf,
|
|||||||
return Err("Registry package path is empty".to_string());
|
return Err("Registry package path is empty".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = Path::new(raw_path);
|
let full_path = registry_path_to_pathbuf(raw_path.trim());
|
||||||
let full_path = if path.is_absolute() {
|
|
||||||
path.to_path_buf()
|
|
||||||
} else {
|
|
||||||
get_registry_cache_root().join(path)
|
|
||||||
};
|
|
||||||
|
|
||||||
if full_path.file_name().and_then(|v| v.to_str()) == Some("dcr.toml") {
|
if full_path.file_name().and_then(|v| v.to_str()) == Some("dcr.toml") {
|
||||||
return full_path
|
return full_path
|
||||||
@@ -102,6 +97,39 @@ pub fn package_root_from_registry_info(pkg_info: &JsonValue) -> Result<PathBuf,
|
|||||||
Ok(full_path)
|
Ok(full_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn registry_path_to_pathbuf(raw: &str) -> PathBuf {
|
||||||
|
let s = raw.trim();
|
||||||
|
if let Some(rest) = s.strip_prefix("file://") {
|
||||||
|
if rest.len() >= 3 && rest.as_bytes()[0] == b'/' && rest.as_bytes()[2] == b':' {
|
||||||
|
return PathBuf::from(&rest[1..]);
|
||||||
|
}
|
||||||
|
if rest.len() >= 2 && rest.as_bytes()[1] == b':' {
|
||||||
|
return PathBuf::from(rest);
|
||||||
|
}
|
||||||
|
if rest.starts_with('/') {
|
||||||
|
return PathBuf::from(rest);
|
||||||
|
}
|
||||||
|
return PathBuf::from(format!("/{rest}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = s.replace('\\', "/");
|
||||||
|
if let Some(rest) = s.strip_prefix("//?/") {
|
||||||
|
s = rest.to_string();
|
||||||
|
} else if let Some(rest) = s.strip_prefix("//./") {
|
||||||
|
s = rest.to_string();
|
||||||
|
}
|
||||||
|
if s.len() >= 3 && s.as_bytes()[0] == b'/' && s.as_bytes()[2] == b':' {
|
||||||
|
s = s[1..].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = PathBuf::from(&s);
|
||||||
|
if path.is_absolute() || (s.len() >= 2 && s.as_bytes()[1] == b':') || s.starts_with('/') {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
get_registry_cache_root().join(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn registry_include_dir(dep_root: &Path) -> PathBuf {
|
pub fn registry_include_dir(dep_root: &Path) -> PathBuf {
|
||||||
dep_root.join("target").join("include")
|
dep_root.join("target").join("include")
|
||||||
}
|
}
|
||||||
@@ -219,16 +247,44 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn registry_package_root_accepts_package_dir_or_manifest_path() {
|
fn registry_package_root_accepts_package_dir_or_manifest_path() {
|
||||||
let pkg = serde_json::json!({ "path": "/tmp/pkg" });
|
let root = std::env::temp_dir().join(format!("dcr_reg_pkg_{}", std::process::id()));
|
||||||
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
|
std::fs::create_dir_all(&root).unwrap();
|
||||||
|
let root_str = root.to_string_lossy().replace('\\', "/");
|
||||||
|
|
||||||
|
let pkg = serde_json::json!({ "path": root_str });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
package_root_from_registry_info(&pkg).unwrap(),
|
package_root_from_registry_info(&pkg)
|
||||||
PathBuf::from("/tmp/pkg")
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.replace('\\', "/"),
|
||||||
|
root_str
|
||||||
);
|
);
|
||||||
|
|
||||||
let pkg = serde_json::json!({ "path": "/tmp/pkg/dcr.toml" });
|
let manifest = format!("{root_str}/dcr.toml");
|
||||||
|
let pkg = serde_json::json!({ "path": manifest });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
package_root_from_registry_info(&pkg).unwrap(),
|
package_root_from_registry_info(&pkg)
|
||||||
PathBuf::from("/tmp/pkg")
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.replace('\\', "/"),
|
||||||
|
root_str
|
||||||
);
|
);
|
||||||
|
let _ = std::fs::remove_dir_all(&root);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn registry_path_strips_windows_extended_prefix() {
|
||||||
|
let p = registry_path_to_pathbuf("//?/D:/cache/mylib");
|
||||||
|
let s = p.to_string_lossy().replace('\\', "/");
|
||||||
|
assert_eq!(s, "D:/cache/mylib");
|
||||||
|
|
||||||
|
let p2 = registry_path_to_pathbuf("/D:/cache/mylib");
|
||||||
|
let s2 = p2.to_string_lossy().replace('\\', "/");
|
||||||
|
assert_eq!(s2, "D:/cache/mylib");
|
||||||
|
|
||||||
|
let p3 = registry_path_to_pathbuf("file:///D:/cache/mylib");
|
||||||
|
let s3 = p3.to_string_lossy().replace('\\', "/");
|
||||||
|
assert_eq!(s3, "D:/cache/mylib");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ use crate::platform;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 {
|
pub fn run_binary(
|
||||||
|
project_name: &str,
|
||||||
|
profile: &str,
|
||||||
|
target_dir: Option<&str>,
|
||||||
|
bin_args: &[String],
|
||||||
|
) -> i32 {
|
||||||
let bin_path = platform::bin_path(profile, project_name, target_dir);
|
let bin_path = platform::bin_path(profile, project_name, target_dir);
|
||||||
if Path::new(&bin_path).exists() {
|
if Path::new(&bin_path).exists() {
|
||||||
let status = Command::new(&bin_path).status();
|
let status = Command::new(&bin_path).args(bin_args).status();
|
||||||
match status {
|
match status {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
return s.code().unwrap_or(0);
|
return s.code().unwrap_or(0);
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ pub fn normalize_platform(platform: &str) -> Option<&str> {
|
|||||||
|
|
||||||
pub fn normalize_target_os(target: &str) -> &str {
|
pub fn normalize_target_os(target: &str) -> &str {
|
||||||
match target {
|
match target {
|
||||||
|
"" => "",
|
||||||
"linux" => "x86_64-unknown-linux-gnu",
|
"linux" => "x86_64-unknown-linux-gnu",
|
||||||
"macos" => "x86_64-apple-darwin",
|
"macos" => "x86_64-apple-darwin",
|
||||||
"windows" => "x86_64-pc-windows-msvc",
|
"windows" => "x86_64-pc-windows-msvc",
|
||||||
@@ -515,6 +516,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_target_short_names() {
|
fn normalize_target_short_names() {
|
||||||
|
assert_eq!(normalize_target_os(""), "");
|
||||||
assert_eq!(normalize_target_os("linux"), "x86_64-unknown-linux-gnu");
|
assert_eq!(normalize_target_os("linux"), "x86_64-unknown-linux-gnu");
|
||||||
assert_eq!(normalize_target_os("macos"), "x86_64-apple-darwin");
|
assert_eq!(normalize_target_os("macos"), "x86_64-apple-darwin");
|
||||||
assert_eq!(normalize_target_os("windows"), "x86_64-pc-windows-msvc");
|
assert_eq!(normalize_target_os("windows"), "x86_64-pc-windows-msvc");
|
||||||
|
|||||||
@@ -87,16 +87,46 @@ fn build_run_clean_flags_normal_project() {
|
|||||||
"dcr run should start"
|
"dcr run should start"
|
||||||
);
|
);
|
||||||
|
|
||||||
let out = run_dcr_env(&["clean", "--release"], &dir, &envs);
|
std::fs::write(
|
||||||
assert!(out.status.success(), "dcr clean --release should succeed");
|
dir.join("src/main.c"),
|
||||||
let target_dir = "target/x86_64-unknown-linux-gnu".to_string();
|
r#"#include <stdio.h>
|
||||||
assert!(
|
int main(int argc, char **argv) {
|
||||||
!dir.join(&target_dir).join("release").exists(),
|
for (int i = 1; i < argc; i++) {
|
||||||
"target/x86_64-unknown-linux-gnu/release should be removed"
|
printf("ARG:%s\n", argv[i]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("rewrite main.c");
|
||||||
|
let out = run_dcr_env(&["run", "--force", "--", "--test_help", "foo"], &dir, &envs);
|
||||||
|
let combined = format!(
|
||||||
|
"{}{}",
|
||||||
|
String::from_utf8_lossy(&out.stdout),
|
||||||
|
String::from_utf8_lossy(&out.stderr)
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
dir.join(&target_dir).join("debug").is_dir(),
|
out.status.success(),
|
||||||
"target/x86_64-unknown-linux-gnu/debug should remain"
|
"dcr run -- <args> should succeed:\n{combined}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
combined.contains("ARG:--test_help") && combined.contains("ARG:foo"),
|
||||||
|
"binary should receive args after `--`:\n{combined}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = run_dcr_env(&["clean", "--release"], &dir, &envs);
|
||||||
|
assert!(out.status.success(), "dcr clean --release should succeed");
|
||||||
|
let release_dir = host_profile_dir(&dir, "release");
|
||||||
|
let debug_dir = host_profile_dir(&dir, "debug");
|
||||||
|
assert!(
|
||||||
|
!release_dir.exists(),
|
||||||
|
"release profile dir should be removed: {}",
|
||||||
|
release_dir.display()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
debug_dir.is_dir(),
|
||||||
|
"debug profile dir should remain: {}",
|
||||||
|
debug_dir.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
let out = run_dcr_env(&["clean"], &dir, &envs);
|
let out = run_dcr_env(&["clean"], &dir, &envs);
|
||||||
@@ -123,16 +153,27 @@ fn build_with_target_config() {
|
|||||||
|
|
||||||
let envs = [("DCR_COMPILER", compiler)];
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
let out = run_dcr_env(&["build"], &dir, &envs);
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||||
|
if !out.status.success() {
|
||||||
|
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
||||||
|
eprintln!("stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
if cfg!(windows) {
|
||||||
|
eprintln!("skipping linux-target path assert: build failed (no linux cross toolchain)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
out.status.success(),
|
out.status.success(),
|
||||||
"dcr build with target = \"linux\" should succeed"
|
"dcr build with target = \"linux\" should succeed"
|
||||||
);
|
);
|
||||||
|
|
||||||
let artifact = default_artifact_path(&dir, &project_name);
|
let out_dir = dir
|
||||||
assert!(
|
.join("target")
|
||||||
artifact.is_file(),
|
.join("x86_64-unknown-linux-gnu")
|
||||||
"artifact should be at default path target/x86_64-unknown-linux-gnu/debug/{}",
|
.join("debug");
|
||||||
project_name
|
assert_artifact_in(
|
||||||
|
&out_dir,
|
||||||
|
&project_name,
|
||||||
|
"build.target=linux should place artifact under target/x86_64-unknown-linux-gnu/debug",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +205,10 @@ fn build_with_out_dir() {
|
|||||||
"dcr build with out_dir should succeed"
|
"dcr build with out_dir should succeed"
|
||||||
);
|
);
|
||||||
|
|
||||||
let artifact = dir.join("_BUILD").join(&project_name);
|
assert_artifact_in(
|
||||||
assert!(
|
&dir.join("_BUILD"),
|
||||||
artifact.is_file(),
|
&project_name,
|
||||||
"artifact should be at _BUILD/{} (custom out_dir)",
|
"artifact should be under custom out_dir _BUILD",
|
||||||
project_name
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let default_path = default_artifact_path(&dir, &project_name);
|
let default_path = default_artifact_path(&dir, &project_name);
|
||||||
@@ -225,15 +265,12 @@ roots = ["src"]
|
|||||||
assert!(out.status.success(), "flat-bin nasm build should succeed");
|
assert!(out.status.success(), "flat-bin nasm build should succeed");
|
||||||
|
|
||||||
let candidates = [
|
let candidates = [
|
||||||
|
host_profile_dir(&dir, "debug").join("boot.bin"),
|
||||||
dir.join("target/debug/boot.bin"),
|
dir.join("target/debug/boot.bin"),
|
||||||
dir.join("target").join(format!(
|
|
||||||
"{}-unknown-linux-gnu/debug/boot.bin",
|
|
||||||
std::env::consts::ARCH
|
|
||||||
)),
|
|
||||||
];
|
];
|
||||||
assert!(
|
assert!(
|
||||||
candidates.iter().any(|p| p.is_file()),
|
candidates.iter().any(|p| p.is_file()),
|
||||||
"expected boot.bin under target/…/debug, candidates: {candidates:?}"
|
"expected boot.bin under host profile dir, candidates: {candidates:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,13 +293,77 @@ fn package_build_target_used_without_cli_flag() {
|
|||||||
|
|
||||||
let envs = [("DCR_COMPILER", compiler)];
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
let out = run_dcr_env(&["build"], &dir, &envs);
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||||
|
if !out.status.success() {
|
||||||
|
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
||||||
|
eprintln!("stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
if cfg!(windows) {
|
||||||
|
eprintln!("skipping linux-target path assert: build failed (no linux cross toolchain)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
assert!(out.status.success(), "build should use package target");
|
assert!(out.status.success(), "build should use package target");
|
||||||
|
|
||||||
let artifact = dir
|
let out_dir = dir
|
||||||
.join("target/x86_64-unknown-linux-gnu/debug")
|
.join("target")
|
||||||
.join(&project_name);
|
.join("x86_64-unknown-linux-gnu")
|
||||||
assert!(
|
.join("debug");
|
||||||
artifact.is_file(),
|
assert_artifact_in(
|
||||||
"artifact should be under package build.target path"
|
&out_dir,
|
||||||
|
&project_name,
|
||||||
|
"artifact should be under package build.target path",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_target_section_ldflags_apply_without_cli_target() {
|
||||||
|
let Some(compiler) = available_compiler() else {
|
||||||
|
eprintln!("no compiler found; skipping host target ldflags test");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dir = unique_sandbox_dir("host_ldflags");
|
||||||
|
let out = run_dcr(&["init"], &dir);
|
||||||
|
assert!(out.status.success(), "dcr init should succeed");
|
||||||
|
|
||||||
|
let host = if cfg!(target_os = "linux") {
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
let env = if cfg!(target_env = "musl") {
|
||||||
|
"musl"
|
||||||
|
} else {
|
||||||
|
"gnu"
|
||||||
|
};
|
||||||
|
format!("{arch}-unknown-linux-{env}")
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
format!("{}-apple-darwin", std::env::consts::ARCH)
|
||||||
|
} else if cfg!(target_os = "windows") {
|
||||||
|
let env = if cfg!(target_env = "gnu") {
|
||||||
|
"gnu"
|
||||||
|
} else {
|
||||||
|
"msvc"
|
||||||
|
};
|
||||||
|
format!("{}-pc-windows-{env}", std::env::consts::ARCH)
|
||||||
|
} else {
|
||||||
|
eprintln!("unsupported host for host_target_section test; skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let toml_path = dir.join("dcr.toml");
|
||||||
|
let toml = std::fs::read_to_string(&toml_path).expect("read dcr.toml");
|
||||||
|
let updated = format!(
|
||||||
|
"{toml}\n[build.{host}]\nldflags = [\"-Wl,-rpath,/nonexistent-dcr-host-ldflags\"]\n"
|
||||||
|
);
|
||||||
|
std::fs::write(&toml_path, updated).expect("write dcr.toml");
|
||||||
|
|
||||||
|
let envs = [("DCR_COMPILER", compiler), ("DCR_DEBUG", "1")];
|
||||||
|
let out = run_dcr_env(&["build", "--force"], &dir, &envs);
|
||||||
|
let combined = format!(
|
||||||
|
"{}{}",
|
||||||
|
String::from_utf8_lossy(&out.stdout),
|
||||||
|
String::from_utf8_lossy(&out.stderr)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
combined.contains("-Wl,-rpath,/nonexistent-dcr-host-ldflags")
|
||||||
|
|| combined.contains("rpath,/nonexistent-dcr-host-ldflags"),
|
||||||
|
"host target section ldflags must be applied on native build:\n{combined}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,12 +117,24 @@ fn registry_dependency_is_built_from_cache() {
|
|||||||
"[registry.local]\nurl = \"file://local\"\npriority = 1\n",
|
"[registry.local]\nurl = \"file://local\"\npriority = 1\n",
|
||||||
)
|
)
|
||||||
.expect("failed to write registry config");
|
.expect("failed to write registry config");
|
||||||
|
let dep_abs = dep.canonicalize().unwrap_or(dep.clone());
|
||||||
|
let mut dep_path = dep_abs.to_string_lossy().replace('\\', "/");
|
||||||
|
if let Some(rest) = dep_path.strip_prefix("//?/") {
|
||||||
|
dep_path = rest.to_string();
|
||||||
|
}
|
||||||
|
if dep_path.len() >= 3 && dep_path.as_bytes()[0] == b'/' && dep_path.as_bytes()[2] == b':' {
|
||||||
|
dep_path = dep_path[1..].to_string();
|
||||||
|
}
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
dcr_home.join("index.json"),
|
dcr_home.join("index.json"),
|
||||||
format!(
|
serde_json::json!({
|
||||||
"{{\"packages\":[{{\"name\":\"mylib\",\"latest_version\":\"0.1.0\",\"path\":\"{}\"}}]}}",
|
"packages": [{
|
||||||
dep.display()
|
"name": "mylib",
|
||||||
),
|
"latest_version": "0.1.0",
|
||||||
|
"path": dep_path
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
)
|
)
|
||||||
.expect("failed to write registry index");
|
.expect("failed to write registry index");
|
||||||
|
|
||||||
@@ -151,11 +163,16 @@ fn registry_dependency_is_built_from_cache() {
|
|||||||
.expect("failed to write app source");
|
.expect("failed to write app source");
|
||||||
|
|
||||||
let index_path = dcr_home.join("index.json");
|
let index_path = dcr_home.join("index.json");
|
||||||
let envs = [
|
let home_s = home.to_string_lossy().to_string();
|
||||||
|
let index_s = index_path.to_string_lossy().to_string();
|
||||||
|
let mut envs = vec![
|
||||||
("DCR_COMPILER", compiler),
|
("DCR_COMPILER", compiler),
|
||||||
("HOME", home.to_str().unwrap()),
|
("HOME", home_s.as_str()),
|
||||||
("DCR_INDEX_PATH", index_path.to_str().unwrap()),
|
("DCR_INDEX_PATH", index_s.as_str()),
|
||||||
];
|
];
|
||||||
|
if cfg!(windows) {
|
||||||
|
envs.push(("USERPROFILE", home_s.as_str()));
|
||||||
|
}
|
||||||
let out = run_dcr_env(&["build"], &app, &envs);
|
let out = run_dcr_env(&["build"], &app, &envs);
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
||||||
|
|||||||
@@ -74,6 +74,32 @@ fn available_compiler() -> Option<&'static str> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn host_profile_dir(project_root: &Path, profile: &str) -> PathBuf {
|
||||||
|
let target = project_root.join("target");
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
let env = if cfg!(target_env = "musl") {
|
||||||
|
"musl"
|
||||||
|
} else {
|
||||||
|
"gnu"
|
||||||
|
};
|
||||||
|
target
|
||||||
|
.join(format!("{arch}-unknown-linux-{env}"))
|
||||||
|
.join(profile)
|
||||||
|
} else if cfg!(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
)) {
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
let os = std::env::consts::OS;
|
||||||
|
target.join(format!("{arch}-unknown-{os}")).join(profile)
|
||||||
|
} else {
|
||||||
|
target.join(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Tests ---
|
// --- Tests ---
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -223,25 +249,24 @@ fn clean_specific_profile() {
|
|||||||
let out = run_dcr(&["init"], &dir);
|
let out = run_dcr(&["init"], &dir);
|
||||||
assert!(out.status.success(), "init should succeed");
|
assert!(out.status.success(), "init should succeed");
|
||||||
|
|
||||||
// Create target/x86_64-unknown-linux-gnu/debug and release dirs
|
let debug_dir = host_profile_dir(&dir, "debug");
|
||||||
let target_base = "target/x86_64-unknown-linux-gnu";
|
let release_dir = host_profile_dir(&dir, "release");
|
||||||
std::fs::create_dir_all(dir.join(target_base).join("debug")).expect("create debug");
|
std::fs::create_dir_all(&debug_dir).expect("create debug");
|
||||||
std::fs::write(dir.join(target_base).join("debug").join("dummy.o"), "x").expect("write");
|
std::fs::write(debug_dir.join("dummy.o"), "x").expect("write");
|
||||||
std::fs::create_dir_all(dir.join(target_base).join("release")).expect("create release");
|
std::fs::create_dir_all(&release_dir).expect("create release");
|
||||||
std::fs::write(dir.join(target_base).join("release").join("dummy.o"), "x").expect("write");
|
std::fs::write(release_dir.join("dummy.o"), "x").expect("write");
|
||||||
|
|
||||||
// Clean only release
|
|
||||||
let out = run_dcr(&["clean", "--release"], &dir);
|
let out = run_dcr(&["clean", "--release"], &dir);
|
||||||
assert!(out.status.success(), "clean --release should succeed");
|
assert!(out.status.success(), "clean --release should succeed");
|
||||||
assert!(
|
assert!(
|
||||||
!dir.join(target_base).join("release").exists(),
|
!release_dir.exists(),
|
||||||
"target/x86_64-unknown-linux-gnu/release should be removed"
|
"release profile dir should be removed: {}",
|
||||||
|
release_dir.display()
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
dir.join("target/x86_64-unknown-linux-gnu")
|
debug_dir.is_dir(),
|
||||||
.join("debug")
|
"debug profile dir should remain: {}",
|
||||||
.is_dir(),
|
debug_dir.display()
|
||||||
"target/x86_64-unknown-linux-gnu/debug should remain"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,19 +304,20 @@ fn staticlib_build() {
|
|||||||
let envs = [("DCR_COMPILER", compiler)];
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
let out = run_dcr_env(&["build"], &dir, &envs);
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||||
assert!(out.status.success(), "staticlib build should succeed");
|
assert!(out.status.success(), "staticlib build should succeed");
|
||||||
// Check that a .a file exists
|
let lib_path = host_profile_dir(&dir, "debug");
|
||||||
let lib_path = dir
|
|
||||||
.join("target")
|
|
||||||
.join("x86_64-unknown-linux-gnu")
|
|
||||||
.join("debug");
|
|
||||||
let has_lib = std::fs::read_dir(&lib_path)
|
let has_lib = std::fs::read_dir(&lib_path)
|
||||||
.map(|entries| {
|
.map(|entries| {
|
||||||
entries
|
entries.filter_map(|e| e.ok()).any(|e| {
|
||||||
.filter_map(|e| e.ok())
|
let name = e.file_name().to_string_lossy().to_string();
|
||||||
.any(|e| e.file_name().to_string_lossy().ends_with(".a"))
|
name.ends_with(".a") || name.ends_with(".lib")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
assert!(has_lib, "staticlib should produce a .a file");
|
assert!(
|
||||||
|
has_lib,
|
||||||
|
"staticlib should produce a .a/.lib in {}",
|
||||||
|
lib_path.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -307,12 +333,11 @@ fn build_release_profile() {
|
|||||||
let envs = [("DCR_COMPILER", compiler)];
|
let envs = [("DCR_COMPILER", compiler)];
|
||||||
let out = run_dcr_env(&["build", "--release"], &dir, &envs);
|
let out = run_dcr_env(&["build", "--release"], &dir, &envs);
|
||||||
assert!(out.status.success(), "release build should succeed");
|
assert!(out.status.success(), "release build should succeed");
|
||||||
|
let release_dir = host_profile_dir(&dir, "release");
|
||||||
assert!(
|
assert!(
|
||||||
dir.join("target")
|
release_dir.is_dir(),
|
||||||
.join("x86_64-unknown-linux-gnu")
|
"release profile dir should exist: {}",
|
||||||
.join("release")
|
release_dir.display()
|
||||||
.is_dir(),
|
|
||||||
"target/x86_64-unknown-linux-gnu/release should exist"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,14 +56,17 @@ fn workspace_build_and_clean_all() {
|
|||||||
"clean --all should not spam missing member target/: {combined}"
|
"clean --all should not spam missing member target/: {combined}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let target_dir = "target/x86_64-unknown-linux-gnu";
|
let release_dir = host_profile_dir(&root, "release");
|
||||||
|
let debug_dir = host_profile_dir(&root, "debug");
|
||||||
assert!(
|
assert!(
|
||||||
!root.join(target_dir).join("release").exists(),
|
!release_dir.exists(),
|
||||||
"root target/x86_64-unknown-linux-gnu/release should be removed"
|
"root release profile dir should be removed: {}",
|
||||||
|
release_dir.display()
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
root.join(target_dir).join("debug").exists(),
|
debug_dir.exists(),
|
||||||
"root target/x86_64-unknown-linux-gnu/debug should remain"
|
"root debug profile dir should remain: {}",
|
||||||
|
debug_dir.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
let out = run_dcr_env(&["clean", "--all"], &root, &envs);
|
let out = run_dcr_env(&["clean", "--all"], &root, &envs);
|
||||||
|
|||||||
@@ -92,10 +92,63 @@ pub fn parse_project_name(toml: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn default_artifact_path(project_root: &Path, project_name: &str) -> PathBuf {
|
pub fn host_profile_dir(project_root: &Path, profile: &str) -> PathBuf {
|
||||||
project_root
|
let target = project_root.join("target");
|
||||||
.join("target")
|
if cfg!(target_os = "linux") {
|
||||||
.join("x86_64-unknown-linux-gnu")
|
let arch = std::env::consts::ARCH;
|
||||||
.join("debug")
|
let env = if cfg!(target_env = "musl") {
|
||||||
.join(project_name)
|
"musl"
|
||||||
|
} else {
|
||||||
|
"gnu"
|
||||||
|
};
|
||||||
|
target
|
||||||
|
.join(format!("{arch}-unknown-linux-{env}"))
|
||||||
|
.join(profile)
|
||||||
|
} else if cfg!(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
)) {
|
||||||
|
let arch = std::env::consts::ARCH;
|
||||||
|
let os = std::env::consts::OS;
|
||||||
|
target.join(format!("{arch}-unknown-{os}")).join(profile)
|
||||||
|
} else {
|
||||||
|
target.join(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn bin_name(project_name: &str) -> String {
|
||||||
|
if cfg!(windows) {
|
||||||
|
format!("{project_name}.exe")
|
||||||
|
} else {
|
||||||
|
project_name.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn default_artifact_path(project_root: &Path, project_name: &str) -> PathBuf {
|
||||||
|
host_profile_dir(project_root, "debug").join(bin_name(project_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn artifact_candidates(dir: &Path, project_name: &str) -> Vec<PathBuf> {
|
||||||
|
let base = bin_name(project_name);
|
||||||
|
let bare = project_name.to_string();
|
||||||
|
vec![
|
||||||
|
dir.join(&base),
|
||||||
|
dir.join(&bare),
|
||||||
|
dir.join(format!("{project_name}.exe")),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn assert_artifact_in(dir: &Path, project_name: &str, ctx: &str) {
|
||||||
|
let candidates = artifact_candidates(dir, project_name);
|
||||||
|
assert!(
|
||||||
|
candidates.iter().any(|p| p.is_file()),
|
||||||
|
"{ctx}: expected binary in {}, candidates: {candidates:?}",
|
||||||
|
dir.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user