20 Commits

Author SHA1 Message Date
97db82e4f8 Merge pull request #20 from dexoron/dev
v0.7.3
2026-06-15 22:36:12 +08:00
3d596d7280 Update install docs 2026-06-15 22:35:38 +08:00
5cb4473bd1 Update README.md 2026-06-15 22:35:06 +08:00
33f955d4e3 refactor(updater): migrate from reqwest to ureq for lightweight builds 2026-06-13 16:15:24 +08:00
838be72dd2 refactor(deps): replace git2 with system git and add vcs support 2026-06-13 15:28:14 +08:00
39fad31c4f Merge pull request #18 from dexoron/master
v0.7.2
2026-06-07 22:31:43 +08:00
c8ef38a99b release: bump version to 0.7.2 and update changelog 2026-06-07 21:41:02 +08:00
8e0c535cfd fix: improve default target triple generation for various OS environments 2026-06-07 21:29:55 +08:00
de88c160ad Merge pull request #16 from XiaoPengYouCode/codex/fix-macos-host-target-link 2026-06-07 21:21:53 +08:00
4daca925a0 Merge pull request #17 from dexoron/master
master -> dev
2026-06-07 21:17:14 +08:00
595b7ac105 Merge pull request #15 from XiaoPengYouCode/codex/fix-readme-badges 2026-06-07 21:13:10 +08:00
XiaoPengYouCode
11ae3762d0 fix: use host macOS target for default builds 2026-06-07 05:41:07 +08:00
XiaoPengYouCode
4c439317e3 docs: fix README badge rendering 2026-06-07 05:09:38 +08:00
0aa3f87f99 feat: temporarily disable Snap Store publishing and update README instructions 2026-06-06 22:18:03 +08:00
a6ce0a3332 feat: enhance release notes extraction and include release name in GitHub release 2026-06-06 21:41:50 +08:00
cea43e99c8 feat: add Windows target dependencies and update README for MinGW 2026-06-06 21:39:17 +08:00
de4fb0bc29 feat: add .SRCINFO for AUR package metadata 2026-06-06 21:33:34 +08:00
59e27b6f5d Merge pull request #14 from dexoron/dev
v0.7.1 The Multi-Arch Expansion
2026-06-06 21:28:24 +08:00
c8cb155e5a feat: update help flag 2026-06-06 21:22:46 +08:00
6424aab69d feet: add license to docs and updated Cargo.toml to add build with no features flag 2026-06-06 21:19:04 +08:00
29 changed files with 839 additions and 1202 deletions

View File

