mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 21:39:36 +00:00
Compare commits
25 Commits
v0.6.9
...
f5d5795872
| Author | SHA1 | Date | |
|---|---|---|---|
| f5d5795872 | |||
| e6788b4d71 | |||
| 865d8c52ff | |||
|
|
0587965283 | ||
|
|
478d1ad0c5 | ||
|
|
060b92111f | ||
|
|
86f0566dc2 | ||
|
|
768e2fc0dc | ||
|
|
619493bcbd | ||
|
|
1dac8661b6 | ||
|
|
80a6d574a3 | ||
|
|
daf465368f | ||
|
|
800fe38fba | ||
|
|
a7f9b79b93 | ||
|
|
b03660a9d7 | ||
|
|
2cb02aa102 | ||
|
|
06a8458ca7 | ||
| c2ef5483a0 | |||
| eb24d616c5 | |||
| 4f76f54b63 | |||
| 37b9b855ec | |||
| 8f0f67ad62 | |||
| 49945aa65d | |||
| 83593aa592 | |||
| 2835188da9 |
120
.github/workflows/dexoron-packages-index.yml
vendored
120
.github/workflows/dexoron-packages-index.yml
vendored
@@ -2,6 +2,11 @@ name: Dexoron Packages Index
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Версия (тег) для ручного импорта пакетов (например, v0.6.9)"
|
||||
required: true
|
||||
type: string
|
||||
workflow_run:
|
||||
workflows: ["Release"]
|
||||
types:
|
||||
@@ -13,7 +18,7 @@ permissions:
|
||||
jobs:
|
||||
update-packages-index:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event_workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Central Packages Repo
|
||||
@@ -23,105 +28,82 @@ jobs:
|
||||
token: ${{ secrets.PACKAGES_REPO_TOKEN }}
|
||||
path: 'central-packages'
|
||||
|
||||
# --- СКАЧИВАНИЕ АРТЕФАКТОВ ИЗ РЕЛИЗА ---
|
||||
- name: Import GPG Key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
|
||||
- name: Download Arch package artifact
|
||||
# --- Загрузка артефактов (сохранена прежняя логика) ---
|
||||
- name: Download Artifacts
|
||||
if: ${{ github.event_name == 'workflow_run' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: arch-package
|
||||
path: central-packages/archlinux/x86_64/
|
||||
github-token: ${{ secrets.PACKAGES_REPO_TOKEN }}
|
||||
path: central-packages/
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Download Debian package artifact
|
||||
if: ${{ github.event_name == 'workflow_run' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: debian-package
|
||||
path: central-packages/debian/
|
||||
github-token: ${{ secrets.PACKAGES_REPO_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
continue-on-error: true
|
||||
- name: Download Packages from Release Assets
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
gh release download "$TAG" --repo dexoron/dcr --pattern "*.pkg.tar.zst" --dir central-packages/archlinux/x86_64/ --clobber
|
||||
gh release download "$TAG" --repo dexoron/dcr --pattern "*.deb" --dir central-packages/debian/ --clobber
|
||||
gh release download "$TAG" --repo dexoron/dcr --pattern "*.rpm" --dir central-packages/fedora/x86_64/ --clobber
|
||||
|
||||
- name: Download RPM package artifact
|
||||
if: ${{ github.event_name == 'workflow_run' }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: rpm-package
|
||||
path: central-packages/rpm/x86_64/
|
||||
github-token: ${{ secrets.PACKAGES_REPO_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
continue-on-error: true
|
||||
# --- ИНДЕКСАЦИЯ И ПОДПИСЬ ---
|
||||
|
||||
# --- ИНДЕКСАЦИЯ REPO ---
|
||||
|
||||
- name: Update Pacman Database (Inside Arch Container)
|
||||
- name: Update Pacman Database
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v ${{ github.workspace }}/central-packages:/packages \
|
||||
-v ~/.gnupg:/root/.gnupg \
|
||||
archlinux:latest \
|
||||
bash -c '
|
||||
cd /packages/archlinux/x86_64 && \
|
||||
rm -f dexoron.db dexoron.files && \
|
||||
shopt -s nullglob
|
||||
packages=(*.pkg.tar.zst)
|
||||
if [ ${#packages[@]} -gt 0 ]; then
|
||||
echo "Found ${#packages[@]} Arch packages. Updating database..."
|
||||
repo-add dexoron.db.tar.gz "${packages[@]}" && \
|
||||
ln -sf dexoron.db.tar.gz dexoron.db && \
|
||||
ln -sf dexoron.files.tar.gz dexoron.files
|
||||
else
|
||||
echo "No .pkg.tar.zst files found. Skipping."
|
||||
fi
|
||||
packages=(*.pkg.tar.zst) && \
|
||||
repo-add --sign dexoron.db.tar.gz "${packages[@]}" && \
|
||||
ln -sf dexoron.db.tar.gz dexoron.db && \
|
||||
ln -sf dexoron.files.tar.gz dexoron.files
|
||||
'
|
||||
- name: Fix GPG permissions after Docker
|
||||
run: sudo chown -R runner:runner ~/.gnupg
|
||||
|
||||
- name: Update Debian APT Repository Index
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y dpkg-dev
|
||||
cd central-packages/debian
|
||||
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
|
||||
|
||||
shopt -s nullglob
|
||||
deb_packages=(*.deb)
|
||||
echo "Origin: Dexoron Packages" > Release
|
||||
echo "Label: Dexoron Packages" >> Release
|
||||
echo "Suite: stable" >> Release
|
||||
echo "Codename: stable" >> Release
|
||||
echo "Architectures: amd64" >> Release
|
||||
echo "Components: main" >> Release
|
||||
echo "Description: Dexoron binary package repository" >> Release
|
||||
echo "Date: $(date -uR)" >> Release
|
||||
echo "SHA256:" >> Release
|
||||
echo " $(sha256sum Packages.gz | awk '{print $1" "$2}')" >> Release
|
||||
|
||||
if [ ${#deb_packages[@]} -gt 0 ]; then
|
||||
echo "Found ${#deb_packages[@]} Debian packages. Generating Packages.gz..."
|
||||
# Генерируем индекс для плоского (flat) репозитория
|
||||
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
|
||||
else
|
||||
echo "No .deb files found. Skipping Debian index update."
|
||||
fi
|
||||
gpg --batch --yes --pinentry-mode loopback --local-user 74322D39483E9C9E8C86C0AA0770A5C2A2A82DAC --detach-sign --armor --output Release.gpg Release
|
||||
gpg --batch --yes --pinentry-mode loopback --local-user 74322D39483E9C9E8C86C0AA0770A5C2A2A82DAC --clearsign --output InRelease Release
|
||||
|
||||
- name: Update Fedora DNF Repository Index
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y createrepo-c
|
||||
cd central-packages/rpm/x86_64
|
||||
|
||||
shopt -s nullglob
|
||||
rpm_packages=(*.rpm)
|
||||
|
||||
if [ ${#rpm_packages[@]} -gt 0 ]; then
|
||||
echo "Found ${#rpm_packages[@]} RPM packages. Generating repodata..."
|
||||
# Пересоздаем метаданные для dnf
|
||||
createrepo_c .
|
||||
else
|
||||
echo "No .rpm files found. Skipping Fedora index update."
|
||||
fi
|
||||
|
||||
# --- ПУШ ИЗМЕНЕНИЙ ---
|
||||
cd central-packages/fedora/x86_64
|
||||
createrepo_c .
|
||||
gpg --batch --yes --detach-sign --armor --output repodata/repomd.xml.asc repodata/repomd.xml
|
||||
|
||||
- name: Push to dexoron/packages
|
||||
run: |
|
||||
cd central-packages
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Проверяем изменения во всех трех целевых папках
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add archlinux/ debian/ rpm/
|
||||
git commit -m "chore: update repositories index (arch, debian, rpm)"
|
||||
git add .
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: update and sign repositories"
|
||||
git push
|
||||
else
|
||||
echo "No changes detected in repository databases."
|
||||
fi
|
||||
76
.github/workflows/release.yml
vendored
76
.github/workflows/release.yml
vendored
@@ -100,10 +100,28 @@ jobs:
|
||||
name: release-notes
|
||||
path: .github/release-notes.md
|
||||
|
||||
# ── Create Release (prevent race conditions) ───────────────────────────────
|
||||
create-release:
|
||||
needs: [setup, notes]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Release Notes
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-notes
|
||||
path: .github
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.setup.outputs.tag }}
|
||||
body_path: .github/release-notes.md
|
||||
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
|
||||
|
||||
# ── Linux Binaries Matrix ──────────────────────────────────────────────────
|
||||
build-linux:
|
||||
if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }}
|
||||
needs: [setup, notes]
|
||||
needs: [setup, notes, create-release]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -129,6 +147,12 @@ jobs:
|
||||
pip install cargo-zigbuild
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Setup Zig
|
||||
if: matrix.target != 'x86_64-unknown-linux-gnu'
|
||||
uses: mlugg/setup-zig@v1
|
||||
with:
|
||||
version: 0.13.0
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -222,14 +246,15 @@ jobs:
|
||||
- name: Build Arch Package inside Container
|
||||
if: matrix.package_type == 'arch'
|
||||
run: |
|
||||
PKGVER='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_ORIG='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_CLEAN="${PKGVER_ORIG//-/.}"
|
||||
TAG='${{ needs.setup.outputs.tag }}'
|
||||
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/${TAG}.tar.gz"
|
||||
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
|
||||
mkdir -p arch-build
|
||||
cat > arch-build/PKGBUILD <<PKG
|
||||
pkgname=dcr
|
||||
pkgver=${PKGVER}
|
||||
pkgver=${PKGVER_CLEAN}
|
||||
pkgrel=1
|
||||
pkgdesc="Cargo-like utility to manage C/C++ projects"
|
||||
arch=('x86_64')
|
||||
@@ -238,14 +263,14 @@ jobs:
|
||||
depends=('gcc-libs')
|
||||
makedepends=('cargo')
|
||||
options=('!lto')
|
||||
source=("\$pkgname-\$pkgver.tar.gz::\$url/archive/refs/tags/v\$pkgver.tar.gz")
|
||||
source=("\$pkgname-\$pkgver.tar.gz::\$url/archive/refs/tags/${TAG}.tar.gz")
|
||||
sha256sums=('${SHA256}')
|
||||
build() {
|
||||
cd "\$srcdir/\$pkgname-\$pkgver"
|
||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||
cargo build --release --locked
|
||||
}
|
||||
package() {
|
||||
cd "\$srcdir/\$pkgname-\$pkgver"
|
||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
|
||||
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
|
||||
}
|
||||
@@ -273,7 +298,7 @@ jobs:
|
||||
|
||||
# ── Publish Packages to Release (Out of Matrix) ────────────────────────────
|
||||
publish-dexoron-packages:
|
||||
needs: [setup, notes, build-linux-packages]
|
||||
needs: [setup, notes, build-linux-packages, create-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Release Notes
|
||||
@@ -302,7 +327,7 @@ jobs:
|
||||
# ── macOS Matrix ───────────────────────────────────────────────────────────
|
||||
build-macos:
|
||||
if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }}
|
||||
needs: [setup, notes]
|
||||
needs: [setup, notes, create-release]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -348,7 +373,7 @@ jobs:
|
||||
# ── BSD Systems Matrix ─────────────────────────────────────────────────────
|
||||
build-bsd:
|
||||
if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }}
|
||||
needs: [setup, notes]
|
||||
needs: [setup, notes, create-release]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -408,9 +433,10 @@ jobs:
|
||||
with:
|
||||
usesh: true
|
||||
run: |
|
||||
export PATH="/usr/pkg/bin:/usr/pkg/sbin:$PATH"
|
||||
pkg_add rust perl gmake
|
||||
export MAKE=gmake
|
||||
mkdir -p /tmp/bin
|
||||
ln -sf /usr/pkg/bin/gmake /tmp/bin/make
|
||||
export PATH="/tmp/bin:/usr/pkg/bin:/usr/pkg/sbin:$PATH"
|
||||
cargo build --release
|
||||
|
||||
- name: Prepare NetBSD Artifact
|
||||
@@ -420,6 +446,8 @@ jobs:
|
||||
cp target/release/dcr "$ASSET"
|
||||
echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV"
|
||||
|
||||
|
||||
|
||||
# --- UPLOAD TARGET ---
|
||||
- name: Upload BSD Asset to Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
@@ -432,7 +460,7 @@ jobs:
|
||||
# ── Windows Matrix ─────────────────────────────────────────────────────────
|
||||
build-windows:
|
||||
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
|
||||
needs: [setup, notes]
|
||||
needs: [setup, notes, create-release]
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -499,14 +527,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PKGVER='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_ORIG='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_CLEAN="${PKGVER_ORIG//-/.}"
|
||||
SRC_URL='${{ steps.meta.outputs.source_url }}'
|
||||
SHA256='${{ steps.meta.outputs.sha256 }}'
|
||||
|
||||
mkdir -p packages/aur/dcr
|
||||
cat > packages/aur/dcr/PKGBUILD <<PKG
|
||||
pkgname=dcr
|
||||
pkgver=${PKGVER}
|
||||
pkgver=${PKGVER_CLEAN}
|
||||
pkgrel=1
|
||||
pkgdesc="Cargo-like utility to manage C/C++ projects"
|
||||
arch=('x86_64' 'aarch64')
|
||||
@@ -519,11 +548,11 @@ jobs:
|
||||
'gcc: build C/C++ projects with GCC'
|
||||
'clang: build C/C++ projects with Clang'
|
||||
)
|
||||
source=("\$pkgname-\$pkgver.tar.gz::\$url/archive/refs/tags/v\$pkgver.tar.gz")
|
||||
source=("\$pkgname-\$pkgver.tar.gz::\${SRC_URL}")
|
||||
sha256sums=('${SHA256}')
|
||||
|
||||
build() {
|
||||
cd "\$srcdir/\$pkgname-\$pkgver"
|
||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||
export CARGO_PROFILE_RELEASE_LTO=false
|
||||
export RUSTFLAGS="\${RUSTFLAGS:-}"
|
||||
export RUSTFLAGS="\${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
|
||||
@@ -531,7 +560,7 @@ jobs:
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "\$srcdir/\$pkgname-\$pkgver"
|
||||
cd "\$srcdir/\$pkgname-${PKGVER_ORIG}"
|
||||
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
|
||||
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
|
||||
}
|
||||
@@ -540,7 +569,7 @@ jobs:
|
||||
cat > packages/aur/dcr/.SRCINFO <<SRC
|
||||
pkgbase = dcr
|
||||
pkgdesc = Cargo-like utility to manage C/C++ projects
|
||||
pkgver = ${PKGVER}
|
||||
pkgver = ${PKGVER_CLEAN}
|
||||
pkgrel = 1
|
||||
url = https://github.com/dexoron/dcr
|
||||
arch = x86_64
|
||||
@@ -551,7 +580,7 @@ jobs:
|
||||
optdepends = gcc: build C/C++ projects with GCC
|
||||
optdepends = clang: build C/C++ projects with Clang
|
||||
options = !lto
|
||||
source = dcr-${PKGVER}.tar.gz::${SRC_URL}
|
||||
source = dcr-${PKGVER_CLEAN}.tar.gz::${SRC_URL}
|
||||
sha256sums = ${SHA256}
|
||||
|
||||
pkgname = dcr
|
||||
@@ -611,14 +640,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PKGVER='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_ORIG='${{ needs.setup.outputs.version }}'
|
||||
PKGVER_CLEAN="${PKGVER_ORIG//-/.}"
|
||||
BIN_URL='${{ steps.meta.outputs.bin_url }}'
|
||||
SHA256='${{ steps.meta.outputs.sha256 }}'
|
||||
|
||||
mkdir -p packages/aur/dcr-bin
|
||||
cat > packages/aur/dcr-bin/PKGBUILD <<PKG
|
||||
pkgname=dcr-bin
|
||||
pkgver=${PKGVER}
|
||||
pkgver=${PKGVER_CLEAN}
|
||||
pkgrel=1
|
||||
pkgdesc="Cargo-like utility to manage C/C++ projects (pre-compiled musl binary)"
|
||||
arch=('x86_64')
|
||||
@@ -641,7 +671,7 @@ jobs:
|
||||
cat > packages/aur/dcr-bin/.SRCINFO <<SRC
|
||||
pkgbase = dcr-bin
|
||||
pkgdesc = Cargo-like utility to manage C/C++ projects (pre-compiled musl binary)
|
||||
pkgver = ${PKGVER}
|
||||
pkgver = ${PKGVER_CLEAN}
|
||||
pkgrel = 1
|
||||
url = https://github.com/dexoron/dcr
|
||||
arch = x86_64
|
||||
@@ -650,7 +680,7 @@ jobs:
|
||||
conflicts = dcr
|
||||
optdepends = gcc: build C/C++ projects with GCC
|
||||
optdepends = clang: build C/C++ projects with Clang
|
||||
source_x86_64 = dcr-${PKGVER}::${BIN_URL}
|
||||
source_x86_64 = dcr-${PKGVER_CLEAN}::${BIN_URL}
|
||||
sha256sums_x86_64 = ${SHA256}
|
||||
|
||||
pkgname = dcr-bin
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## [0.7.0] - 2026-05-30
|
||||
|
||||
Added:
|
||||
|
||||
- **OpenBSD and NetBSD target support** — full platform routing with dynamically generated target triples using `std::env::consts::ARCH` and `std::env::consts::OS`. Affects `build`, `run`, `clean`, and the platform module.
|
||||
- **`src/platform/bsd.rs`** — new dedicated BSD platform module shared by FreeBSD, OpenBSD, NetBSD, providing `bin_path`, `lib_path`, `elf_path`, `efi_path`, and `shared_lib_path`.
|
||||
- **`build.out_dir` configuration option** — custom output directory that overrides the default `target/<triple>/<profile>` path for final artifacts. Supported in `build`, `run`, and config validation.
|
||||
- **`dcr fmt` command** — new CLI command that formats all C/C++ source files (`*.c`, `*.cpp`, `*.h`, `*.hpp`) in `src/` and `tests/` using `clang-format`.
|
||||
- **Incremental linking** — `needs_link()` function in `common.rs` that checks whether any object file is newer than the linked output, skipping unnecessary re-linking. Implemented for all backends (`unix_cc`, `msvc`, `gas`, `nasm`).
|
||||
- **`build.kind = "none"` and `"custom"`** — two new project kind types for special build scenarios where no standard artifact is produced.
|
||||
- **`install_bsd.sh`** — dedicated POSIX-compliant install script for BSD systems (FreeBSD, OpenBSD, NetBSD) with binary download and source build modes.
|
||||
- **Linux ARM64 support in `install.sh`** — added `Linux:aarch64|Linux:arm64` target triple detection for pre-built binary downloads.
|
||||
- **BSD OS detection in `install.sh`** — FreeBSD, OpenBSD, NetBSD detection and target triple resolution.
|
||||
- **`rust-toolchain.toml`** — explicit `stable` channel toolchain pinning.
|
||||
- **Integration tests** — `build_with_target_config` (verifies `build.target = "linux"`) and `build_with_out_dir` (verifies custom output directory).
|
||||
- **`get_build_string_with_profile` made `pub`** — so `run.rs` can resolve custom output directory configuration.
|
||||
|
||||
Changed:
|
||||
|
||||
- **`build.target` semantics changed** — now strictly holds the target triple (e.g. `x86_64-unknown-linux-gnu`) or a short name (`linux`, `macos`, `windows`). No longer doubles as a custom output directory — that functionality moved to `build.out_dir`.
|
||||
- **`build.standard` made optional** — changed from `String` to `Option<String>`. Validation only enforces non-empty for non-ASM languages. Skipped in `dcr.toml` output when empty.
|
||||
- **`dcr run` with `out_dir`** — now resolves target directory respecting `build.out_dir` when configured, via `get_build_string_with_profile()` from `build.rs`.
|
||||
- **`collect_sources()` returns empty vec** instead of error when no source files are found, allowing `kind = "none"` or `kind = "custom"` projects to have no source files.
|
||||
- **CI/CD release workflow refactored** — `git2` made target-specific (no vendored-openssl on Windows), Zig-based cross-compilation for non-x86_64 Linux targets, Arch Linux package version sanitization (dashes → dots), NetBSD `gmake` symlink.
|
||||
- **README compatibility table** — FreeBSD, OpenBSD, NetBSD build/runtime status upgraded from community/best-effort to officially supported.
|
||||
- **Documentation updated** — `build-section.md` documents new `build.target` and `build.out_dir` fields. `target-directory.md` rewritten to clarify the distinction.
|
||||
- **Dev channel dependency check in `install.sh`** — checks for `python3` or `jq` before attempting dev channel installations.
|
||||
|
||||
Fixed:
|
||||
|
||||
- **`dcr run` stdout/stderr not inherited** — child process output was captured and manually printed, breaking interactive programs. Fixed by switching to `Command::status()`.
|
||||
- **Release CI race condition** — build matrix jobs could upload assets to a release that did not yet exist. Fixed by adding a dedicated `create-release` job.
|
||||
- **GHA release — stable toolchain override** — resolved Rust toolchain override issues in CI.
|
||||
- **Arch Linux package version sanitization** — version strings with dashes (e.g. `0.7.0-dev`) are invalid for Arch Linux `pkgver`. Fixed by replacing dashes with dots.
|
||||
- **GPG permissions after Docker** — Docker operations changed GPG directory ownership. Fixed by running `chown` after Docker commands.
|
||||
- **RPM package artifact paths** — RPM artifacts placed under `rpm/x86_64/` instead of `fedora/x86_64/`. Corrected in Dexoron Packages Index workflow.
|
||||
- **`install.sh` JSON parsing robustness** — added `jq` as primary parser with `python3` fallback for dev channel release lookups.
|
||||
|
||||
Removed:
|
||||
|
||||
- **`format_roots()` helper** — removed from `common.rs`. Was only used by the old error path in `collect_sources()`.
|
||||
- **Individual per-distro artifact download steps** — three separate `actions/download-artifact` steps replaced with unified `gh release download --clobber`.
|
||||
|
||||
## [0.6.9] - 2026-05-28
|
||||
|
||||
Added:
|
||||
|
||||
@@ -33,7 +33,7 @@ DCR supports cross-compilation via `--target <triple>` or short names:
|
||||
- Configure multiple targets in `dcr.toml`:
|
||||
|
||||
```toml
|
||||
[build.targets]
|
||||
[build]
|
||||
targets = ["linux", "macos"]
|
||||
|
||||
[build.linux]
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -112,7 +112,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dcr"
|
||||
version = "0.6.9"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"git2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dcr"
|
||||
version = "0.6.9"
|
||||
version = "0.7.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
|
||||
@@ -42,7 +42,10 @@ sha2 = "0.10"
|
||||
toml = "1.0.3"
|
||||
serde_json = "1"
|
||||
ctrlc = "3"
|
||||
git2 = { version = "0.20.4", features = ["vendored-libgit2", "vendored-openssl"] }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
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"] }
|
||||
50
README.md
50
README.md
@@ -33,6 +33,32 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>Linux (Dexoron Packages)</b></td>
|
||||
<td>
|
||||
|
||||
See https://dcr.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>
|
||||
|
||||
@@ -71,6 +97,16 @@ cargo install dcr
|
||||
curl -fsSL https://dcr.dexoron.su/install.sh | bash
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>BSD (script)</b></td>
|
||||
<td>
|
||||
|
||||
```sh
|
||||
curl -fsSL https://dcr.dexoron.su/install_bsd.sh | bash
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -142,15 +178,21 @@ kind = "bin"
|
||||
|---|---|
|
||||
| `dcr new <name>` | Create a new project |
|
||||
| `dcr init` | Initialize current directory |
|
||||
| `dcr add <dep>` | Add a dependency |
|
||||
| `dcr build [--release]` | Build the project |
|
||||
| `dcr run [--release]` | Build and run |
|
||||
| `dcr clean` | Remove build artifacts |
|
||||
| `dcr test` | Run project tests |
|
||||
| `dcr fmt` | Format C/C++ sources (clang-format) |
|
||||
| `dcr tree` | View dependency tree |
|
||||
| `dcr gen <vscode\|clion\|compile-commands>` | Generate IDE integration |
|
||||
| `dcr setup` | Initialize DCR registry |
|
||||
|
||||
other in [docs/](docs/) and `dcr --help`
|
||||
|
||||
---
|
||||
|
||||
## Platforms
|
||||
## Platforms(pre-build source)
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -159,9 +201,9 @@ other in [docs/](docs/) and `dcr --help`
|
||||
| Linux | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
||||
| macOS | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Windows | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
||||
| FreeBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||
| OpenBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||
| NetBSD | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ |
|
||||
| FreeBSD | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| OpenBSD | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
| NetBSD | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
✅ officially supported · ⚠️ community / best-effort · ❌ not supported
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
* [dcr clean](commands/dcr-clean.md)
|
||||
* [dcr gen](commands/dcr-gen.md)
|
||||
* [dcr test](commands/dcr-test.md)
|
||||
* [dcr fmt](commands/dcr-fmt.md)
|
||||
* [dcr tree](commands/dcr-tree.md)
|
||||
|
||||
## Dependencies & Build
|
||||
|
||||
@@ -30,9 +30,12 @@ dcr build --debug --clean
|
||||
- `build.compiler`
|
||||
- `build.language`
|
||||
- `build.standard`
|
||||
- `build.cxx_standard`
|
||||
- `build.kind`
|
||||
- `build.target`
|
||||
- `build.out_dir`
|
||||
- `build.platform`
|
||||
- `package.type`
|
||||
- `build.cflags`
|
||||
- `build.ldflags`
|
||||
- `build.ldscript`
|
||||
@@ -58,8 +61,9 @@ dcr build --debug --clean
|
||||
|
||||
- Profile flag (`--debug` / `--release`) can appear in any argument position (duplicates are rejected).
|
||||
- Unknown profile flags return an error.
|
||||
- `--target <triple>` builds for specified target (short names: `linux`, `macos`, `windows`).
|
||||
- `--target <triple>` builds for specified target (short names: `linux`, `macos`, `windows`, `freebsd`, `openbsd`, `netbsd`).
|
||||
- `--verbose` prints the compiler/linker command lines to stderr (also works via `DCR_DEBUG` env var).
|
||||
- `--workspace <name>` filters build to a specific workspace member.
|
||||
- If no `--target`, builds for `build.targets` or native.
|
||||
- Incremental rebuild checks source/object mtime and tracked header dependencies.
|
||||
- `--force` skips build cache checks and recompiles.
|
||||
|
||||
19
docs/commands/dcr-fmt.md
Normal file
19
docs/commands/dcr-fmt.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# dcr fmt
|
||||
|
||||
Formats all C/C++ source files using `clang-format`.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
dcr fmt
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
1. Scans `src/` and `tests/` directories recursively.
|
||||
2. Formats all C/C++ source and header files (`.c`, `.cpp`, `.cxx`, `.cc`, `.h`, `.hpp`, `.hxx`, `.hh`) using `clang-format`.
|
||||
3. Uses `.clang-format` if present in the project root, otherwise falls back to LLVM style.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `clang-format` must be installed and available in PATH.
|
||||
@@ -23,11 +23,12 @@ dcr run --debug --clean
|
||||
|
||||
## Restrictions
|
||||
|
||||
- If `build.kind` (or `build.{profile}.kind`) is `"staticlib"` or `"sharedlib"`, `run` exits with error.
|
||||
- If `build.kind` (or `build.{profile}.kind`) is `"staticlib"`, `"sharedlib"`, `"efi"`, or `"elf"`, `run` exits with error.
|
||||
- The profile is selected from the first argument.
|
||||
- `--target <triple>` runs the binary for specified target (short names: `linux`, `macos`, `windows`).
|
||||
- `--target <triple>` runs the binary for specified target (short names: `linux`, `macos`, `windows`, `freebsd`, `openbsd`, `netbsd`).
|
||||
- `--force` skips build cache checks and recompiles.
|
||||
- `--clean` removes `target/<profile>` and `build.clean` paths before building.
|
||||
- `--workspace <name>` filters build to a specific workspace member.
|
||||
|
||||
## `run.cmd` variables
|
||||
|
||||
|
||||
@@ -30,10 +30,13 @@ You can also add `--force` or `--clean` to `build`/`run`.
|
||||
|
||||
## Artifacts
|
||||
|
||||
By default:
|
||||
By default (for `kind = "bin"`):
|
||||
|
||||
- `target/debug/<name>` (or `.exe` on Windows)
|
||||
- `target/release/<name>` (or `.exe` on Windows)
|
||||
- `target/<target>/debug/<name>` (or `.exe` on Windows)
|
||||
- `target/<target>/release/<name>` (or `.exe` on Windows)
|
||||
|
||||
Where `<target>` is `native` or a target triple.
|
||||
Use `build.out_dir` to override the output directory.
|
||||
|
||||
Default compiler flags:
|
||||
|
||||
@@ -58,6 +61,14 @@ Shared library mode (`kind = "sharedlib"`):
|
||||
- macOS: `lib<name>.dylib`
|
||||
- Windows: `<name>.dll`
|
||||
|
||||
UEFI executable mode (`kind = "efi"`):
|
||||
|
||||
- All platforms: `<name>.efi`
|
||||
|
||||
Bare-metal ELF mode (`kind = "elf"`):
|
||||
|
||||
- All platforms: `<name>` (no extension)
|
||||
|
||||
## Profile overrides
|
||||
|
||||
Use `[build.debug]` and `[build.release]` to override build settings per profile.
|
||||
|
||||
@@ -13,7 +13,8 @@ kind = "bin"
|
||||
# optional:
|
||||
# inherit = true # Inherit from base (default true)
|
||||
# targets = ["linux", "macos"] # Multiple targets to build
|
||||
# target = "./dist"
|
||||
# out_dir = "./dist"
|
||||
# target = "x86_64-unknown-linux-gnu"
|
||||
# platform = "x86_64"
|
||||
# cflags = ["-Wall"]
|
||||
# ldflags = ["-lm"]
|
||||
@@ -31,17 +32,17 @@ kind = "bin"
|
||||
|
||||
- `language` (string or array, required): `c`, `c++`, `cpp`, `cxx`, or `asm`.
|
||||
Mixed languages can be specified as an array, for example `["c", "c++", "asm"]`.
|
||||
- `standard` (string, required): C language standard passed to compiler (e.g. `c11`, `c17`, `gnu11`).
|
||||
- `standard` (string, optional): C language standard passed to compiler (e.g. `c11`, `c17`, `gnu11`). Defaults to `c11`.
|
||||
- `cxx_standard` (string, optional): C++ language standard passed to compiler for `.cpp`/`.cxx`/`.cc` files
|
||||
(e.g. `c++17`, `gnu++17`, `c++20`). When set, overrides `standard` for C++ sources.
|
||||
- `compiler` (string, required): compiler command (for example `clang`, `gcc`, `cl`).
|
||||
- `kind` (string, required): `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
|
||||
- `kind` (string, optional): `bin`, `staticlib`, `sharedlib`, `efi`, `elf`, `none`, or `custom`. Defaults to `bin`.
|
||||
- `filename` (string, optional): custom output filename (without extension). Overrides `package.name` for the final artifact.
|
||||
- `extension` (string, optional): custom file extension without the leading dot (e.g. `BIN`, `efi`, `EFI`). Combined with `filename`.
|
||||
- `inherit` (bool, optional): inherit settings from base `[build]` (default `true`). If `false`, only use target/profile specific settings.
|
||||
- `targets` (string array, optional): list of targets to build simultaneously. Supports short names (`linux`, `macos`, `windows`).
|
||||
- `target` (string, optional): custom output directory for final artifact.
|
||||
When set, DCR automatically injects `--target=<value>` into `cflags` (useful for clang cross-compilation).
|
||||
- `target` (string, optional): platform target triple (e.g. `x86_64-unknown-linux-gnu`) or short name (`linux`, `macos`, `windows`). Same as `--target` CLI flag.
|
||||
- `out_dir` (string, optional): custom output directory for final artifact (overrides default `target/<target>/<profile>/`).
|
||||
- `platform` (string, optional): architecture hint for compiler (used for `-march` or `/arch`).
|
||||
- `cflags` (string array, optional): extra compile flags.
|
||||
Supports variable substitution — see [Variable substitution](#variable-substitution) below.
|
||||
|
||||
@@ -35,17 +35,16 @@ Workspace-only root:
|
||||
- `package.name`
|
||||
- `package.version`
|
||||
- `build.language`
|
||||
- `build.standard`
|
||||
- `build.compiler`
|
||||
- `build.kind`
|
||||
|
||||
`build.kind` must be either `bin`, `staticlib`, or `sharedlib`.
|
||||
|
||||
## Optional keys
|
||||
|
||||
- `build.standard` (default: `c11`)
|
||||
- `build.kind` (default: `bin`; valid: `bin`, `staticlib`, `sharedlib`, `efi`, `elf`, `none`, `custom`)
|
||||
- `build.inherit`
|
||||
- `build.targets`
|
||||
- `build.target`
|
||||
- `build.out_dir`
|
||||
- `build.platform`
|
||||
- `build.cflags`
|
||||
- `build.ldflags`
|
||||
|
||||
@@ -18,6 +18,8 @@ frecli = { path = "./lib/frecli", include = ["."], lib = ["."], libs = ["frecli"
|
||||
|
||||
### Registry dependencies
|
||||
Defined by name and version string. DCR automatically resolves the package from the configured registry index.
|
||||
- `version` (string, required): version requirement.
|
||||
- `features` (string array, optional): enable specific features.
|
||||
|
||||
### Git dependencies
|
||||
Defined by repository URL and optionally branch, tag, or revision.
|
||||
|
||||
@@ -23,6 +23,9 @@ dcr run --target linux --release
|
||||
- `linux` → `x86_64-unknown-linux-gnu`
|
||||
- `macos` → `x86_64-apple-darwin`
|
||||
- `windows` → `x86_64-pc-windows-msvc`
|
||||
- `freebsd` → `x86_64-unknown-freebsd`
|
||||
- `openbsd` → `x86_64-unknown-openbsd`
|
||||
- `netbsd` → `x86_64-unknown-netbsd`
|
||||
|
||||
### Full Target Triples
|
||||
DCR supports any valid target triple in the format `<arch>-<vendor>-<os>-<abi>`. Common examples:
|
||||
@@ -32,6 +35,9 @@ DCR supports any valid target triple in the format `<arch>-<vendor>-<os>-<abi>`.
|
||||
- `aarch64-apple-darwin`
|
||||
- `x86_64-pc-windows-msvc`
|
||||
- `i686-pc-windows-gnu`
|
||||
- `x86_64-unknown-freebsd`
|
||||
- `x86_64-unknown-openbsd`
|
||||
- `x86_64-unknown-netbsd`
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -40,11 +46,11 @@ DCR supports any valid target triple in the format `<arch>-<vendor>-<os>-<abi>`.
|
||||
Configure multiple targets to build simultaneously:
|
||||
|
||||
```toml
|
||||
[build.targets]
|
||||
[build]
|
||||
targets = ["linux", "macos", "windows"]
|
||||
|
||||
# Or per profile
|
||||
[build.release.targets]
|
||||
[build.release]
|
||||
targets = ["linux", "macos"]
|
||||
```
|
||||
|
||||
@@ -66,6 +72,10 @@ cflags = ["-Wall", "-O2", "-arch", "x86_64"]
|
||||
[build.windows]
|
||||
compiler = "x86_64-w64-mingw32-gcc"
|
||||
ldflags = ["-static"]
|
||||
|
||||
[build.freebsd]
|
||||
compiler = "clang"
|
||||
cflags = ["-Wall", "-O2"]
|
||||
```
|
||||
|
||||
### Target-Specific Toolchain
|
||||
@@ -99,6 +109,9 @@ cmd = "open ./target/macos/release/myapp.app"
|
||||
|
||||
[run.windows]
|
||||
cmd = "wine ./target/windows/release/myapp.exe"
|
||||
|
||||
[run.freebsd]
|
||||
cmd = "./target/freebsd/release/myapp"
|
||||
```
|
||||
|
||||
## Workspace and Dependencies Inheritance
|
||||
@@ -151,7 +164,10 @@ target/
|
||||
├── x86_64-unknown-linux-gnu/
|
||||
│ ├── debug/
|
||||
│ └── release/
|
||||
└── aarch64-linux-gnu/
|
||||
├── aarch64-linux-gnu/
|
||||
│ ├── debug/
|
||||
│ └── release/
|
||||
└── x86_64-unknown-freebsd/
|
||||
├── debug/
|
||||
└── release/
|
||||
```
|
||||
@@ -200,4 +216,4 @@ dcr build --target windows --release
|
||||
- **Linking errors**: Check `ldflags` and library paths for cross-compilation.
|
||||
- **Header issues**: Include paths may differ; use target-specific `cflags`.
|
||||
|
||||
For more help, see [Troubleshooting map](troubleshooting-map.md).
|
||||
For more help, see [Troubleshooting map](../reference-and-help/troubleshooting-map.md).
|
||||
@@ -26,6 +26,8 @@ json = { git = "https://github.com/nlohmann/json", tag = "v3.11.2" }
|
||||
- `include`: optional include subpaths (absolute or relative to dep root).
|
||||
- `lib`: optional library subpaths (absolute or relative to dep root).
|
||||
- `libs`: optional linker library names.
|
||||
- `features`: optional feature flags for registry dependencies.
|
||||
- `default-features`: whether to include default features (default `true`).
|
||||
|
||||
## Common valid compact forms
|
||||
|
||||
@@ -35,6 +37,8 @@ mylib = { path = "./libs/mylib" }
|
||||
gitlib = { git = "https://github.com/user/repo" }
|
||||
# Shortest form for path
|
||||
simple = "./libs/simple"
|
||||
# Registry with features
|
||||
sdl2 = { version = "2.0.0", features = ["vulkan"] }
|
||||
```
|
||||
|
||||
In these forms DCR will try defaults for include/lib dirs and use the dependency name as the linker library name.
|
||||
|
||||
@@ -2,22 +2,29 @@
|
||||
|
||||
By default, DCR writes final artifacts to `target/<target>/<profile>/` (where `<target>` is `native` or target triple).
|
||||
|
||||
You can override this with `build.target`:
|
||||
You can override this with `build.out_dir`:
|
||||
|
||||
```toml
|
||||
[build]
|
||||
target = "./dist"
|
||||
out_dir = "./dist"
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
- Binary mode:
|
||||
- Linux/macOS: `<target>/<name>`
|
||||
- Windows: `<target>/<name>.exe`
|
||||
- Linux/macOS: `<out_dir>/<name>`
|
||||
- Windows: `<out_dir>/<name>.exe`
|
||||
- Static library mode:
|
||||
- Linux/macOS: `<target>/lib<name>.a`
|
||||
- Windows: `<target>/<name>.lib`
|
||||
- Linux/macOS: `<out_dir>/lib<name>.a`
|
||||
- Windows: `<out_dir>/<name>.lib`
|
||||
|
||||
## Important detail
|
||||
|
||||
`build.target` changes only the final artifact location. Object cache and dependency sync directories still use `target/<target>/<profile>/...`.
|
||||
`build.out_dir` changes only the final artifact location. Object cache and dependency sync directories still use `target/<target>/<profile>/...`.
|
||||
|
||||
## Distinction from platform targets
|
||||
|
||||
- `build.out_dir` — custom **output directory** path.
|
||||
- `build.target` / `--target <triple>` CLI flag — **platform target** for cross-compilation (e.g. `x86_64-unknown-linux-gnu`, or short name `linux`).
|
||||
- `build.targets` (array) — list of **platform targets** to build simultaneously.
|
||||
- `[build.<target>]` sections — **target-specific config overrides** (e.g. `[build.linux]`).
|
||||
|
||||
@@ -37,4 +37,4 @@ project/
|
||||
- `target/<target>/<profile>/lib<name>.a` (or `<name>.lib` on Windows): output for `kind = "staticlib"`.
|
||||
- `target/<target>/<profile>/lib<name>.so` (or `.dylib` on macOS, `<name>.dll` on Windows): output for `kind = "sharedlib"`.
|
||||
|
||||
If `build.target` is set, the final artifact is written to that custom directory, while object cache still stays in `target/<target>/<profile>/obj`.
|
||||
If `build.out_dir` is set, the final artifact is written to that custom directory, while object cache still stays in `target/<target>/<profile>/obj`.
|
||||
|
||||
@@ -13,6 +13,8 @@ dcr clean [--debug|--release] [--all]
|
||||
dcr tree
|
||||
dcr test
|
||||
dcr gen <subcommand>
|
||||
dcr fmt
|
||||
dcr setup
|
||||
dcr --help
|
||||
dcr --version
|
||||
dcr --update
|
||||
@@ -20,11 +22,13 @@ dcr --update
|
||||
|
||||
## Notes on argument parsing
|
||||
|
||||
- `build`/`run` parse flags in any order (`--debug|--release`, `--force`, `--clean`, `--verbose`) and reject duplicates.
|
||||
- `build`/`run` parse flags in any order (`--debug|--release`, `--force`, `--clean`, `--verbose`, `--workspace`) and reject duplicates.
|
||||
- `new` requires exactly one argument.
|
||||
- `init` and `--update` do not accept extra arguments.
|
||||
- `clean` accepts `--debug|--release` and optional `--all`.
|
||||
- `gen` supports `project-info`, `compile-commands`, `vscode`, `clion`.
|
||||
- `fmt` formats C/C++ sources (`.c`, `.cpp`, `.cxx`, `.cc`, `.h`, `.hpp`, `.hxx`, `.hh`) in `src/` and `tests/` using `clang-format`.
|
||||
- `setup` initializes DCR registries and local configuration.
|
||||
- `tree` shows a dependency tree (similar to `cargo tree`).
|
||||
- `test` / `tests` runs the project's tests.
|
||||
|
||||
|
||||
@@ -19,13 +19,14 @@ categories = ["string", "..."] # optional
|
||||
|
||||
[build]
|
||||
language = "c|c++|cpp|cxx|asm" # required (string or array)
|
||||
standard = "string" # required (e.g. "c11", "gnu11")
|
||||
standard = "string" # optional (default "c11")
|
||||
cxx_standard = "string" # optional (e.g. "c++17", "gnu++17")
|
||||
compiler = "string" # required (e.g. "clang", "gcc")
|
||||
kind = "bin|staticlib|sharedlib|efi|elf" # required
|
||||
kind = "bin|staticlib|sharedlib|efi|elf|none|custom" # optional (default "bin")
|
||||
target = "string" # optional (cross-compilation target triple)
|
||||
out_dir = "string" # optional (custom output directory)
|
||||
platform = "string" # optional (architecture hint, -march)
|
||||
type = "string" # optional
|
||||
|
||||
cflags = ["string", "..."] # optional (supports {version} etc.)
|
||||
ldflags = ["string", "..."] # optional (supports {version} etc.)
|
||||
ldscript = "string" # optional (linker script path)
|
||||
@@ -121,14 +122,15 @@ rev = "string" # optional (git commit)
|
||||
include = ["string", "..."] # optional (include dirs)
|
||||
lib = ["string", "..."] # optional (lib dirs)
|
||||
libs = ["string", "..."] # optional (link libs)
|
||||
features = ["string", "..."] # optional (registry features)
|
||||
features = ["string", "..."] # optional (registry features)
|
||||
default-features = true # optional (include default features)
|
||||
```
|
||||
|
||||
## Validation rules
|
||||
|
||||
- `[package]`, `[build]`, `[dependencies]` must exist.
|
||||
- Required string fields must be non-empty.
|
||||
- `build.kind` must be `bin`, `staticlib`, `sharedlib`, `efi`, or `elf`.
|
||||
- `build.kind` must be `bin`, `staticlib`, `sharedlib`, `efi`, `elf`, `none`, or `custom`.
|
||||
- Dependency fields `include/lib/libs` must be string arrays when provided.
|
||||
- `build.exclude`/`build.include` must be string arrays when provided.
|
||||
- `build.roots` must be a string array when provided.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## What commands are available?
|
||||
|
||||
`new`, `init`, `build`, `run`, `clean`, `gen`, `--help`, `--version`, `--update`.
|
||||
`new`, `init`, `add`, `build`, `run`, `clean`, `test`, `fmt`, `tree`, `gen`, `setup`, `--help`, `--version`, `--update`.
|
||||
|
||||
## Is `dcr.toml` mandatory?
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@ Used with `dcr build` and `dcr run`:
|
||||
|
||||
- `--debug`: select debug profile.
|
||||
- `--release`: select release profile.
|
||||
- `--target <triple>`: build for specified target (short names: `linux`, `macos`, `windows`, `freebsd`, `openbsd`, `netbsd`).
|
||||
- `--force`: ignore incremental cache checks and rebuild.
|
||||
- `--clean`: remove `target/<profile>` and `build.clean` paths before build.
|
||||
- `--verbose`: print compiler/linker command lines to stderr.
|
||||
- `--workspace <name>`: filter build to a specific workspace member.
|
||||
|
||||
Notes:
|
||||
|
||||
|
||||
44
install.sh
44
install.sh
@@ -30,8 +30,8 @@ CHANNEL=""
|
||||
|
||||
check_os() {
|
||||
case "$(uname -s)" in
|
||||
Linux|Darwin) ;;
|
||||
*) error "Only Linux and macOS are supported"; exit 1 ;;
|
||||
Linux|Darwin|FreeBSD|OpenBSD|NetBSD) ;;
|
||||
*) error "Only Linux, macOS, and BSD systems are supported"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -41,15 +41,26 @@ detect_target() {
|
||||
arch="$(uname -m)"
|
||||
|
||||
case "$os:$arch" in
|
||||
Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;;
|
||||
Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;;
|
||||
Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;;
|
||||
Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;;
|
||||
Linux:aarch64|Linux:arm64) TARGET_TRIPLE="aarch64-unknown-linux-gnu" ;;
|
||||
Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;;
|
||||
Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;;
|
||||
FreeBSD:x86_64|FreeBSD:amd64) TARGET_TRIPLE="x86_64-unknown-freebsd" ;;
|
||||
OpenBSD:x86_64|OpenBSD:amd64) TARGET_TRIPLE="x86_64-unknown-openbsd" ;;
|
||||
NetBSD:x86_64|NetBSD:amd64) TARGET_TRIPLE="x86_64-unknown-netbsd" ;;
|
||||
|
||||
*) error "Unsupported platform: $os/$arch"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_common_dependencies() {
|
||||
command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
|
||||
if [[ "$CHANNEL" == "dev" ]]; then
|
||||
if ! command -v python3 >/dev/null 2>&1 && ! command -v jq >/dev/null 2>&1; then
|
||||
error "For dev installations, either 'python3' or 'jq' must be installed to parse GitHub API response."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_build_dependencies() {
|
||||
@@ -83,15 +94,16 @@ select_install_mode() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Возвращает JSON нужного релиза в stdout
|
||||
# Returns JSON of the requested release to stdout
|
||||
fetch_release_json() {
|
||||
if [[ "$CHANNEL" == "dev" ]]; then
|
||||
log "Looking for latest dev (pre-release)..."
|
||||
local json
|
||||
local json result
|
||||
json="$(curl -fsSL "$GITHUB_API_ALL")"
|
||||
# Первый pre-release в списке
|
||||
local result
|
||||
result="$(printf '%s' "$json" | python3 - <<'EOF'
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
result="$(printf '%s' "$json" | jq -e -c 'map(select(.prerelease == true))[0]')"
|
||||
else
|
||||
result="$(printf '%s' "$json" | python3 - <<'EOF'
|
||||
import sys, json
|
||||
releases = json.load(sys.stdin)
|
||||
pre = [r for r in releases if r.get("prerelease", False)]
|
||||
@@ -101,7 +113,9 @@ else:
|
||||
print(json.dumps(pre[0]), end="")
|
||||
EOF
|
||||
)"
|
||||
if [[ -z "$result" || "$result" == "{}" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -z "$result" || "$result" == "{}" || "$result" == "null" ]]; then
|
||||
error "No dev (pre-release) found on GitHub"
|
||||
exit 1
|
||||
fi
|
||||
@@ -123,9 +137,9 @@ download_binary() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Версия без ведущего 'v'
|
||||
# Version without leading 'v'
|
||||
version="${tag#v}"
|
||||
# Имя бинарника: dcr-<triple>-<version>
|
||||
# Binary name: dcr-<triple>-<version>
|
||||
asset_name="dcr-${TARGET_TRIPLE}-${version}"
|
||||
|
||||
log "Fetching release ${tag} (channel: ${CHANNEL})..."
|
||||
@@ -149,7 +163,7 @@ prepare_sources() {
|
||||
rm -rf "$TMPDIR"
|
||||
git clone --depth 1 "$REPO_URL" "$TMPDIR"
|
||||
if [[ "$CHANNEL" == "dev" ]]; then
|
||||
# Клонируем dev ветку, если она есть
|
||||
# Clone dev branch if it exists
|
||||
git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \
|
||||
git clone --depth 1 "$REPO_URL" "$TMPDIR"
|
||||
fi
|
||||
@@ -193,8 +207,8 @@ main() {
|
||||
|
||||
check_os
|
||||
detect_target
|
||||
check_common_dependencies
|
||||
select_channel
|
||||
check_common_dependencies
|
||||
select_install_mode
|
||||
cleanup
|
||||
|
||||
|
||||
223
install_bsd.sh
Executable file
223
install_bsd.sh
Executable file
@@ -0,0 +1,223 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
TMPDIR="/tmp/dcr-install"
|
||||
INSTALL_PATH="$HOME/.local/share/dcr"
|
||||
BINPATH="$HOME/.local/bin"
|
||||
LOGFILE="$HOME/.cache/dcr-install.log"
|
||||
REPO_URL="https://github.com/dexoron/dcr"
|
||||
GITHUB_API_LATEST="https://api.github.com/repos/dexoron/dcr/releases/latest"
|
||||
GITHUB_API_ALL="https://api.github.com/repos/dexoron/dcr/releases"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
mkdir -p "$(dirname "$LOGFILE")"
|
||||
|
||||
log() { printf "${BLUE}[%s]${NC} %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOGFILE"; }
|
||||
success() { printf "${GREEN}✔ %s${NC}\n" "$1" | tee -a "$LOGFILE"; }
|
||||
warn() { printf "${YELLOW}⚠ %s${NC}\n" "$1" | tee -a "$LOGFILE"; }
|
||||
error() { printf "${RED}✖ %s${NC}\n" "$1" | tee -a "$LOGFILE" >&2; }
|
||||
|
||||
INSTALL_MODE=""
|
||||
CHANNEL=""
|
||||
|
||||
check_os() {
|
||||
case "$(uname -s)" in
|
||||
FreeBSD|OpenBSD|NetBSD|DragonFly) ;;
|
||||
*) error "Only BSD systems (FreeBSD, OpenBSD, NetBSD, DragonFly) are supported by this script"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_target() {
|
||||
local os arch
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
|
||||
case "$os:$arch" in
|
||||
FreeBSD:x86_64|FreeBSD:amd64) TARGET_TRIPLE="x86_64-unknown-freebsd" ;;
|
||||
OpenBSD:x86_64|OpenBSD:amd64) TARGET_TRIPLE="x86_64-unknown-openbsd" ;;
|
||||
NetBSD:x86_64|NetBSD:amd64) TARGET_TRIPLE="x86_64-unknown-netbsd" ;;
|
||||
DragonFly:x86_64|DragonFly:amd64) TARGET_TRIPLE="x86_64-unknown-dragonfly" ;;
|
||||
*) error "Unsupported BSD platform: $os/$arch"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_common_dependencies() {
|
||||
command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
|
||||
if [ "$CHANNEL" = "dev" ]; then
|
||||
if ! command -v python3 >/dev/null 2>&1 && ! command -v jq >/dev/null 2>&1; then
|
||||
error "For dev installations, either 'python3' or 'jq' must be installed to parse GitHub API response."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_build_dependencies() {
|
||||
command -v git >/dev/null 2>&1 || { error "git is not installed"; exit 1; }
|
||||
command -v cargo >/dev/null 2>&1 || { error "cargo is not installed"; exit 1; }
|
||||
}
|
||||
|
||||
select_channel() {
|
||||
echo "Choose channel:"
|
||||
echo " 1) Latest stable release (default)"
|
||||
echo " 2) Latest dev (pre-release)"
|
||||
printf "Enter 1 or 2 [1]: "
|
||||
read -r choice
|
||||
|
||||
case "${choice:-1}" in
|
||||
1) CHANNEL="stable" ;;
|
||||
2) CHANNEL="dev" ;;
|
||||
*) error "Unknown option"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
select_install_mode() {
|
||||
echo "Choose installation mode:"
|
||||
echo " 1) Download prebuilt binary from GitHub Release (recommended)"
|
||||
echo " 2) Build from git"
|
||||
printf "Enter 1 or 2 [1]: "
|
||||
read -r choice
|
||||
|
||||
case "${choice:-1}" in
|
||||
1) INSTALL_MODE="release" ;;
|
||||
2) INSTALL_MODE="build" ;;
|
||||
*) error "Unknown option"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
fetch_release_json() {
|
||||
if [ "$CHANNEL" = "dev" ]; then
|
||||
log "Looking for latest dev (pre-release)..."
|
||||
local json result
|
||||
json="$(curl -fsSL "$GITHUB_API_ALL")"
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
result="$(printf '%s' "$json" | jq -e -c 'map(select(.prerelease == true))[0]')"
|
||||
else
|
||||
result="$(printf '%s' "$json" | python3 - <<'EOF'
|
||||
import sys, json
|
||||
releases = json.load(sys.stdin)
|
||||
pre = [r for r in releases if r.get("prerelease", False)]
|
||||
if not pre:
|
||||
print("{}", end="")
|
||||
else:
|
||||
print(json.dumps(pre[0]), end="")
|
||||
EOF
|
||||
)"
|
||||
fi
|
||||
|
||||
if [ -z "$result" ] || [ "$result" = "{}" ] || [ "$result" = "null" ]; then
|
||||
error "No dev (pre-release) found on GitHub"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s' "$result"
|
||||
else
|
||||
curl -fsSL "$GITHUB_API_LATEST"
|
||||
fi
|
||||
}
|
||||
|
||||
download_binary() {
|
||||
local release_json tag version asset_name download_url
|
||||
|
||||
release_json="$(fetch_release_json)"
|
||||
|
||||
tag="$(printf '%s\n' "$release_json" | \
|
||||
sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p' | head -n1)"
|
||||
if [ -z "$tag" ]; then
|
||||
error "Failed to determine release version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${tag#v}"
|
||||
asset_name="dcr-${TARGET_TRIPLE}-${version}"
|
||||
|
||||
log "Fetching release ${tag} (channel: ${CHANNEL})..."
|
||||
|
||||
download_url="$(printf '%s\n' "$release_json" | \
|
||||
sed -n "s#.*\"browser_download_url\": \"\([^\"]*/${asset_name}\)\".*#\1#p" | head -n1)"
|
||||
|
||||
if [ -z "$download_url" ]; then
|
||||
error "Asset ${asset_name} not found in release ${tag}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_PATH"
|
||||
curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
|
||||
chmod +x "$INSTALL_PATH/dcr"
|
||||
success "Binary ${asset_name} downloaded (${tag})"
|
||||
}
|
||||
|
||||
prepare_sources() {
|
||||
log "Fetching sources..."
|
||||
rm -rf "$TMPDIR"
|
||||
git clone --depth 1 "$REPO_URL" "$TMPDIR"
|
||||
if [ "$CHANNEL" = "dev" ]; then
|
||||
git clone --depth 1 --branch dev "$REPO_URL" "$TMPDIR" 2>/dev/null || \
|
||||
git clone --depth 1 "$REPO_URL" "$TMPDIR"
|
||||
fi
|
||||
success "Sources fetched"
|
||||
}
|
||||
|
||||
build_binary() {
|
||||
log "Building release binary..."
|
||||
(cd "$TMPDIR" && cargo build --release)
|
||||
success "Build completed"
|
||||
}
|
||||
|
||||
install_built_binary() {
|
||||
mkdir -p "$INSTALL_PATH"
|
||||
cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
|
||||
chmod +x "$INSTALL_PATH/dcr"
|
||||
success "Binary installed from source"
|
||||
}
|
||||
|
||||
install_link() {
|
||||
log "Creating symlink..."
|
||||
mkdir -p "$BINPATH"
|
||||
ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
|
||||
success "Command 'dcr' added to $BINPATH"
|
||||
}
|
||||
|
||||
check_path() {
|
||||
if ! echo "$PATH" | grep -q "$BINPATH"; then
|
||||
warn "Directory $BINPATH not found in PATH"
|
||||
echo "Add this to ~/.bashrc, ~/.zshrc or ~/.profile:"
|
||||
echo "export PATH=\"$BINPATH:\$PATH\""
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMPDIR" 2>/dev/null || true
|
||||
}
|
||||
|
||||
main() {
|
||||
log "Starting DCR installation for BSD"
|
||||
|
||||
check_os
|
||||
select_channel
|
||||
check_common_dependencies
|
||||
detect_target
|
||||
select_install_mode
|
||||
cleanup
|
||||
|
||||
if [ "$INSTALL_MODE" = "build" ]; then
|
||||
check_build_dependencies
|
||||
prepare_sources
|
||||
build_binary
|
||||
install_built_binary
|
||||
else
|
||||
download_binary
|
||||
fi
|
||||
|
||||
install_link
|
||||
check_path
|
||||
cleanup
|
||||
|
||||
success "Installation completed successfully"
|
||||
log "Log saved to $LOGFILE"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
192
scripts/sync-releases-to-gitlab.py
Normal file
192
scripts/sync-releases-to-gitlab.py
Normal file
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sync all GitHub Releases (with assets) to GitLab Releases."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
GL_TOKEN = os.environ.get("GL_TOKEN")
|
||||
if not GL_TOKEN:
|
||||
print("FATAL: GL_TOKEN environment variable not set", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
GL_PROJECT = "dexoron-studio%2Fdcr"
|
||||
GL_PROJECT_RAW = "dexoron-studio/dcr"
|
||||
GL_API = "https://gitlab.com/api/v4"
|
||||
GH_REPO = "dexoron/dcr"
|
||||
|
||||
|
||||
def gl_api(method, path, data=None, filepath=None, retries=5):
|
||||
url = f"{GL_API}/{path}"
|
||||
headers = {"PRIVATE-TOKEN": GL_TOKEN}
|
||||
|
||||
if data is not None and filepath is None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
body = json.dumps(data).encode()
|
||||
elif filepath:
|
||||
boundary = uuid.uuid4().hex
|
||||
with open(filepath, "rb") as f:
|
||||
fdata = f.read()
|
||||
fname = os.path.basename(filepath)
|
||||
parts = []
|
||||
parts.append(f"--{boundary}".encode())
|
||||
parts.append(f'Content-Disposition: form-data; name="file"; filename="{fname}"'.encode())
|
||||
parts.append(b"Content-Type: application/octet-stream")
|
||||
parts.append(b"")
|
||||
parts.append(fdata)
|
||||
parts.append(f"--{boundary}--".encode())
|
||||
parts.append(b"")
|
||||
body = b"\r\n".join(parts)
|
||||
headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
|
||||
else:
|
||||
body = None
|
||||
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
text = resp.read().decode()
|
||||
return json.loads(text) if text else None
|
||||
except urllib.error.HTTPError as e:
|
||||
body_text = e.read().decode() if e.fp else ""
|
||||
if e.code in (409, 429) and attempt < retries - 1:
|
||||
time.sleep(3)
|
||||
continue
|
||||
if attempt < retries - 1:
|
||||
time.sleep(1)
|
||||
continue
|
||||
print(f" HTTP {e.code}: {body_text[:300]}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
if attempt < retries - 1:
|
||||
time.sleep(1)
|
||||
continue
|
||||
print(f" Error: {e}", file=sys.stderr)
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def download_file(url, dest):
|
||||
with urllib.request.urlopen(url) as resp:
|
||||
with open(dest, "wb") as f:
|
||||
f.write(resp.read())
|
||||
|
||||
|
||||
# --- MAIN ---
|
||||
print("Fetching GitHub releases...")
|
||||
with urllib.request.urlopen(f"https://api.github.com/repos/{GH_REPO}/releases?per_page=50") as r:
|
||||
gh_releases = json.loads(r.read().decode())
|
||||
|
||||
print(f"Found {len(gh_releases)} releases on GitHub")
|
||||
|
||||
# Reverse to process oldest first (GitHub returns newest first)
|
||||
gh_releases.reverse()
|
||||
|
||||
print("Fetching existing GitLab releases and their assets...")
|
||||
existing = gl_api("GET", f"projects/{GL_PROJECT}/releases?per_page=100") or []
|
||||
existing = existing if isinstance(existing, list) else []
|
||||
|
||||
# Build map: tag -> set of existing asset names
|
||||
gl_assets = {}
|
||||
for rel in existing:
|
||||
tag = rel["tag_name"]
|
||||
links = rel.get("assets", {}).get("links", []) or rel.get("assets", {}).get("sources", []) or []
|
||||
gl_assets[tag] = {l.get("name", "") for l in links if isinstance(l, dict)}
|
||||
# Also get the release description to check if it needs updating
|
||||
gl_assets[tag + "__desc"] = rel.get("description", "")
|
||||
|
||||
print(f"Existing GitLab releases: {len(existing)}")
|
||||
|
||||
download_dir = "/tmp/gh-assets"
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
|
||||
# Reverse: process older first so newer releases with many assets get their description too
|
||||
# Actually, process in original order
|
||||
total = len(gh_releases)
|
||||
for i, rel in enumerate(gh_releases):
|
||||
tag = rel["tag_name"]
|
||||
body = rel.get("body", "") or ""
|
||||
assets = rel.get("assets", [])
|
||||
if isinstance(assets, dict):
|
||||
assets = assets.get("nodes", []) or []
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"[{i+1}/{total}] {tag} ({len(assets)} assets)")
|
||||
|
||||
# Create or update release
|
||||
existing_tag = tag in gl_assets
|
||||
if not existing_tag:
|
||||
print(f" Creating release...")
|
||||
result = gl_api("POST", f"projects/{GL_PROJECT}/releases", data={
|
||||
"tag_name": tag,
|
||||
"name": rel.get("name", tag),
|
||||
"description": body,
|
||||
"ref": "master",
|
||||
})
|
||||
if result is None:
|
||||
print(f" FAILED — skip")
|
||||
continue
|
||||
print(f" OK")
|
||||
else:
|
||||
# Update description if needed
|
||||
existing_desc = gl_assets.get(tag + "__desc", "")
|
||||
if body and body != existing_desc:
|
||||
print(f" Updating description...")
|
||||
gl_api("PUT", f"projects/{GL_PROJECT}/releases/{urllib.request.quote(tag, safe='')}",
|
||||
data={"description": body})
|
||||
|
||||
existing_links = gl_assets.get(tag, set())
|
||||
|
||||
for j, asset in enumerate(assets):
|
||||
aname = asset.get("name", "")
|
||||
aurl = asset.get("browser_download_url", "")
|
||||
if not aurl:
|
||||
continue
|
||||
|
||||
if aname in existing_links:
|
||||
print(f" [{j+1}/{len(assets)}] {aname} — already exists")
|
||||
continue
|
||||
|
||||
fpath = os.path.join(download_dir, aname)
|
||||
print(f" [{j+1}/{len(assets)}] {aname}...", end=" ", flush=True)
|
||||
|
||||
# Download from GitHub
|
||||
try:
|
||||
download_file(aurl, fpath)
|
||||
except Exception as e:
|
||||
print(f"download failed: {e}")
|
||||
continue
|
||||
|
||||
# Upload to GitLab
|
||||
up = gl_api("POST", f"projects/{GL_PROJECT}/uploads", filepath=fpath)
|
||||
if not up or "url" not in up:
|
||||
size_mb = os.path.getsize(fpath) / (1024 * 1024)
|
||||
print(f"upload failed ({size_mb:.1f} MB)")
|
||||
os.remove(fpath)
|
||||
continue
|
||||
|
||||
file_url = up["url"]
|
||||
full_url = f"https://gitlab.com/{GL_PROJECT_RAW}{file_url}"
|
||||
|
||||
# Link asset to release
|
||||
encoded_tag = urllib.request.quote(tag, safe='')
|
||||
link = gl_api("POST",
|
||||
f"projects/{GL_PROJECT}/releases/{encoded_tag}/assets/links",
|
||||
data={"name": aname, "url": full_url},
|
||||
retries=5)
|
||||
if link:
|
||||
print(f"linked")
|
||||
existing_links.add(aname)
|
||||
else:
|
||||
print(f"link failed")
|
||||
|
||||
os.remove(fpath)
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("Done!")
|
||||
@@ -80,15 +80,26 @@ pub fn build(args: &[String]) -> i32 {
|
||||
}
|
||||
if flags.target.is_none() {
|
||||
let default_target = if cfg!(target_os = "linux") {
|
||||
"x86_64-unknown-linux-gnu"
|
||||
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"x86_64-apple-darwin"
|
||||
"x86_64-apple-darwin".to_string()
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"x86_64-pc-windows-msvc"
|
||||
"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"
|
||||
"unknown".to_string()
|
||||
};
|
||||
flags.target = Some(default_target.to_string());
|
||||
flags.target = Some(default_target);
|
||||
}
|
||||
if flags.clean {
|
||||
let mut clean_args = Vec::new();
|
||||
@@ -200,7 +211,7 @@ fn get_string_with_profile_and_target(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_build_string_with_profile(config: &Config, field: &str, profile: &str) -> String {
|
||||
pub fn get_build_string_with_profile(config: &Config, field: &str, profile: &str) -> String {
|
||||
get_string_with_profile_and_target(config, field, profile, None)
|
||||
}
|
||||
|
||||
@@ -390,6 +401,15 @@ fn ensure_target_dirs(
|
||||
let default_dir = if cfg!(target_os = "linux") {
|
||||
let arch = std::env::consts::ARCH;
|
||||
format!("{arch}-unknown-linux-gnu/{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;
|
||||
format!("{arch}-unknown-{os}/{profile}")
|
||||
} else {
|
||||
profile.to_string()
|
||||
};
|
||||
@@ -692,26 +712,31 @@ fn build_project_at(
|
||||
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
|
||||
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
|
||||
|
||||
let target_dir_binding = match workspace_root {
|
||||
Some(root) => {
|
||||
let target_str = build_target
|
||||
.map(normalize_target_os)
|
||||
.filter(|t| !t.is_empty());
|
||||
let rel: PathBuf = match target_str {
|
||||
Some(ref t) => Path::new("target").join(t).join(profile),
|
||||
None => {
|
||||
let default_dir = if cfg!(target_os = "linux") {
|
||||
let arch = std::env::consts::ARCH;
|
||||
format!("{arch}-unknown-linux-gnu/{profile}")
|
||||
} else {
|
||||
profile.to_string()
|
||||
};
|
||||
Path::new("target").join(&default_dir)
|
||||
}
|
||||
};
|
||||
root.join(&rel).to_string_lossy().to_string()
|
||||
let out_dir_config = get_build_string_with_profile(&config, "out_dir", profile);
|
||||
let target_dir_binding = if !out_dir_config.is_empty() {
|
||||
out_dir_config
|
||||
} else {
|
||||
match workspace_root {
|
||||
Some(root) => {
|
||||
let target_str = build_target
|
||||
.map(normalize_target_os)
|
||||
.filter(|t| !t.is_empty());
|
||||
let rel: PathBuf = match target_str {
|
||||
Some(ref t) => Path::new("target").join(t).join(profile),
|
||||
None => {
|
||||
let default_dir = if cfg!(target_os = "linux") {
|
||||
let arch = std::env::consts::ARCH;
|
||||
format!("{arch}-unknown-linux-gnu/{profile}")
|
||||
} else {
|
||||
profile.to_string()
|
||||
};
|
||||
Path::new("target").join(&default_dir)
|
||||
}
|
||||
};
|
||||
root.join(&rel).to_string_lossy().to_string()
|
||||
}
|
||||
None => normalize_target(build_target.unwrap_or(""), profile).unwrap_or_default(),
|
||||
}
|
||||
None => normalize_target(build_target.unwrap_or(""), profile).unwrap_or_default(),
|
||||
};
|
||||
let target_dir = if target_dir_binding.is_empty() {
|
||||
None
|
||||
|
||||
@@ -120,11 +120,22 @@ fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
|
||||
})
|
||||
.or_else(|| {
|
||||
Some(if cfg!(target_os = "linux") {
|
||||
"x86_64-unknown-linux-gnu".to_string()
|
||||
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()
|
||||
})
|
||||
|
||||
81
src/cli/fmt.rs
Normal file
81
src/cli/fmt.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 crate::utils::log::error;
|
||||
use crate::utils::text::{BOLD_GREEN, colored};
|
||||
use glob::glob;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn fmt(_args: &[String]) -> i32 {
|
||||
let patterns = [
|
||||
"src/**/*.c",
|
||||
"src/**/*.cpp",
|
||||
"src/**/*.cxx",
|
||||
"src/**/*.cc",
|
||||
"src/**/*.h",
|
||||
"src/**/*.hpp",
|
||||
"src/**/*.hxx",
|
||||
"src/**/*.hh",
|
||||
"tests/**/*.c",
|
||||
"tests/**/*.cpp",
|
||||
"tests/**/*.cxx",
|
||||
"tests/**/*.cc",
|
||||
"tests/**/*.h",
|
||||
"tests/**/*.hpp",
|
||||
"tests/**/*.hxx",
|
||||
"tests/**/*.hh",
|
||||
];
|
||||
|
||||
let mut files = Vec::new();
|
||||
for pattern in patterns {
|
||||
if let Ok(paths) = glob(pattern) {
|
||||
for path in paths.flatten() {
|
||||
if let Some(s) = path.to_str() {
|
||||
files.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if files.is_empty() {
|
||||
println!(" {} No files to format", colored("Format", BOLD_GREEN));
|
||||
return 0;
|
||||
}
|
||||
|
||||
println!(
|
||||
" {} {} files",
|
||||
colored("Formatting", BOLD_GREEN),
|
||||
files.len()
|
||||
);
|
||||
|
||||
let status = Command::new("clang-format").arg("-i").args(&files).status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
println!(" {} successful", colored("Format", BOLD_GREEN));
|
||||
0
|
||||
}
|
||||
Ok(s) => {
|
||||
error(&format!("clang-format failed with status: {}", s));
|
||||
1
|
||||
}
|
||||
Err(e) => {
|
||||
error(&format!("Failed to execute clang-format: {}", e));
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ pub fn help() -> i32 {
|
||||
println!(" run [--profile] Build and run the project (default: --debug)");
|
||||
println!(" tree Display the dependency tree");
|
||||
println!(" test Run project tests (alias: tests)");
|
||||
println!(" fmt Format all C/C++ source files using clang-format");
|
||||
println!(" clean Remove the target directory");
|
||||
println!(" gen <subcommand> Generate IDE integration files");
|
||||
printc("FLAGS:", BOLD_GREEN);
|
||||
|
||||
@@ -20,6 +20,7 @@ pub mod build;
|
||||
pub mod clean;
|
||||
pub mod flag_update;
|
||||
pub mod flags;
|
||||
pub mod fmt;
|
||||
pub mod r#gen;
|
||||
pub mod help;
|
||||
pub mod init;
|
||||
|
||||
@@ -83,7 +83,6 @@ pub fn run(args: &[String]) -> i32 {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let flags = match parse_build_run_flags(args) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return 1,
|
||||
@@ -160,15 +159,26 @@ fn run_project(
|
||||
let mut target = flags.target.clone();
|
||||
if target.is_none() {
|
||||
let default_target = if cfg!(target_os = "linux") {
|
||||
"x86_64-unknown-linux-gnu"
|
||||
format!("{}-unknown-linux-gnu", std::env::consts::ARCH)
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"x86_64-apple-darwin"
|
||||
"x86_64-apple-darwin".to_string()
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"x86_64-pc-windows-msvc"
|
||||
"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"
|
||||
"unknown".to_string()
|
||||
};
|
||||
target = Some(default_target.to_string());
|
||||
target = Some(default_target);
|
||||
}
|
||||
|
||||
let build_kind = config
|
||||
@@ -177,14 +187,20 @@ fn run_project(
|
||||
.or_else(|| config.get("build.kind").and_then(|v| v.as_str()))
|
||||
.unwrap_or("");
|
||||
|
||||
let normalized_target_dir = match workspace_root {
|
||||
Some(wr) => target
|
||||
.as_ref()
|
||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile))
|
||||
.map(|rel| wr.join(&rel).to_string_lossy().to_string()),
|
||||
None => target
|
||||
.as_ref()
|
||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)),
|
||||
let out_dir =
|
||||
crate::cli::build::get_build_string_with_profile(&config, "out_dir", &flags.profile);
|
||||
let normalized_target_dir = if !out_dir.is_empty() {
|
||||
Some(out_dir)
|
||||
} else {
|
||||
match workspace_root {
|
||||
Some(wr) => target
|
||||
.as_ref()
|
||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile))
|
||||
.map(|rel| wr.join(&rel).to_string_lossy().to_string()),
|
||||
None => target
|
||||
.as_ref()
|
||||
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)),
|
||||
}
|
||||
};
|
||||
|
||||
let version = config
|
||||
|
||||
@@ -37,9 +37,6 @@ pub fn collect_sources(
|
||||
collect_sources_rec(root, extensions, &mut sources, exclude_dirs, include_paths)?;
|
||||
}
|
||||
sources.sort();
|
||||
if sources.is_empty() {
|
||||
return Err(format!("No source files found in {}", format_roots(roots)));
|
||||
}
|
||||
Ok(sources)
|
||||
}
|
||||
|
||||
@@ -89,17 +86,6 @@ fn collect_sources_rec(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_roots(roots: &[PathBuf]) -> String {
|
||||
if roots.is_empty() {
|
||||
return "<none>".to_string();
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for root in roots {
|
||||
out.push(root.to_string_lossy().to_string());
|
||||
}
|
||||
out.join(", ")
|
||||
}
|
||||
|
||||
pub fn is_excluded(path: &Path, exclude_dirs: &[PathBuf], include_paths: &[String]) -> bool {
|
||||
if matches_patterns(path, include_paths, true) {
|
||||
return false;
|
||||
@@ -316,6 +302,31 @@ pub fn needs_rebuild(source: &str, object: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn needs_link(objects: &[String], output_path: &str) -> bool {
|
||||
let out_meta = match fs::metadata(output_path) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return true,
|
||||
};
|
||||
let out_time = match out_meta.modified() {
|
||||
Ok(t) => t,
|
||||
Err(_) => return true,
|
||||
};
|
||||
for obj in objects {
|
||||
let obj_meta = match fs::metadata(obj) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return true,
|
||||
};
|
||||
let obj_time = match obj_meta.modified() {
|
||||
Ok(t) => t,
|
||||
Err(_) => return true,
|
||||
};
|
||||
if obj_time > out_time {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn parse_d_file(content: &str) -> Vec<String> {
|
||||
let mut deps = Vec::new();
|
||||
let text = content.replace("\\\n", " ").replace("\\\r\n", " ");
|
||||
@@ -418,6 +429,55 @@ mod tests {
|
||||
assert_eq!(result, "target/debug/obj/main.c.obj");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_link_no_output() {
|
||||
let dir = temp_dir("link_no_out");
|
||||
let obj1 = dir.join("test1.o");
|
||||
let obj2 = dir.join("test2.o");
|
||||
let out = dir.join("app");
|
||||
fs::write(&obj1, "").unwrap();
|
||||
fs::write(&obj2, "").unwrap();
|
||||
let objs = vec![
|
||||
obj1.to_string_lossy().to_string(),
|
||||
obj2.to_string_lossy().to_string(),
|
||||
];
|
||||
assert!(needs_link(&objs, &out.to_string_lossy()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_link_fresh() {
|
||||
let dir = temp_dir("link_fresh");
|
||||
let obj1 = dir.join("test1.o");
|
||||
let obj2 = dir.join("test2.o");
|
||||
let out = dir.join("app");
|
||||
fs::write(&obj1, "").unwrap();
|
||||
fs::write(&obj2, "").unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
fs::write(&out, "").unwrap();
|
||||
let objs = vec![
|
||||
obj1.to_string_lossy().to_string(),
|
||||
obj2.to_string_lossy().to_string(),
|
||||
];
|
||||
assert!(!needs_link(&objs, &out.to_string_lossy()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_link_stale() {
|
||||
let dir = temp_dir("link_stale");
|
||||
let obj1 = dir.join("test1.o");
|
||||
let obj2 = dir.join("test2.o");
|
||||
let out = dir.join("app");
|
||||
fs::write(&obj1, "").unwrap();
|
||||
fs::write(&out, "").unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
fs::write(&obj2, "").unwrap();
|
||||
let objs = vec![
|
||||
obj1.to_string_lossy().to_string(),
|
||||
obj2.to_string_lossy().to_string(),
|
||||
];
|
||||
assert!(needs_link(&objs, &out.to_string_lossy()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_rebuild_no_object() {
|
||||
let dir = temp_dir("rebuild_no_obj");
|
||||
@@ -550,7 +610,8 @@ mod tests {
|
||||
let result = collect_sources(&roots, &["c"], &[], &include);
|
||||
std::env::set_current_dir(prev).unwrap();
|
||||
|
||||
assert!(result.is_err(), "empty src should return error");
|
||||
let sources = result.expect("empty src should return Ok");
|
||||
assert!(sources.is_empty(), "empty src should return empty vec");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
if !common::needs_link(&objects, &lib_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
|
||||
"lib"
|
||||
} else {
|
||||
@@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
if !common::needs_link(&objects, &out_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
cmd.arg("-o").arg(out_path);
|
||||
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
|
||||
@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
if !common::needs_link(&objects, &lib_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("lib"));
|
||||
cmd.arg("/nologo").arg(format!("/OUT:{lib_path}"));
|
||||
for obj in &objects {
|
||||
@@ -117,6 +121,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
if !common::needs_link(&objects, &out_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
cmd.arg(format!("/Fe:{out_path}"));
|
||||
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
|
||||
@@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
if !common::needs_link(&objects, &lib_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") {
|
||||
"lib"
|
||||
} else {
|
||||
@@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
if !common::needs_link(&objects, &out_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
cmd.arg("-o").arg(out_path);
|
||||
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
|
||||
@@ -45,6 +45,10 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
|
||||
if ctx.kind == "staticlib" {
|
||||
let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir);
|
||||
if !common::needs_link(&objects, &lib_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
let mut cmd = Command::new(ctx.archiver.unwrap_or("ar"));
|
||||
cmd.arg("rcs").arg(&lib_path);
|
||||
for obj in &objects {
|
||||
@@ -106,6 +110,11 @@ pub fn build(ctx: &BuildContext) -> Result<f64, String> {
|
||||
} else {
|
||||
platform::bin_path(ctx.profile, &final_name, ctx.target_dir)
|
||||
};
|
||||
|
||||
if !common::needs_link(&objects, &out_path) {
|
||||
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
|
||||
return Ok(elapsed);
|
||||
}
|
||||
cmd.arg("-o").arg(out_path);
|
||||
|
||||
if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {
|
||||
|
||||
@@ -27,7 +27,15 @@ const DEFAULT_LANGUAGE: &str = "c";
|
||||
const DEFAULT_STANDARD: &str = "c11";
|
||||
const DEFAULT_COMPILER: &str = "clang";
|
||||
const DEFAULT_KIND: &str = "bin";
|
||||
const VALID_KINDS: &[&str] = &["bin", "staticlib", "sharedlib", "efi", "elf"];
|
||||
const VALID_KINDS: &[&str] = &[
|
||||
"bin",
|
||||
"staticlib",
|
||||
"sharedlib",
|
||||
"efi",
|
||||
"elf",
|
||||
"none",
|
||||
"custom",
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
@@ -101,7 +109,8 @@ pub struct PackageConfig {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct BuildConfig {
|
||||
pub language: LanguageConfig,
|
||||
pub standard: String,
|
||||
#[serde(default)]
|
||||
pub standard: Option<String>,
|
||||
#[serde(default)]
|
||||
pub cxx_standard: Option<String>,
|
||||
pub compiler: String,
|
||||
@@ -110,6 +119,8 @@ pub struct BuildConfig {
|
||||
#[serde(default)]
|
||||
pub target: Option<String>,
|
||||
#[serde(default)]
|
||||
pub out_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub platform: Option<String>,
|
||||
#[serde(default)]
|
||||
pub cflags: Vec<String>,
|
||||
@@ -353,7 +364,14 @@ impl Config {
|
||||
}
|
||||
|
||||
validate_language_config(&build.language, "build.language")?;
|
||||
if build.standard.trim().is_empty() {
|
||||
let lang_is_asm = match &build.language {
|
||||
LanguageConfig::One(s) => s.trim().eq_ignore_ascii_case("asm"),
|
||||
LanguageConfig::Many(v) => v.len() == 1 && v[0].trim().eq_ignore_ascii_case("asm"),
|
||||
};
|
||||
if let Some(ref std) = build.standard
|
||||
&& std.trim().is_empty()
|
||||
&& !lang_is_asm
|
||||
{
|
||||
return Err(ConfigError::Invalid("build.standard is empty".into()));
|
||||
}
|
||||
if build.compiler.trim().is_empty() {
|
||||
@@ -380,6 +398,9 @@ impl Config {
|
||||
if let Some(target) = &build.target {
|
||||
validate_non_empty_string(target, "build.target")?;
|
||||
}
|
||||
if let Some(out_dir) = &build.out_dir {
|
||||
validate_non_empty_string(out_dir, "build.out_dir")?;
|
||||
}
|
||||
for profile in ["release", "debug"] {
|
||||
if let Some(section) = self
|
||||
.data
|
||||
@@ -523,7 +544,9 @@ impl Config {
|
||||
if let Some(lang) = table.get("language") {
|
||||
self.validate_language(lang)?;
|
||||
}
|
||||
for key in ["standard", "compiler", "kind", "target", "platform"] {
|
||||
for key in [
|
||||
"standard", "compiler", "kind", "target", "out_dir", "platform",
|
||||
] {
|
||||
if let Some(value) = table.get(key) {
|
||||
let s = value.as_str().unwrap_or("");
|
||||
if s.trim().is_empty() {
|
||||
@@ -754,7 +777,9 @@ fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
||||
} else {
|
||||
out.push_str(&format!("language = \"{language}\"\n"));
|
||||
}
|
||||
out.push_str(&format!("standard = \"{standard}\"\n"));
|
||||
if !standard.is_empty() {
|
||||
out.push_str(&format!("standard = \"{standard}\"\n"));
|
||||
}
|
||||
out.push_str(&format!("compiler = \"{compiler}\"\n"));
|
||||
let kind = build
|
||||
.get("kind")
|
||||
@@ -766,6 +791,11 @@ fn format_toml(value: &Value) -> Result<String, ConfigError> {
|
||||
{
|
||||
out.push_str(&format!("target = \"{target}\"\n"));
|
||||
}
|
||||
if let Some(out_dir) = build.get("out_dir").and_then(|v| v.as_str())
|
||||
&& !out_dir.trim().is_empty()
|
||||
{
|
||||
out.push_str(&format!("out_dir = \"{out_dir}\"\n"));
|
||||
}
|
||||
if let Some(cflags) = build.get("cflags") {
|
||||
out.push_str(&format!("cflags = {}\n", format_string_array(cflags)));
|
||||
}
|
||||
|
||||
@@ -22,12 +22,10 @@ use std::process::Command;
|
||||
pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 {
|
||||
let bin_path = platform::bin_path(profile, project_name, target_dir);
|
||||
if Path::new(&bin_path).exists() {
|
||||
let output = Command::new(&bin_path).output();
|
||||
match output {
|
||||
Ok(o) => {
|
||||
print!("{}", String::from_utf8_lossy(&o.stdout));
|
||||
eprint!("{}", String::from_utf8_lossy(&o.stderr));
|
||||
return o.status.code().unwrap_or(0);
|
||||
let status = Command::new(&bin_path).status();
|
||||
match status {
|
||||
Ok(s) => {
|
||||
return s.code().unwrap_or(0);
|
||||
}
|
||||
Err(_) => {
|
||||
return 1;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//
|
||||
// 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 crate::utils::log::error;
|
||||
mod cli;
|
||||
mod config;
|
||||
@@ -41,6 +40,7 @@ fn main() {
|
||||
"run" => cli::run::run(rest),
|
||||
"tree" => cli::tree::tree(rest),
|
||||
"test" | "tests" => cli::test::test(rest),
|
||||
"fmt" => cli::fmt::fmt(rest),
|
||||
"clean" => cli::clean::clean(rest),
|
||||
"gen" => cli::r#gen::r#gen(rest),
|
||||
"--version" => {
|
||||
|
||||
105
src/platform/bsd.rs
Normal file
105
src/platform/bsd.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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 fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||
None => {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let target = format!("{arch}-unknown-{os}");
|
||||
format!("./target/{target}/{profile}/{name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name),
|
||||
None => {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let target = format!("{arch}-unknown-{os}");
|
||||
format!("./target/{target}/{profile}/lib{name}.a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||
None => {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let target = format!("{arch}-unknown-{os}");
|
||||
format!("./target/{target}/{profile}/{name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name),
|
||||
None => {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let target = format!("{arch}-unknown-{os}");
|
||||
format!("./target/{target}/{profile}/{name}.efi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared_lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/lib{}.so", dir.trim_end_matches('/'), name),
|
||||
None => {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let target = format!("{arch}-unknown-{os}");
|
||||
format!("./target/{target}/{profile}/lib{name}.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bin_path_default() {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let expected = format!("./target/{arch}-unknown-{os}/debug/hello");
|
||||
assert_eq!(bin_path("debug", "hello", None), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lib_path_default() {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let expected = format!("./target/{arch}-unknown-{os}/debug/libmylib.a");
|
||||
assert_eq!(lib_path("debug", "mylib", None), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_lib_path_default() {
|
||||
let arch = std::env::consts::ARCH;
|
||||
let os = std::env::consts::OS;
|
||||
let expected = format!("./target/{arch}-unknown-{os}/debug/libmylib.so");
|
||||
assert_eq!(shared_lib_path("debug", "mylib", None), expected);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
pub mod bsd;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -35,7 +42,24 @@ pub fn bin_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
{
|
||||
return windows::bin_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
{
|
||||
return bsd::bin_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||
@@ -57,7 +81,24 @@ pub fn elf_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
{
|
||||
return windows::elf_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
{
|
||||
return bsd::elf_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}", dir.trim_end_matches('/'), name),
|
||||
@@ -79,7 +120,24 @@ pub fn efi_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
{
|
||||
return windows::efi_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
{
|
||||
return bsd::efi_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/{}.efi", dir.trim_end_matches('/'), name),
|
||||
@@ -101,7 +159,24 @@ pub fn lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> String {
|
||||
{
|
||||
return windows::lib_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
{
|
||||
return bsd::lib_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/lib{}.a", dir.trim_end_matches('/'), name),
|
||||
@@ -123,7 +198,24 @@ pub fn shared_lib_path(profile: &str, name: &str, target_dir: Option<&str>) -> S
|
||||
{
|
||||
return windows::shared_lib_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
))]
|
||||
{
|
||||
return bsd::shared_lib_path(profile, name, target_dir);
|
||||
}
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonfly"
|
||||
)))]
|
||||
{
|
||||
match target_dir {
|
||||
Some(dir) => format!("{}/lib{}.so", dir.trim_end_matches('/'), name),
|
||||
|
||||
@@ -383,6 +383,96 @@ fn registry_dependency_is_built_from_cache() {
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_project_name(toml: &str) -> String {
|
||||
toml.lines()
|
||||
.find(|l| l.trim().starts_with("name ="))
|
||||
.and_then(|l| l.split('=').nth(1))
|
||||
.map(|s| s.trim().trim_matches('"').to_string())
|
||||
.expect("could not parse project name from dcr.toml")
|
||||
}
|
||||
|
||||
fn default_artifact_path(project_root: &Path, project_name: &str) -> PathBuf {
|
||||
project_root
|
||||
.join("target")
|
||||
.join("x86_64-unknown-linux-gnu")
|
||||
.join("debug")
|
||||
.join(project_name)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_with_target_config() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
eprintln!("no compiler found; skipping target config test");
|
||||
return;
|
||||
};
|
||||
|
||||
let dir = unique_sandbox_dir("target_cfg");
|
||||
let out = run_dcr(&["init"], &dir);
|
||||
assert!(out.status.success(), "dcr init should succeed");
|
||||
|
||||
let toml_path = dir.join("dcr.toml");
|
||||
let toml = std::fs::read_to_string(&toml_path).expect("failed to read dcr.toml");
|
||||
let project_name = parse_project_name(&toml);
|
||||
let updated = toml.replace("[build]", "[build]\ntarget = \"linux\"");
|
||||
std::fs::write(&toml_path, updated).expect("failed to write dcr.toml");
|
||||
|
||||
let envs = [("DCR_COMPILER", compiler)];
|
||||
let out = run_dcr_env(&["build"], &dir, &envs);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"dcr build with target = \"linux\" should succeed"
|
||||
);
|
||||
|
||||
let artifact = default_artifact_path(&dir, &project_name);
|
||||
assert!(
|
||||
artifact.is_file(),
|
||||
"artifact should be at default path target/x86_64-unknown-linux-gnu/debug/{}",
|
||||
project_name
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_with_out_dir() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
eprintln!("no compiler found; skipping out_dir test");
|
||||
return;
|
||||
};
|
||||
|
||||
let dir = unique_sandbox_dir("out_dir");
|
||||
let out = run_dcr(&["init"], &dir);
|
||||
assert!(out.status.success(), "dcr init should succeed");
|
||||
|
||||
let toml_path = dir.join("dcr.toml");
|
||||
let toml = std::fs::read_to_string(&toml_path).expect("failed to read dcr.toml");
|
||||
let project_name = parse_project_name(&toml);
|
||||
let updated = toml.replace("[build]", "[build]\nout_dir = \"./_BUILD\"");
|
||||
std::fs::write(&toml_path, updated).expect("failed to write dcr.toml");
|
||||
|
||||
let envs = [("DCR_COMPILER", compiler)];
|
||||
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));
|
||||
}
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"dcr build with out_dir should succeed"
|
||||
);
|
||||
|
||||
let artifact = dir.join("_BUILD").join(&project_name);
|
||||
assert!(
|
||||
artifact.is_file(),
|
||||
"artifact should be at _BUILD/{} (custom out_dir)",
|
||||
project_name
|
||||
);
|
||||
|
||||
let default_path = default_artifact_path(&dir, &project_name);
|
||||
assert!(
|
||||
!default_path.exists(),
|
||||
"artifact should NOT be at default path when out_dir is set"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcr_test_runs_without_sandbox_dependency() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
|
||||
Reference in New Issue
Block a user