@@ -41,10 +41,6 @@ on:
description: "Publish to crates.io" description: "Publish to crates.io"
type: boolean type: boolean
default: false default: false
run_snap:
description: "Publish to Snap Store (dcrup)"
type: boolean
default: false
run_brew: run_brew:
description: "Update Homebrew tap" description: "Update Homebrew tap"
type: boolean type: boolean
@@ -83,24 +79,40 @@ jobs:
notes: notes:
needs: setup needs: setup
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
release_name: ${{ steps.extract.outputs.release_name }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: ${{ needs.setup.outputs.tag }} ref: ${{ needs.setup.outputs.tag }}
- name: Prepare Release Notes - name: Prepare Release Notes
id: extract
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
tag="${{ needs.setup.outputs.version }}" tag="${{ needs.setup.outputs.version }}"
notes_file=".github/release-notes.md" notes_file=".github/release-notes.md"
if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then
# Extract release name from header: ## [0.7.1] - 2026-06-02 "Title"
release_name=$(awk -v ver="$tag" '
$0 ~ "^## \\[" ver "\\]" {
match($0, /"([^"]+)"/, arr)
if (arr[1] != "") {
print "v" ver " - " arr[1]
} else {
print "v" ver
}
}
' CHANGELOG.md)
echo "release_name=${release_name}" >> "$GITHUB_OUTPUT"
awk -v ver="$tag" ' awk -v ver="$tag" '
$0 ~ "^## \\[" ver "\\]" {found=1; next} $0 ~ "^## \\[" ver "\\]" {found=1; next}
found && $0 ~ "^## \\[" {exit} found && $0 ~ "^## \\[" {exit}
found {print} found {print}
' CHANGELOG.md | sed -e '${/^$/d;}' > "$notes_file" ' CHANGELOG.md | sed -e '${/^$/d;}' > "$notes_file"
else else
echo "release_name=v${tag}" >> "$GITHUB_OUTPUT"
echo "No CHANGELOG section for $tag." > "$notes_file" echo "No CHANGELOG section for $tag." > "$notes_file"
git log -1 --pretty=format:"%s%n%n%b" >> "$notes_file" git log -1 --pretty=format:"%s%n%n%b" >> "$notes_file"
fi fi
@@ -126,6 +138,7 @@ jobs:
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ needs.setup.outputs.tag }} tag_name: ${{ needs.setup.outputs.tag }}
name: ${{ needs.notes.outputs.release_name }}
body_path: .github/release-notes.md body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }} prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
@@ -651,6 +664,8 @@ jobs:
done done
} }
PKG PKG
cat > packages/aur/dcr/.SRCINFO <<SRC
pkgbase = dcr pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = ${PKGVER_CLEAN} pkgver = ${PKGVER_CLEAN}
@@ -1229,34 +1244,11 @@ jobs:
git commit -m "dcr ${{ needs.setup.outputs.version }}" git commit -m "dcr ${{ needs.setup.outputs.version }}"
git push git push
# ── Snap Store (dcrup) ───────────────────────────────────────────────────── # ── Snap Store (dcrup) — temporarily disabled ────────────────────────────
publish-snap: # Classic confinement was denied for this project (too new).
if: | # Snap artifact is still built and attached to the release via
(github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') || # publish-dexoron-packages. Users can install manually:
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_snap == 'true') # sudo snap install --classic --dangerous dcrup_*.snap
needs: [setup, build-linux-packages] #
runs-on: ubuntu-latest # publish-snap:
steps: # if: ...
- name: Download Built Packages
uses: actions/download-artifact@v4
with:
name: snap-package
path: dist-packages
- name: Resolve Snap File
id: snap-file
run: |
snap_file=$(ls dist-packages/*.snap 2>/dev/null | head -1)
if [ -z "$snap_file" ]; then
echo "No .snap file found in dist-packages/"
exit 1
fi
echo "snap=$snap_file" >> "$GITHUB_OUTPUT"
- name: Publish to Snap Store
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
with:
snap: ${{ steps.snap-file.outputs.snap }}
release: stable

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ target/
sandbox/** sandbox/**
.idea/ .idea/
.vscode/ .vscode/
.cargo/
.env .env
personal/ personal/

View File

@@ -1,5 +1,63 @@
# Changelog # Changelog
## [0.7.3] - 2026-06-13 "Оптимизация Зависимостей и VCS / Dependency Optimization & VCS"
### RU
**Добавлено:**
- **Параметр `--vcs` для команд `new` и `init`** — добавлена возможность явно указывать систему контроля версий через `--vcs <git|none>`.
- **Интеграция Git-информации в `--version`** — утилита теперь выводит текущий короткий хэш коммита и суффикс `-dirty`, если в репозитории DCR есть незакоммиченные изменения.
- **Автоматическая генерация `.gitignore`** — при инициализации Git-репозитория автоматически создается файл `.gitignore` с исключением директории `/target`.
- **Защита от вложенных репозиториев** — автоматическая инициализация Git теперь пропускается, если `dcr new` или `dcr init` запускаются внутри уже существующего Git-репозитория.
- **Тесты для верификации VCS** — добавлены интеграционные тесты `new_vcs_options_work` и `init_vcs_options_work` для проверки логики инициализации репозиториев.
**Изменено:**
- **Полный отказ от библиотеки `git2`** — работа с Git (клонирование зависимостей, инициализация VCS, проверка статуса) переведена на вызовы системного `git` через `Command`. Это облегчает сборку проекта и убирает необходимость в фичах `vendored` и `openssl`.
- **Миграция с `reqwest` на `ureq`** — в модуле обновления (`flag_update.rs`) тяжелый `reqwest` заменен на легковесный синхронный клиент `ureq 2.10`, что уменьшило оверхед и размер итогового бинарника.
### EN
**Added:**
- **`--vcs` option for `new` and `init` commands** — introduced explicit version control system selection via `--vcs <git|none>`.
- **Git metadata integration in `--version`** — the CLI now appends the current short commit hash and a `-dirty` suffix if there are uncommitted changes in the DCR repository.
- **Automatic `.gitignore` generation** — Git repository initialization now automatically writes a `.gitignore` file excluding the `/target` directory.
- **Nested repository prevention** — automatic Git initialization is now skipped if `dcr new` or `dcr init` is executed inside an already existing Git repository.
- **VCS verification tests** — added `new_vcs_options_work` and `init_vcs_options_work` integration tests to ensure reliable repository behavior.
**Changed:**
- **Complete removal of `git2` dependency** — all Git actions (dependency fetching, VCS setup, status checks) are now delegated to the system `git` executable via `Command`. This simplifies compilation and drops the need for `vendored` and `openssl` features.
- **Migrated from `reqwest` to `ureq`** — replaced the heavy `reqwest` crate in `flag_update.rs` with the lightweight synchronous `ureq 2.10` client, reducing overhead and the final binary size.
## [0.7.2] - 2026-06-07 "Исправление Таргетов и Линковки / Target & Linker Fixes"
### RU
**Добавлено:**
- Учтён `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`.
- **Бейджи GitHub Stars и GPL-3.0 не рендерились в README** — блок бейджей переведён на чистый HTML.
**Изменено:**
- Вычисление таргета по умолчанию вынесено в общую функцию `default_target_triple()` — устранено дублирование в build, run, clean.
### EN
**Added:**
- `default_target_triple()` now respects `target_env` — Linux (gnu/musl) and Windows (msvc/gnu) use the correct environment instead of hardcoded values.
**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`.
- **GitHub Stars and GPL-3.0 badges not rendering in README** — badge block converted to pure HTML.
**Changed:**
- 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"
### RU ### RU
@@ -12,7 +70,7 @@
- Оптимизация сборки: `opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`. - Оптимизация сборки: `opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`.
- Поддержка новых архитектур (i686, armv7, riscv64gc, aarch64) в рабочих процессах и установочных скриптах. - Поддержка новых архитектур (i686, armv7, riscv64gc, aarch64) в рабочих процессах и установочных скриптах.
- Сборка пакетов `deb` и `rpm` для всех 5 архитектур Linux через `cargo-zigbuild`. - Сборка пакетов `deb` и `rpm` для всех 5 архитектур Linux через `cargo-zigbuild`.
- Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и публикация Snap в CI. - Пакеты AUR (`dcr-dev`, `dcr-dev-bin`) и сборка Snap в CI.
**Изменено:** **Изменено:**
- Функция `validate_package_name` стала публичной. - Функция `validate_package_name` стала публичной.
@@ -20,6 +78,15 @@
- Инструкция в README изменена с `| bash` на `| sh` для POSIX. - Инструкция в README изменена с `| bash` на `| sh` для POSIX.
- Отключена архитектура Windows 32-bit (GNU) из-за проблем со сборкой. - Отключена архитектура Windows 32-bit (GNU) из-за проблем со сборкой.
**Исправлено:**
- **`dcr new <invalid-name>` создаёт директорию, затем падает** — валидация теперь происходит до операций с файлами.
- **`dcr init` создаёт dcr.toml, затем падает при невалидном имени директории** — то же исправление.
- **Man-страницы отсутствовали после установки** — теперь устанавливаются через скрипты установки, пакетные менеджеры и Homebrew.
- **Ложное срабатывание на букву диска в Windows** — в парсинге файлов зависимостей.
- **CRLF `\r\n` ломает парсер зависимостей** — теперь обрабатываются смешанные окончания строк.
- **Новая строка после обратного слеша не поглощалась** — в обработке escape-последовательностей `parse_d_file`.
### EN ### EN
**Added:** **Added:**
@@ -30,7 +97,7 @@
- Release profile optimizations (`opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`). - Release profile optimizations (`opt-level = "z"`, LTO, `codegen-units = 1`, `panic = "abort"`, `strip = true`).
- Linux i686, armv7, riscv64gc, aarch64 architecture support in release workflows and installation scripts. - Linux i686, armv7, riscv64gc, aarch64 architecture support in release workflows and installation scripts.
- Multi-arch `deb` and `rpm` package builds for all 5 Linux architectures via `cargo-zigbuild`. - Multi-arch `deb` and `rpm` package builds for all 5 Linux architectures via `cargo-zigbuild`.
- AUR packages (`dcr-dev`, `dcr-dev-bin`) and automated Snapcraft publishing 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.

1037
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dcr" name = "dcr"
version = "0.7.1" version = "0.7.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."
@@ -61,7 +61,7 @@ assets = [
[dependencies] [dependencies]
glob = "0.3" glob = "0.3"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } ureq = { version = "2.10", default-features = false, features = ["json", "native-certs"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
self-replace = "1" self-replace = "1"
sha2 = "0.10" sha2 = "0.10"
@@ -69,16 +69,17 @@ toml = "1.0.3"
serde_json = "1" serde_json = "1"
ctrlc = "3" ctrlc = "3"
[target.'cfg(not(windows))'.dependencies]
git2 = { version = "0.20.4", features = ["vendored-libgit2", "vendored-openssl"] }
openssl = { version = "0.10", features = ["vendored"] }
[target.'cfg(windows)'.dependencies]
git2 = { version = "0.20.4", features = ["vendored-libgit2"] }
[profile.release] [profile.release]
opt-level = "z" opt-level = "z"
lto = true lto = "thin"
codegen-units = 1 codegen-units = 1
panic = "abort" panic = "abort"
strip = true
[profile.fast]
inherits = "release"
opt-level = 1
lto = false
codegen-units = 16
panic = "abort"
strip = true strip = true

View File

@@ -1,22 +1,21 @@
<div align="center"> <h1 align="center">DCR — Dexoron Cargo Realization</h1>
# DCR — Dexoron Cargo Realization <p align="center"><strong>A Cargo-style build tool for C/C++ projects</strong></p>
**A Cargo-style build tool for C/C++ projects** <p align="center">
<a href="https://github.com/dexoron/dcr/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/dexoron/dcr/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/dexoron/dcr/releases/latest"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/dexoron/dcr"></a>
<a href="https://github.com/dexoron/dcr/releases"><img alt="Platform" src="https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey"></a>
<br>
<a href="https://aur.archlinux.org/packages/dcr"><img alt="AUR" src="https://img.shields.io/aur/version/dcr"></a>
<a href="https://crates.io/crates/dcr"><img alt="Crates.io" src="https://img.shields.io/crates/v/dcr"></a>
<a href="https://github.com/dexoron/homebrew-dexoron"><img alt="Homebrew" src="https://img.shields.io/badge/homebrew-dexoron%2Fdexoron-orange"></a>
<br>
<a href="https://github.com/dexoron/dcr/stargazers"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/dexoron/dcr?style=flat"></a>
<a href="LICENSE"><img alt="License: GPL-3.0" src="https://img.shields.io/badge/License-GPL--3.0-blue.svg"></a>
</p>
[![CI](https://github.com/dexoron/dcr/actions/workflows/ci.yml/badge.svg)](https://github.com/dexoron/dcr/actions/workflows/ci.yml) <!-- Snap badge removed: classic confinement was denied for this project. -->
[![GitHub Release](https://img.shields.io/github/v/release/dexoron/dcr)](https://github.com/dexoron/dcr/releases/latest)
[![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey)](https://github.com/dexoron/dcr/releases)
<br/>
[![AUR](https://img.shields.io/aur/version/dcr)](https://aur.archlinux.org/packages/dcr)
[![Crates.io](https://img.shields.io/crates/v/dcr)](https://crates.io/crates/dcr)
[![Homebrew](https://img.shields.io/badge/homebrew-dexoron%2Fdexoron-orange)](https://github.com/dexoron/homebrew-dexoron)
[![Snap](https://img.shields.io/badge/snap-dcrup-orange)](https://snapcraft.io/dcrup)
<br/>
[![GitHub Stars](https://img.shields.io/github/stars/dexoron/dcr?style=flat)](https://github.com/dexoron/dcr/stargazers)
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](LICENSE)
</div>
--- ---
@@ -38,7 +37,7 @@
<td><b>Linux (Dexoron Packages)</b></td> <td><b>Linux (Dexoron Packages)</b></td>
<td> <td>
See https://dcr.dexoron.su for settings youre packages manager See https://pkg.dexoron.su for settings youre packages manager
**Arch Linux:** **Arch Linux:**
@@ -127,8 +126,11 @@ curl -fsSL https://dcr.dexoron.su/install_bsd.sh | sh
<td><b>Snap (Linux)</b></td> <td><b>Snap (Linux)</b></td>
<td> <td>
> Snap Store publishing temporarily disabled — see [#51671](https://forum.snapcraft.io/t/classic-confinement-request-for-dcrup/51671).
> Download the `.snap` from [GitHub Releases](https://github.com/dexoron/dcr/releases/latest) and install manually:
```sh ```sh
sudo snap install dcrup sudo snap install --classic --dangerous dcrup_*.snap
``` ```
</td> </td>
@@ -226,7 +228,7 @@ other in [docs/](docs/), `dcr --help` and `dcr <command> --help`
| Linux (Musl) | ✅ | ✅ | ✅ | ✅ | 🚧 | | Linux (Musl) | ✅ | ✅ | ✅ | ✅ | 🚧 |
| macOS | ✅ | ✅ | ❌ | ❌ | ❌ | | macOS | ✅ | ✅ | ❌ | ❌ | ❌ |
| Windows (MSVC) | ✅ | ✅ | ❌ | ✅ | ❌ | | Windows (MSVC) | ✅ | ✅ | ❌ | ✅ | ❌ |
| Windows (MinGW) | ✅ | 🚧 | ❌ | ✅ | ❌ | | Windows (MinGW) | ✅ | 🚧 | ❌ | ❌ | ❌ |
| FreeBSD | ✅ | 🚧 | ❌ | ❌ | ❌ | | FreeBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
| OpenBSD | ✅ | 🚧 | ❌ | ❌ | ❌ | | OpenBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
| NetBSD | ✅ | 🚧 | ❌ | ❌ | ❌ | | NetBSD | ✅ | 🚧 | ❌ | ❌ | ❌ |
@@ -271,6 +273,7 @@ cargo test
<div align="center"> <div align="center">
GPL-3.0 — see [LICENSE](LICENSE)<br/> GPL-3.0 — see [LICENSE](LICENSE)<br/>
DCR is a build tool, not a library. The GPL applies only to DCR's own source code, not to projects built with it.<br/>
Made with ❤️ by [Dexoron](https://github.com/dexoron) and contributors. Made with ❤️ by [Dexoron](https://github.com/dexoron) and contributors.
</div> </div>

View File

@@ -18,4 +18,37 @@
fn main() { fn main() {
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string()); let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string());
println!("cargo:rustc-env=DCR_TARGET={target}"); println!("cargo:rustc-env=DCR_TARGET={target}");
// Let's try to retrieve the commit hash for the version
let commit = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
});
// Check if there are any uncommitted changes in the repository
let is_dirty = std::process::Command::new("git")
.args(["status", "--porcelain"])
.output()
.ok()
.map(|o| o.status.success() && !o.stdout.is_empty())
.unwrap_or(false);
if let Some(commit_hash) = commit {
let dirty_suffix = if is_dirty { "-dirty" } else { "" };
println!(
"cargo:rustc-env=DCR_GIT_INFO= (git {}{})",
commit_hash, dirty_suffix
);
} else {
println!("cargo:rustc-env=DCR_GIT_INFO=");
}
} }

View File

@@ -44,6 +44,7 @@ dcr gen vscode
| [Self Update](/docs/self-update) | Auto-update | | [Self Update](/docs/self-update) | Auto-update |
| [FAQ](/docs/faq) | Frequently asked questions | | [FAQ](/docs/faq) | Frequently asked questions |
| [Changelog](/docs/changelog) | Version history | | [Changelog](/docs/changelog) | Version history |
| [License](/docs/license) | DCR and vendored library licenses |
| [Contributing](/docs/contributing) | DCR development | | [Contributing](/docs/contributing) | DCR development |
## Features ## Features

View File

@@ -6,7 +6,7 @@ sidebar_label: Installation
## Linux (Dexoron Packages) ## Linux (Dexoron Packages)
Configure your package manager at https://dcr.dexoron.su and install: Configure your package manager at https://pkg.dexoron.su and install:
**Arch Linux** **Arch Linux**
```sh ```sh

46
docs/license.mdx Normal file
View File

@@ -0,0 +1,46 @@
---
sidebar_label: License
---
# License
## DCR
DCR itself is [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html) licensed.
> **Note:** DCR is a build tool, not a library. The GPL applies only to DCR's own source code. Projects built with DCR are not subject to DCR's license — their licensing is determined solely by their own code and dependencies.
## Vendored libraries
DCR statically links the following C libraries through its vendored Rust dependencies:
| Library | License | Bundled by |
|---|---|---|
| [libgit2](https://libgit2.org) | MIT OR Apache-2.0 | `git2` / `libgit2-sys` |
| [libssh2](https://libssh2.org) | MIT OR Apache-2.0 | `libgit2-sys` |
| [zlib](https://zlib.net) | MIT OR Apache-2.0 | `libgit2-sys` |
| [OpenSSL](https://openssl.org) | Apache-2.0 | `openssl-sys` / `openssl-src` |
| [ring](https://github.com/briansmith/ring) | Apache-2.0 AND ISC | `rustls` |
## Rust dependencies
DCR is written in Rust. The following notable Rust crates are statically linked:
| Crate | License |
|---|---|
| [`reqwest`](https://crates.io/crates/reqwest) | MIT OR Apache-2.0 |
| [`rustls`](https://crates.io/crates/rustls) | Apache-2.0 OR ISC OR MIT |
| [`serde`](https://crates.io/crates/serde) | MIT OR Apache-2.0 |
| [`toml`](https://crates.io/crates/toml) | MIT OR Apache-2.0 |
| [`sha2`](https://crates.io/crates/sha2) | MIT OR Apache-2.0 |
| [`git2`](https://crates.io/crates/git2) | MIT OR Apache-2.0 |
| [`openssl`](https://crates.io/crates/openssl) | Apache-2.0 |
| [`tokio`](https://crates.io/crates/tokio) | MIT |
| [`hyper`](https://crates.io/crates/hyper) | MIT |
| [`ctrlc`](https://crates.io/crates/ctrlc) | MIT OR Apache-2.0 |
Full dependency tree (143 packages) is available in [`Cargo.lock`](https://github.com/dexoron/dcr/blob/main/Cargo.lock). The overwhelming majority are dual-licensed under MIT OR Apache-2.0.
## Additional credits
- DCR's CLI design and project model are inspired by [Cargo](https://doc.rust-lang.org/cargo/).

View File

@@ -34,9 +34,12 @@ pub struct AddArgs {
} }
pub fn add(args: &[String]) -> i32 { pub fn add(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr add <name> <source> [--branch <b>] [--tag <t>] [--rev <r>]", BOLD_CYAN); printc(
" dcr add <name> <source> [--branch <b>] [--tag <t>] [--rev <r>]",
BOLD_CYAN,
);
println!(); println!();
printc("DESCRIPTION:", BOLD_GREEN); printc("DESCRIPTION:", BOLD_GREEN);
println!(" Adds a dependency to the project."); println!(" Adds a dependency to the project.");

View File

@@ -23,9 +23,9 @@ use crate::core::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,
normalize_target_os, parse_version_info, profile_table, resolve_compiler, get_language_with_profile, normalize_target_os, parse_version_info, prepend_clang_target_flag,
resolve_pkg_config_flags, resolve_tool, profile_table, resolve_compiler, resolve_pkg_config_flags, resolve_tool,
}; };
use crate::utils::fs::{check_dir, find_project_root, with_dir}; use crate::utils::fs::{check_dir, find_project_root, with_dir};
use crate::utils::log::error; use crate::utils::log::error;
@@ -42,9 +42,12 @@ static BUILD_INTERRUPTED: AtomicBool = AtomicBool::new(false);
static SIGNAL_HANDLER: Once = Once::new(); static SIGNAL_HANDLER: Once = Once::new();
pub fn build(args: &[String]) -> i32 { pub fn build(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr build [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose]", BOLD_CYAN); printc(
" dcr build [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose]",
BOLD_CYAN,
);
println!(); println!();
printc("DESCRIPTION:", BOLD_GREEN); printc("DESCRIPTION:", BOLD_GREEN);
println!(" Compiles the project. Default profile is --debug."); println!(" Compiles the project. Default profile is --debug.");
@@ -97,27 +100,7 @@ pub fn build(args: &[String]) -> i32 {
} }
} }
if flags.target.is_none() { if flags.target.is_none() {
let default_target = if cfg!(target_os = "linux") { flags.target = Some(default_target_triple());
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
} else if cfg!(target_os = "macos") {
"x86_64-apple-darwin".to_string()
} else if cfg!(target_os = "windows") {
"x86_64-pc-windows-msvc".to_string()
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
format!(
"{}-unknown-{}",
std::env::consts::ARCH,
std::env::consts::OS
)
} else {
"unknown".to_string()
};
flags.target = Some(default_target);
} }
if flags.clean { if flags.clean {
let mut clean_args = Vec::new(); let mut clean_args = Vec::new();
@@ -715,19 +698,13 @@ fn build_project_at(
tc_cxx.as_deref(), tc_cxx.as_deref(),
tc_as.as_deref(), tc_as.as_deref(),
); );
if let Some(t) = build_target
&& !t.trim().is_empty()
&& resolved_compiler.to_lowercase().contains("clang")
{
let target_flag = format!("--target={}", t.trim());
if !build_cflags
.iter()
.any(|f| f == &target_flag || f.starts_with("--target="))
{
build_cflags.insert(0, target_flag);
}
}
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref()); let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
prepend_clang_target_flag(&mut build_cflags, build_target, &resolved_compiler);
prepend_clang_target_flag(
&mut build_ldflags,
build_target,
resolved_linker.as_deref().unwrap_or(&resolved_compiler),
);
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref()); let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
let out_dir_config = get_build_string_with_profile(&config, "out_dir", profile); let out_dir_config = get_build_string_with_profile(&config, "out_dir", profile);

View File

@@ -18,7 +18,7 @@
use crate::config::flags; use crate::config::flags;
use crate::core::config::Config; use crate::core::config::Config;
use crate::core::workspace::parse_workspace; use crate::core::workspace::parse_workspace;
use crate::utils::build::parse_version_info; use crate::utils::build::{default_target_triple, parse_version_info};
use crate::utils::fs::{check_dir, find_project_root, with_dir}; use crate::utils::fs::{check_dir, find_project_root, with_dir};
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
@@ -27,9 +27,12 @@ use std::fs;
use std::path::Path; use std::path::Path;
pub fn clean(args: &[String]) -> i32 { pub fn clean(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr clean [--debug | --release] [--target <triple>] [--all]", BOLD_CYAN); printc(
" dcr clean [--debug | --release] [--target <triple>] [--all]",
BOLD_CYAN,
);
println!(); println!();
printc("DESCRIPTION:", BOLD_GREEN); printc("DESCRIPTION:", BOLD_GREEN);
println!(" Removes build artifacts from the target directory."); println!(" Removes build artifacts from the target directory.");
@@ -133,28 +136,7 @@ fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
}) })
.or_else(|| { .or_else(|| Some(default_target_triple()));
Some(if cfg!(target_os = "linux") {
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
} else if cfg!(target_os = "macos") {
"x86_64-apple-darwin".to_string()
} else if cfg!(target_os = "windows") {
"x86_64-pc-windows-msvc".to_string()
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
format!(
"{}-unknown-{}",
std::env::consts::ARCH,
std::env::consts::OS
)
} else {
"unknown".to_string()
})
});
if flags.all if flags.all
&& let Some(workspace) = parse_workspace( && let Some(workspace) = parse_workspace(

View File

@@ -17,9 +17,9 @@
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
use reqwest::blocking::Client;
use serde::Deserialize; use serde::Deserialize;
use std::fs; use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::process::Command; use std::process::Command;
@@ -40,7 +40,7 @@ struct ReleaseAsset {
} }
pub fn flag_update(args: &[String]) -> i32 { pub fn flag_update(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr --update", BOLD_CYAN); printc(" dcr --update", BOLD_CYAN);
println!(); println!();
@@ -72,15 +72,8 @@ pub fn flag_update(args: &[String]) -> i32 {
let current_version = env!("CARGO_PKG_VERSION"); let current_version = env!("CARGO_PKG_VERSION");
let target = env!("DCR_TARGET"); let target = env!("DCR_TARGET");
let client = match Client::builder().user_agent("dcr-updater").build() {
Ok(client) => client,
Err(_) => {
error("Failed to initialize HTTP client");
return 1;
}
};
let release = match fetch_latest_release(&client) { let release = match fetch_latest_release() {
Ok(release) => release, Ok(release) => release,
Err(err) => { Err(err) => {
error(&format!("Failed to check for updates: {err}")); error(&format!("Failed to check for updates: {err}"));
@@ -104,7 +97,7 @@ pub fn flag_update(args: &[String]) -> i32 {
return 1; return 1;
}; };
let bytes = match download_asset(&client, &asset.browser_download_url) { let bytes = match download_asset(&asset.browser_download_url) {
Ok(bytes) => bytes, Ok(bytes) => bytes,
Err(err) => { Err(err) => {
error(&format!("Failed to download update: {err}")); error(&format!("Failed to download update: {err}"));
@@ -131,35 +124,35 @@ pub fn flag_update(args: &[String]) -> i32 {
0 0
} }
fn fetch_latest_release(client: &Client) -> Result<Release, String> { fn fetch_latest_release() -> Result<Release, String> {
let response = client let response = ureq::get(LATEST_RELEASE_URL)
.get(LATEST_RELEASE_URL) .set("User-Agent", "dcr-updater")
.send() .call()
.map_err(|_| "GitHub API request failed".to_string())?; .map_err(|e| match e {
ureq::Error::Status(code, _) => format!("GitHub API returned status {code}"),
if !response.status().is_success() { ureq::Error::Transport(e) => format!("GitHub API request failed: {e}"),
return Err(format!("GitHub API returned status {}", response.status())); })?;
}
response response
.json::<Release>() .into_json::<Release>()
.map_err(|_| "GitHub API response has an unexpected format".to_string()) .map_err(|_| "GitHub API response has an unexpected format".to_string())
} }
fn download_asset(client: &Client, url: &str) -> Result<Vec<u8>, String> { fn download_asset(url: &str) -> Result<Vec<u8>, String> {
let response = client let response = ureq::get(url)
.get(url) .set("User-Agent", "dcr-updater")
.send() .call()
.map_err(|_| "Download request failed".to_string())?; .map_err(|e| match e {
ureq::Error::Status(code, _) => format!("Download returned status {code}"),
ureq::Error::Transport(e) => format!("Download request failed: {e}"),
})?;
if !response.status().is_success() { let mut reader = response.into_reader();
return Err(format!("Download returned status {}", response.status())); let mut data = Vec::new();
} reader
.read_to_end(&mut data)
response .map_err(|_| "Failed to read downloaded data".to_string())?;
.bytes() Ok(data)
.map(|bytes| bytes.to_vec())
.map_err(|_| "Failed to read downloaded data".to_string())
} }
fn asset_candidates(target: &str) -> Vec<String> { fn asset_candidates(target: &str) -> Vec<String> {

View File

@@ -21,7 +21,7 @@ use glob::glob;
use std::process::Command; use std::process::Command;
pub fn fmt(args: &[String]) -> i32 { pub fn fmt(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr fmt", BOLD_CYAN); printc(" dcr fmt", BOLD_CYAN);
println!(); println!();

View File

@@ -16,7 +16,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::config::FILE_MAIN_C; use crate::config::FILE_MAIN_C;
use crate::core::config::{validate_package_name, Config}; use crate::core::config::{Config, validate_package_name};
use crate::core::vcs::VcsKind;
use crate::utils::fs::check_dir; use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
@@ -25,9 +26,9 @@ use std::io::Write;
use toml::Value; use toml::Value;
pub fn init(args: &[String]) -> i32 { pub fn init(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr init", BOLD_CYAN); printc(" dcr init [--vcs <git|none>]", BOLD_CYAN);
println!(); println!();
printc("DESCRIPTION:", BOLD_GREEN); printc("DESCRIPTION:", BOLD_GREEN);
println!(" Initializes the current directory as a DCR project."); println!(" Initializes the current directory as a DCR project.");
@@ -35,7 +36,25 @@ pub fn init(args: &[String]) -> i32 {
return 0; return 0;
} }
if !args.is_empty() { let mut vcs_str = None;
let mut clean_args = Vec::new();
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--vcs" {
if let Some(val) = iter.next() {
vcs_str = Some(val.clone());
} else {
error("--vcs requires a value");
return 1;
}
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
vcs_str = Some(stripped.to_string());
} else {
clean_args.push(arg.clone());
}
}
if !clean_args.is_empty() {
warn("Command does not support additional arguments"); warn("Command does not support additional arguments");
return 1; return 1;
} }
@@ -106,6 +125,40 @@ pub fn init(args: &[String]) -> i32 {
colored("src/main.c", BOLD_CYAN) colored("src/main.c", BOLD_CYAN)
); );
// settings VCS
let mut vcs_kind = VcsKind::Git;
if let Some(ref vcs_val) = vcs_str {
match VcsKind::parse(vcs_val) {
Ok(kind) => vcs_kind = kind,
Err(e) => {
error(&e);
return 1;
}
}
} else if let Ok(cwd_path) = std::env::current_dir()
&& crate::core::vcs::find_existing_vcs(&cwd_path).is_some()
{
vcs_kind = VcsKind::None;
}
if vcs_kind == VcsKind::Git {
if crate::utils::git::is_git_available() {
let project_path = std::path::Path::new(".");
if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) {
warn(&format!("Failed to initialize git repository: {}", e));
} else {
println!(
" {} Initialized git repository",
colored("✔", BOLD_GREEN)
);
}
} else {
warn(
"Git is not installed or not found in PATH. Skipping Git repository initialization.",
);
}
}
println!( println!(
"Project `{}` successfully created\n", "Project `{}` successfully created\n",
colored(project_name.as_str(), BOLD_GREEN) colored(project_name.as_str(), BOLD_GREEN)

View File

@@ -16,7 +16,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::config::FILE_MAIN_C; use crate::config::FILE_MAIN_C;
use crate::core::config::{validate_package_name, Config}; use crate::core::config::{Config, validate_package_name};
use crate::core::vcs::VcsKind;
use crate::utils::fs::check_dir; use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
@@ -25,9 +26,9 @@ use std::io::Write;
use toml::Value; use toml::Value;
pub fn new(args: &[String]) -> i32 { pub fn new(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr new <name>", BOLD_CYAN); printc(" dcr new <name> [--vcs <git|none>]", BOLD_CYAN);
println!(); println!();
printc("DESCRIPTION:", BOLD_GREEN); printc("DESCRIPTION:", BOLD_GREEN);
println!(" Creates a new C/C++ project with the given name."); println!(" Creates a new C/C++ project with the given name.");
@@ -37,16 +38,34 @@ pub fn new(args: &[String]) -> i32 {
let items = check_dir(None).unwrap_or_default(); let items = check_dir(None).unwrap_or_default();
if args.is_empty() { let mut vcs_str = None;
let mut clean_args = Vec::new();
let mut iter = args.iter();
while let Some(arg) = iter.next() {
if arg == "--vcs" {
if let Some(val) = iter.next() {
vcs_str = Some(val.clone());
} else {
error("--vcs requires a value");
return 1;
}
} else if let Some(stripped) = arg.strip_prefix("--vcs=") {
vcs_str = Some(stripped.to_string());
} else {
clean_args.push(arg.clone());
}
}
if clean_args.is_empty() {
error("Project name not specified"); error("Project name not specified");
return 1; return 1;
} }
if args.len() > 1 { if clean_args.len() > 1 {
warn("Command does not support additional arguments"); warn("Command does not support additional arguments");
return 1; return 1;
} }
let project_name = &args[0]; let project_name = &clean_args[0];
println!( println!(
"Creating a Project `{}`...", "Creating a Project `{}`...",
colored(project_name, BOLD_CYAN) colored(project_name, BOLD_CYAN)
@@ -127,6 +146,40 @@ pub fn new(args: &[String]) -> i32 {
colored("src/main.c", BOLD_CYAN) colored("src/main.c", BOLD_CYAN)
); );
// settings VCS
let mut vcs_kind = VcsKind::Git;
if let Some(ref vcs_val) = vcs_str {
match VcsKind::parse(vcs_val) {
Ok(kind) => vcs_kind = kind,
Err(e) => {
error(&e);
return 1;
}
}
} else if let Ok(cwd) = std::env::current_dir()
&& crate::core::vcs::find_existing_vcs(&cwd).is_some()
{
vcs_kind = VcsKind::None;
}
if vcs_kind == VcsKind::Git {
if crate::utils::git::is_git_available() {
let project_path = std::path::Path::new(project_name);
if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) {
warn(&format!("Failed to initialize git repository: {}", e));
} else {
println!(
" {} Initialized git repository",
colored("✔", BOLD_GREEN)
);
}
} else {
warn(
"Git is not installed or not found in PATH. Skipping Git repository initialization.",
);
}
}
println!( println!(
"Project `{}` successfully created\n", "Project `{}` successfully created\n",
colored(project_name, BOLD_GREEN) colored(project_name, BOLD_GREEN)

View File

@@ -19,7 +19,7 @@ use crate::cli::build::build;
use crate::cli::flags::parse_build_run_flags; use crate::cli::flags::parse_build_run_flags;
use crate::core::config::Config; use crate::core::config::Config;
use crate::core::runner::run_binary; use crate::core::runner::run_binary;
use crate::utils::build::{normalize_target_os, parse_version_info}; use crate::utils::build::{default_target_triple, normalize_target_os, parse_version_info};
use crate::utils::fs::find_project_root; use crate::utils::fs::find_project_root;
use crate::utils::fs::with_dir; use crate::utils::fs::with_dir;
use crate::utils::log::error; use crate::utils::log::error;
@@ -56,9 +56,12 @@ fn get_run_cmd(
} }
pub fn run(args: &[String]) -> i32 { pub fn run(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose]", BOLD_CYAN); printc(
" dcr run [--debug | --release] [--target <triple>] [--force] [--clean] [--verbose]",
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\".");
@@ -175,27 +178,7 @@ fn run_project(
// If no target specified, use default host target for target-specific config // If no target specified, use default host target for target-specific config
let mut target = flags.target.clone(); let mut target = flags.target.clone();
if target.is_none() { if target.is_none() {
let default_target = if cfg!(target_os = "linux") { target = Some(default_target_triple());
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
} else if cfg!(target_os = "macos") {
"x86_64-apple-darwin".to_string()
} else if cfg!(target_os = "windows") {
"x86_64-pc-windows-msvc".to_string()
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
format!(
"{}-unknown-{}",
std::env::consts::ARCH,
std::env::consts::OS
)
} else {
"unknown".to_string()
};
target = Some(default_target);
} }
let build_kind = config let build_kind = config

View File

@@ -18,7 +18,7 @@
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
pub fn setup(args: &[String]) -> i32 { pub fn setup(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr setup", BOLD_CYAN); printc(" dcr setup", BOLD_CYAN);
println!(); println!();

View File

@@ -23,7 +23,7 @@ use std::path::{Path, PathBuf};
use toml::Value; use toml::Value;
pub fn tree(args: &[String]) -> i32 { pub fn tree(args: &[String]) -> i32 {
if args.first().map_or(false, |a| a == "--help") { if args.first().is_some_and(|a| a == "--help") {
printc("USAGE:", BOLD_GREEN); printc("USAGE:", BOLD_GREEN);
printc(" dcr tree", BOLD_CYAN); printc(" dcr tree", BOLD_CYAN);
println!(); println!();

View File

@@ -29,43 +29,5 @@ pub fn fetch_git_dep(
return Ok(()); return Ok(());
} }
let mut builder = git2::build::RepoBuilder::new(); crate::utils::git::git_clone_and_checkout(url, target_dir, branch, tag, rev)
if let Some(b) = branch {
builder.branch(b);
}
let repo = builder
.clone(url, target_dir)
.map_err(|e| format!("failed to clone {}: {}", url, e))?;
if let Some(t) = tag {
checkout_ref(&repo, &format!("refs/tags/{}", t))?;
} else if let Some(r) = rev {
checkout_ref(&repo, r)?;
}
Ok(())
}
#[allow(dead_code)]
fn checkout_ref(repo: &git2::Repository, reference_str: &str) -> Result<(), String> {
let (object, reference) = repo
.revparse_ext(reference_str)
.map_err(|e| format!("failed to find ref {}: {}", reference_str, e))?;
repo.checkout_tree(&object, None).map_err(|e| {
let ref_name = reference.as_ref().and_then(|r| r.name()).unwrap_or("HEAD");
format!("failed to checkout {}: {}", ref_name, e)
})?;
match reference {
Some(gref) => {
let ref_name = gref.name().unwrap_or("HEAD");
repo.set_head(ref_name)
}
None => repo.set_head_detached(object.id()),
}
.map_err(|e| format!("failed to set HEAD: {}", e))?;
Ok(())
} }

View File

@@ -20,4 +20,5 @@ pub mod config;
pub mod deps; pub mod deps;
pub mod registry; pub mod registry;
pub mod runner; pub mod runner;
pub mod vcs;
pub mod workspace; pub mod workspace;

32
src/core/vcs/git.rs Normal file
View File

@@ -0,0 +1,32 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::fs;
use std::path::Path;
// creating git init and .gitignore
pub fn init(path: &Path) -> Result<(), String> {
crate::utils::git::git_init(path)?;
let gitignore_path = path.join(".gitignore");
if !gitignore_path.exists() {
fs::write(&gitignore_path, "/target\n")
.map_err(|e| format!("failed to create .gitignore: {}", e))?;
}
Ok(())
}

61
src/core/vcs/mod.rs Normal file
View File

@@ -0,0 +1,61 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub mod git;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VcsKind {
Git,
None,
}
impl VcsKind {
pub fn parse(s: &str) -> Result<Self, String> {
match s.to_lowercase().as_str() {
"git" => Ok(Self::Git),
"none" => Ok(Self::None),
_ => Err(format!("Unknown VCS: {}", s)),
}
}
}
pub fn init_vcs(kind: VcsKind, path: &Path) -> Result<(), String> {
match kind {
VcsKind::Git => git::init(path),
VcsKind::None => Ok(()),
}
}
// look for git repo in parents to avoid nested repos
pub fn find_existing_vcs(path: &Path) -> Option<VcsKind> {
let mut current = if path.is_file() {
path.parent()
} else {
Some(path)
};
while let Some(dir) = current {
if dir.join(".git").exists() {
return Some(VcsKind::Git);
}
current = dir.parent();
}
None
}

View File

@@ -44,7 +44,12 @@ fn main() {
"clean" => cli::clean::clean(rest), "clean" => cli::clean::clean(rest),
"gen" => cli::r#gen::r#gen(rest), "gen" => cli::r#gen::r#gen(rest),
"--version" => { "--version" => {
println!("dcr {} ({})", env!("CARGO_PKG_VERSION"), env!("DCR_TARGET")); println!(
"dcr {}{} ({})",
env!("CARGO_PKG_VERSION"),
env!("DCR_GIT_INFO"),
env!("DCR_TARGET")
);
0 0
} }
"--help" => cli::help::help(), "--help" => cli::help::help(),

View File

@@ -72,6 +72,53 @@ pub fn normalize_target_os(target: &str) -> &str {
} }
} }
pub fn default_target_triple() -> String {
let arch = std::env::consts::ARCH;
if cfg!(target_os = "linux") {
let env = if cfg!(target_env = "musl") {
"musl"
} else {
"gnu"
};
format!("{arch}-unknown-linux-{env}")
} else if cfg!(target_os = "macos") {
format!("{arch}-apple-darwin")
} else if cfg!(target_os = "windows") {
let env = if cfg!(target_env = "gnu") {
"gnu"
} else {
"msvc"
};
format!("{arch}-pc-windows-{env}")
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
format!("{arch}-unknown-{}", std::env::consts::OS)
} else {
"unknown".to_string()
}
}
pub fn prepend_clang_target_flag(flags: &mut Vec<String>, target: Option<&str>, tool: &str) {
let Some(target) = target.map(str::trim).filter(|t| !t.is_empty()) else {
return;
};
if !tool.to_lowercase().contains("clang") {
return;
}
let target_flag = format!("--target={target}");
if !flags
.iter()
.any(|f| f == &target_flag || f.starts_with("--target="))
{
flags.insert(0, target_flag);
}
}
pub fn get_config_str(config: &Config, key: &str) -> String { pub fn get_config_str(config: &Config, key: &str) -> String {
config config
.get(key) .get(key)
@@ -380,6 +427,61 @@ mod tests {
assert_eq!(normalize_target_os("unknown"), "unknown"); assert_eq!(normalize_target_os("unknown"), "unknown");
} }
#[test]
fn default_target_triple_uses_host_arch() {
let target = default_target_triple();
if cfg!(target_os = "linux") {
assert_eq!(
target,
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
);
} else if cfg!(target_os = "macos") {
assert_eq!(target, format!("{}-apple-darwin", std::env::consts::ARCH));
} else if cfg!(target_os = "windows") {
assert_eq!(
target,
format!("{}-pc-windows-msvc", std::env::consts::ARCH)
);
} else if cfg!(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)) {
assert_eq!(
target,
format!(
"{}-unknown-{}",
std::env::consts::ARCH,
std::env::consts::OS
)
);
} else {
assert_eq!(target, "unknown");
}
}
#[test]
fn prepend_clang_target_flag_adds_once_for_clang() {
let mut flags = vec!["-O2".to_string()];
prepend_clang_target_flag(&mut flags, Some("aarch64-apple-darwin"), "clang");
prepend_clang_target_flag(&mut flags, Some("aarch64-apple-darwin"), "clang");
assert_eq!(
flags,
vec![
"--target=aarch64-apple-darwin".to_string(),
"-O2".to_string()
]
);
}
#[test]
fn prepend_clang_target_flag_ignores_non_clang_tools() {
let mut flags = vec!["-O2".to_string()];
prepend_clang_target_flag(&mut flags, Some("aarch64-apple-darwin"), "gcc");
assert_eq!(flags, vec!["-O2".to_string()]);
}
#[test] #[test]
fn parse_version_parts() { fn parse_version_parts() {
let info = parse_version_info("1.2.3-beta"); let info = parse_version_info("1.2.3-beta");

112
src/utils/git.rs Normal file
View File

@@ -0,0 +1,112 @@
// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::path::Path;
use std::process::Command;
// checking installed git on system
pub fn is_git_available() -> bool {
Command::new("git")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
// warning of git not in system
#[allow(dead_code)]
pub fn warn_if_git_missing() {
if !is_git_available() {
crate::utils::log::warn(
"Git is not installed or not found in PATH. Some features (like dependency cloning and VCS initialization) may fail.",
);
}
}
// init repo in folder
pub fn git_init(path: &Path) -> Result<(), String> {
if !is_git_available() {
return Err("git executable not found in PATH".to_string());
}
let status = Command::new("git")
.arg("init")
.current_dir(path)
.status()
.map_err(|e| format!("failed to execute git init: {}", e))?;
if !status.success() {
return Err("git init failed".to_string());
}
Ok(())
}
// Clone the dependency and switch to the branch/tag/commit
#[allow(dead_code)]
pub fn git_clone_and_checkout(
url: &str,
target_dir: &Path,
branch: Option<&str>,
tag: Option<&str>,
rev: Option<&str>,
) -> Result<(), String> {
if !is_git_available() {
return Err("git executable not found in PATH".to_string());
}
let mut args = vec!["clone"];
if let Some(b) = branch {
args.push("--branch");
args.push(b);
args.push("--single-branch");
} else if let Some(t) = tag {
args.push("--branch");
args.push(t);
args.push("--single-branch");
}
args.push(url);
let target_dir_str = target_dir.to_string_lossy().to_string();
args.push(&target_dir_str);
let status = Command::new("git")
.args(&args)
.status()
.map_err(|e| format!("failed to execute git clone: {}", e))?;
if !status.success() {
return Err(format!("git clone of {} failed", url));
}
if let Some(r) = rev {
let checkout_status = Command::new("git")
.arg("checkout")
.arg(r)
.current_dir(target_dir)
.status()
.map_err(|e| format!("failed to execute git checkout: {}", e))?;
if !checkout_status.success() {
return Err(format!("git checkout to revision {} failed", r));
}
}
Ok(())
}

View File

@@ -17,5 +17,6 @@
pub mod build; pub mod build;
pub mod fs; pub mod fs;
pub mod git;
pub mod log; pub mod log;
pub mod text; pub mod text;

View File

@@ -99,6 +99,60 @@ fn new_creates_project_layout() {
); );
} }
fn is_git_in_path() -> bool {
std::process::Command::new("git")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[test]
fn new_vcs_options_work() {
// 1. Test --vcs none
let dir = unique_sandbox_dir("new_vcs_none");
let out = run_dcr(&["new", "hello_none", "--vcs", "none"], &dir);
assert!(out.status.success(), "dcr new --vcs none should succeed");
let project_dir = dir.join("hello_none");
assert!(project_dir.is_dir());
assert!(!project_dir.join(".git").exists());
assert!(!project_dir.join(".gitignore").exists());
// 2. Test --vcs git
if is_git_in_path() {
let dir = unique_sandbox_dir("new_vcs_git");
let out = run_dcr(&["new", "hello_git", "--vcs", "git"], &dir);
assert!(out.status.success(), "dcr new --vcs git should succeed");
let project_dir = dir.join("hello_git");
assert!(project_dir.is_dir());
assert!(project_dir.join(".git").is_dir());
assert!(project_dir.join(".gitignore").is_file());
let gitignore = std::fs::read_to_string(project_dir.join(".gitignore")).unwrap();
assert!(gitignore.contains("/target"));
}
}
#[test]
fn init_vcs_options_work() {
// 1. Test --vcs none
let dir = unique_sandbox_dir("init_vcs_none");
let out = run_dcr(&["init", "--vcs", "none"], &dir);
assert!(out.status.success(), "dcr init --vcs none should succeed");
assert!(!dir.join(".git").exists());
assert!(!dir.join(".gitignore").exists());
// 2. Test --vcs git
if is_git_in_path() {
let dir = unique_sandbox_dir("init_vcs_git");
let out = run_dcr(&["init", "--vcs", "git"], &dir);
assert!(out.status.success(), "dcr init --vcs git should succeed");
assert!(dir.join(".git").is_dir());
assert!(dir.join(".gitignore").is_file());
let gitignore = std::fs::read_to_string(dir.join(".gitignore")).unwrap();
assert!(gitignore.contains("/target"));
}
}
#[test] #[test]
fn init_and_clean_remove_target() { fn init_and_clean_remove_target() {
let dir = unique_sandbox_dir("init"); let dir = unique_sandbox_dir("init");