22 Commits

Author SHA1 Message Date
94aa6c0f01 Merge pull request #8 from babichgh/master
Update user input prompts to read from /dev/tty
2026-05-30 13:28:40 +08:00
John Babich
e462de6272 Update user input prompts to read from /dev/tty
Redirect input for user prompts to /dev/tty for better compatibility.
2026-05-29 23:58:03 +03:00
c2ef5483a0 update: update dexoron packages 2026-05-28 21:11:49 +08:00
eb24d616c5 fix: Fix GPG permissions after Docker 2026-05-28 18:16:18 +08:00
4f76f54b63 fix: update GPG signing process to resolve permission issues and clarify Release generation 2026-05-28 18:11:34 +08:00
37b9b855ec feat: streamline artifact download process and enhance repository indexing with signing 2026-05-28 18:08:06 +08:00
8f0f67ad62 fix: correct paths for RPM package artifacts and update repository indexing 2026-05-28 17:49:29 +08:00
49945aa65d feat: add --clobber flag to Download Packages from Release Assets 2026-05-28 17:44:51 +08:00
83593aa592 feat: enhance Dexoron Packages Index workflow with manual dispatch and improved artifact handling 2026-05-28 17:41:50 +08:00
2835188da9 feat: update Dexoron Packages Index workflow and improve release process for NetBSD 2026-05-28 17:09:36 +08:00
78160e2bb9 feat: set MAKE environment variable for NetBSD build process 2026-05-28 16:55:51 +08:00
7f9cd3d643 fix: correct path for Snap package output in release workflow 2026-05-28 16:53:14 +08:00
4d1bd1dbdc feat: update snap packaging directory structure and fix NetBSD package installation command 2026-05-28 16:43:27 +08:00
7bc571ea27 feat: update snap packaging paths and improve asset configuration in Cargo.toml 2026-05-28 16:10:28 +08:00
f431c3d018 feat: update package metadata for RPM generation in Cargo.toml and fix snap package path 2026-05-28 15:34:24 +08:00
9fd1652ebb chore: release v0.6.9 2026-05-28 15:18:03 +08:00
c59d6de54f feat: enhance release workflow with Arch Linux native build and AUR update options 2026-05-28 00:13:01 +08:00
73b6ce903e feat: add Arch Linux native package build process to release workflow 2026-05-28 00:07:36 +08:00
e97b9b6e00 feat: enhance package database update process with checks for existing files 2026-05-27 23:58:53 +08:00
12d1bc5111 feat: add Dexoron Packages Index workflow for automatic package updates 2026-05-27 23:55:11 +08:00
8c9dd39503 feat: update version to 0.6.9 and add packaging for Linux and BSD
- Bump version from 0.6.8 to 0.6.9 in Cargo.toml and Cargo.lock.
- Enhance GitHub Actions workflow to build and publish .deb, .rpm, and Snap packages.
- Implement AUR publishing for both dcr and dcr-bin packages.
- Add support for workspace-only projects in the build system.
- Introduce new `workspace` flag for build and run commands.
- Create snapcraft.yaml for Snap package configuration.
- Update build.rs and related files to handle workspace members and inheritance.
- Modify config handling to support optional package and build configurations.
2026-05-27 23:14:49 +08:00
e3c35793ec feat: enhance FreeBSD, OpenBSD, and NetBSD build processes in release workflow 2026-05-27 20:42:30 +08:00
16 changed files with 1058 additions and 259 deletions

View File

@@ -1,138 +0,0 @@
name: AUR Publish
on:
workflow_dispatch:
inputs:
version:
description: "Tag version without v prefix (e.g. 0.2.2)"
required: true
push:
tags:
- "v*"
permissions:
contents: read
jobs:
publish-aur:
# Не публикуем dev/pre-release теги в AUR
if: "!contains(github.ref_name, '-')"
runs-on: ubuntu-latest
environment: aur
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive version and checksum
id: meta
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
PKGVER="${{ inputs.version }}"
else
PKGVER="${GITHUB_REF_NAME#v}"
fi
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/v${PKGVER}.tar.gz"
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
echo "pkgver=$PKGVER" >> "$GITHUB_OUTPUT"
echo "source_url=$SRC_URL" >> "$GITHUB_OUTPUT"
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
- name: Render AUR metadata
shell: bash
run: |
set -euo pipefail
PKGVER='${{ steps.meta.outputs.pkgver }}'
SRC_URL='${{ steps.meta.outputs.source_url }}'
SHA256='${{ steps.meta.outputs.sha256 }}'
cat > packaging/aur/dcr/PKGBUILD <<PKG
pkgname=dcr
pkgver=${PKGVER}
pkgrel=1
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64' 'aarch64')
url="https://github.com/dexoron/dcr"
license=('MIT')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
optdepends=(
'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")
sha256sums=('${SHA256}')
build() {
cd "\$srcdir/\$pkgname-\$pkgver"
export CARGO_PROFILE_RELEASE_LTO=false
export RUSTFLAGS="\${RUSTFLAGS:-}"
export RUSTFLAGS="\${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
cargo build --release --locked
}
package() {
cd "\$srcdir/\$pkgname-\$pkgver"
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
}
PKG
cat > packaging/aur/dcr/.SRCINFO <<SRC
pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = ${PKGVER}
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
arch = aarch64
license = MIT
makedepends = cargo
depends = gcc-libs
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}
sha256sums = ${SHA256}
pkgname = dcr
SRC
- name: Push to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_PACKAGE: dcr
shell: bash
run: |
set -euo pipefail
if [[ -z "${AUR_SSH_PRIVATE_KEY:-}" ]]; then
echo "AUR_SSH_PRIVATE_KEY secret is missing"
exit 1
fi
install -d -m 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
export GIT_SSH_COMMAND='ssh -i ~/.ssh/aur -o IdentitiesOnly=yes'
git clone "ssh://aur@aur.archlinux.org/${AUR_PACKAGE}.git" aur-repo
cp packaging/aur/dcr/PKGBUILD aur-repo/PKGBUILD
cp packaging/aur/dcr/.SRCINFO aur-repo/.SRCINFO
cd aur-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet; then
echo "No AUR changes"
exit 0
fi
git add PKGBUILD .SRCINFO
git commit -m "dcr ${{ steps.meta.outputs.pkgver }}"
git push

View File

@@ -0,0 +1,109 @@
name: Dexoron Packages Index
on:
workflow_dispatch:
inputs:
tag:
description: "Версия (тег) для ручного импорта пакетов (например, v0.6.9)"
required: true
type: string
workflow_run:
workflows: ["Release"]
types:
- completed
permissions:
contents: read
jobs:
update-packages-index:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' || github.event_workflow_run.conclusion == 'success' }}
steps:
- name: Checkout Central Packages Repo
uses: actions/checkout@v4
with:
repository: 'dexoron/packages'
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 Artifacts
if: ${{ github.event_name == 'workflow_run' }}
uses: actions/download-artifact@v4
with:
path: central-packages/
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- 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: 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 && \
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
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
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/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"
git add .
if ! git diff --cached --quiet; then
git commit -m "chore: update and sign repositories"
git push
fi

View File

@@ -13,6 +13,14 @@ on:
description: "Build Linux"
type: boolean
default: true
run_linux_packages:
description: "Build Linux Packages (.deb, .rpm, .snap, .pkg.tar.zst)"
type: boolean
default: true
run_aur:
description: "Update AUR packages (dcr & dcr-bin)"
type: boolean
default: false
run_bsd:
description: "Build BSD"
type: boolean
@@ -92,7 +100,7 @@ jobs:
name: release-notes
path: .github/release-notes.md
# ── Linux ──────────────────────────────────────────────────────────────────
# ── Linux Binaries Matrix ──────────────────────────────────────────────────
build-linux:
if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }}
needs: [setup, notes]
@@ -148,7 +156,150 @@ jobs:
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── macOS ──────────────────────────────────────────────────────────────────
# ── Linux Packages Matrix (.deb, .rpm, .pkg.tar.zst, .snap) ────────────────
build-linux-packages:
if: ${{ github.event_name == 'push' || github.event.inputs.run_linux_packages == 'true' }}
needs: [setup, notes]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package_type: deb
artifact_name: debian-package
- package_type: rpm
artifact_name: rpm-package
- package_type: arch
artifact_name: arch-package
- package_type: snap
artifact_name: snap-package
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.setup.outputs.tag }}
- name: Setup Rust (For non-container builds)
if: matrix.package_type != 'arch'
uses: dtolnay/rust-toolchain@stable
# ── DEB Build ──
- name: Build Debian Package
if: matrix.package_type == 'deb'
run: |
cargo install cargo-deb
cargo build --release
cargo deb --no-build
mkdir -p dist-packages && cp target/debian/*.deb dist-packages/
# ── RPM Build ──
- name: Build RPM Package
if: matrix.package_type == 'rpm'
run: |
cargo install cargo-generate-rpm
cargo build --release
cargo generate-rpm
mkdir -p dist-packages && cp target/generate-rpm/*.rpm dist-packages/
# ── Snap Build ──
- name: Build Snap Package
if: matrix.package_type == 'snap'
run: |
cargo build --release
mkdir -p packaging/snap/dist/bin
cp target/release/dcr packaging/snap/dist/bin/
- name: Snapcraft Build Action
if: matrix.package_type == 'snap'
uses: snapcore/action-build@v1
with:
path: packaging/snap
- name: Prepare Snap Output
if: matrix.package_type == 'snap'
run: mkdir -p dist-packages && cp packaging/snap/*.snap dist-packages/
# ── Arch Linux Native Build ──
- name: Build Arch Package inside Container
if: matrix.package_type == 'arch'
run: |
PKGVER='${{ needs.setup.outputs.version }}'
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}
pkgrel=1
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64')
url="https://github.com/dexoron/dcr"
license=('GPL-3.0-or-later')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
source=("\$pkgname-\$pkgver.tar.gz::\$url/archive/refs/tags/v\$pkgver.tar.gz")
sha256sums=('${SHA256}')
build() {
cd "\$srcdir/\$pkgname-\$pkgver"
cargo build --release --locked
}
package() {
cd "\$srcdir/\$pkgname-\$pkgver"
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
}
PKG
docker run --rm \
-v ${{ github.workspace }}/arch-build:/build \
archlinux:latest \
bash -c "
pacman -Syu --noconfirm base-devel cargo gcc-libs git && \
useradd -m builduser && \
echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \
chown -R builduser:builduser /build && \
cd /build && \
su builduser -c 'makepkg -s --noconfirm'
"
mkdir -p dist-packages && cp arch-build/*.pkg.tar.zst dist-packages/
# ── Upload Artifacts ──
- name: Upload Package Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: dist-packages/*
if-no-files-found: ignore
# ── Publish Packages to Release (Out of Matrix) ────────────────────────────
publish-dexoron-packages:
needs: [setup, notes, build-linux-packages]
runs-on: ubuntu-latest
steps:
- name: Download Release Notes
uses: actions/download-artifact@v4
with:
name: release-notes
path: .github
- name: Download Built Packages
uses: actions/download-artifact@v4
with:
path: collected-packages
- name: Upload Everything to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.setup.outputs.tag }}
files: |
collected-packages/debian-package/*
collected-packages/rpm-package/*
collected-packages/arch-package/*
collected-packages/snap-package/*
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── macOS Matrix ───────────────────────────────────────────────────────────
build-macos:
if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }}
needs: [setup, notes]
@@ -194,7 +345,7 @@ jobs:
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── BSD ────────────────────────────────────────────────────────────────────
# ── BSD Systems Matrix ─────────────────────────────────────────────────────
build-bsd:
if: ${{ github.event_name == 'push' || github.event.inputs.run_bsd == 'true' }}
needs: [setup, notes]
@@ -202,9 +353,7 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-freebsd
# При необходимости сюда можно докинуть x86_64-unknown-netbsd
system: [freebsd, openbsd, netbsd]
steps:
- uses: actions/checkout@v4
with:
@@ -216,34 +365,72 @@ jobs:
name: release-notes
path: .github
- name: Install cargo-zigbuild
run: |
pip install cargo-zigbuild
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
# --- FREEBSD ---
- name: Build in FreeBSD VM
if: matrix.system == 'freebsd'
uses: vmactions/freebsd-vm@v1
with:
targets: ${{ matrix.target }}
usesh: true
run: |
export PATH="/usr/local/bin:/usr/local/sbin:$PATH"
pkg install -y rust perl5 gmake
cargo build --release
- name: Build
run: cargo zigbuild --release --target ${{ matrix.target }}
- name: Prepare Artifact
- name: Prepare FreeBSD Artifact
if: matrix.system == 'freebsd'
run: |
ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}"
cp target/${{ matrix.target }}/release/dcr "$ASSET"
echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV"
ASSET="dcr-x86_64-unknown-freebsd-${{ needs.setup.outputs.version }}"
cp target/release/dcr "$ASSET"
echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV"
- name: Upload Release Asset
# --- OPENBSD ---
- name: Build in OpenBSD VM
if: matrix.system == 'openbsd'
uses: vmactions/openbsd-vm@v1
with:
usesh: true
run: |
export PATH="/usr/local/bin:$PATH"
pkg_add rust perl gmake
cargo build --release
- name: Prepare OpenBSD Artifact
if: matrix.system == 'openbsd'
run: |
ASSET="dcr-x86_64-unknown-openbsd-${{ needs.setup.outputs.version }}"
cp target/release/dcr "$ASSET"
echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV"
# --- NETBSD ---
- name: Build in NetBSD VM
if: matrix.system == 'netbsd'
uses: vmactions/netbsd-vm@v1
with:
usesh: true
run: |
pkg_add rust perl 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
if: matrix.system == 'netbsd'
run: |
ASSET="dcr-x86_64-unknown-netbsd-${{ needs.setup.outputs.version }}"
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
with:
tag_name: ${{ needs.setup.outputs.tag }}
files: ${{ env.ASSET_NAME }}
files: ${{ env.BSD_ASSET }}
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── Windows ────────────────────────────────────────────────────────────────
# ── Windows Matrix ─────────────────────────────────────────────────────────
build-windows:
if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
needs: [setup, notes]
@@ -288,6 +475,217 @@ jobs:
files: ${{ env.ASSET_NAME }}
body_path: .github/release-notes.md
prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}
# ── Arch User Repository (dcr) ───────────────────────────────────
publish-aur:
if: ${{ (github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') || github.event.inputs.run_aur == 'true' }}
needs: [setup]
runs-on: ubuntu-latest
environment: aur
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive checksum
id: meta
shell: bash
run: |
set -euo pipefail
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/${{ needs.setup.outputs.tag }}.tar.gz"
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
echo "source_url=$SRC_URL" >> "$GITHUB_OUTPUT"
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
- name: Render AUR metadata
shell: bash
run: |
set -euo pipefail
PKGVER='${{ needs.setup.outputs.version }}'
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}
pkgrel=1
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64' 'aarch64')
url="https://github.com/dexoron/dcr"
license=('GPL-3.0-or-later')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
optdepends=(
'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")
sha256sums=('${SHA256}')
build() {
cd "\$srcdir/\$pkgname-\$pkgver"
export CARGO_PROFILE_RELEASE_LTO=false
export RUSTFLAGS="\${RUSTFLAGS:-}"
export RUSTFLAGS="\${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
cargo build --release --locked
}
package() {
cd "\$srcdir/\$pkgname-\$pkgver"
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
}
PKG
cat > packages/aur/dcr/.SRCINFO <<SRC
pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = ${PKGVER}
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
arch = aarch64
license = GPL-3.0-or-later
makedepends = cargo
depends = gcc-libs
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}
sha256sums = ${SHA256}
pkgname = dcr
SRC
- name: Push to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_PACKAGE: dcr
shell: bash
run: |
set -euo pipefail
install -d -m 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
export GIT_SSH_COMMAND='ssh -i ~/.ssh/aur -o IdentitiesOnly=yes'
git clone "ssh://aur@aur.archlinux.org/${AUR_PACKAGE}.git" aur-repo
cp packages/aur/dcr/PKGBUILD aur-repo/PKGBUILD
cp packages/aur/dcr/.SRCINFO aur-repo/.SRCINFO
cd aur-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet; then
echo "No AUR changes"
exit 0
fi
git add PKGBUILD .SRCINFO
git commit -m "dcr ${{ needs.setup.outputs.version }}"
git push
# ── Arch User Repository (dcr-bin) ────────────────────────────────
publish-aur-bin:
if: ${{ (github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') || github.event.inputs.run_aur == 'true' }}
needs: [setup, build-linux]
runs-on: ubuntu-latest
environment: aur
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive checksum
id: meta
shell: bash
run: |
set -euo pipefail
BIN_URL="https://github.com/dexoron/dcr/releases/download/${{ needs.setup.outputs.tag }}/dcr-x86_64-unknown-linux-musl-${{ needs.setup.outputs.version }}"
SHA256="$(curl -fsSL "$BIN_URL" | sha256sum | awk '{print $1}')"
echo "bin_url=$BIN_URL" >> "$GITHUB_OUTPUT"
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
- name: Render AUR Metadata
shell: bash
run: |
set -euo pipefail
PKGVER='${{ needs.setup.outputs.version }}'
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}
pkgrel=1
pkgdesc="Cargo-like utility to manage C/C++ projects (pre-compiled musl binary)"
arch=('x86_64')
url="https://github.com/dexoron/dcr"
license=('GPL-3.0-or-later')
provides=('dcr')
conflicts=('dcr')
optdepends=(
'gcc: build C/C++ projects with GCC'
'clang: build C/C++ projects with Clang'
)
source_x86_64=("dcr-\$pkgver::${BIN_URL}")
sha256sums_x86_64=('${SHA256}')
package() {
install -Dm755 "\$srcdir/dcr-\$pkgver" "\$pkgdir/usr/bin/dcr"
}
PKG
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}
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
license = GPL-3.0-or-later
provides = dcr
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}
sha256sums_x86_64 = ${SHA256}
pkgname = dcr-bin
SRC
- name: Push to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_PACKAGE: dcr-bin
shell: bash
run: |
set -euo pipefail
install -d -m 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
export GIT_SSH_COMMAND='ssh -i ~/.ssh/aur -o IdentitiesOnly=yes'
git clone "ssh://aur@aur.archlinux.org/${AUR_PACKAGE}.git" aur-repo
cp packages/aur/dcr-bin/PKGBUILD aur-repo/PKGBUILD
cp packages/aur/dcr-bin/.SRCINFO aur-repo/.SRCINFO
cd aur-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet; then
echo "No AUR changes"
exit 0
fi
git add PKGBUILD .SRCINFO
git commit -m "dcr-bin ${{ needs.setup.outputs.version }}"
git push
# ── crates.io ──────────────────────────────────────────────────────────────
publish-crates:
@@ -296,6 +694,9 @@ jobs:
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
needs: [setup, build-linux, build-macos, build-windows, build-bsd]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
@@ -304,7 +705,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish
# ── Homebrew tap ───────────────────────────────────────────────────────────
publish-brew:

View File

@@ -1,5 +1,52 @@
# Changelog
## [0.6.9] - 2026-05-28
Added:
- **Dexoron Packages Index** — new standalone CI workflow (`.github/workflows/dexoron-packages-index.yml`)
that automatically updates the `dexoron/packages` repository indexes after a release.
Supports Arch Linux (`repo-add`), Debian (`dpkg-scanpackages`), and Fedora (`createrepo_c`).
Triggered by `workflow_dispatch` or `workflow_run` on the Release workflow.
- **Arch Linux native package** (`.pkg.tar.zst`) build inside the release workflow —
builds from source in an `archlinux:latest` Docker container using a dynamically
generated PKGBUILD.
- **AUR publish support** — new `run_aur` workflow dispatch input and `publish-aur` /
`publish-aur-bin` jobs that push PKGBUILD / `.SRCINFO` to `aur.archlinux.org`.
Changed:
- **Release workflow refactored** — Linux packages (deb, rpm, snap, arch) and BSD
builds now use `strategy.matrix` instead of separate jobs. A new
`publish-dexoron-packages` job collects all package artifacts and uploads them
to the GitHub Release in one step. Removed standalone `build-arch-package`,
`build-snap`, `build-deb-rpm` jobs. The old `run_arch_native` input is now
`run_linux_packages`.
- **Dexoron Packages Index workflow** extracted from `release.yml` into its own
file with support for Debian and RPM repository indexes in addition to Arch.
- **git2 vendoring** — enabled `vendored-libgit2` and `vendored-openssl` features
for reproducible CI builds without system libraries.
- **cargo-generate-rpm metadata** — updated to the current `package.metadata.rpm`
format (uses `files` table instead of `assets` array).
Fixed:
- **`--target=<triple>` no longer passed to GCC** — the flag is now only injected into
cflags when the resolved compiler name contains `"clang"`. GCC rejects this flag
with `unrecognized command-line option`.
- **Workspace clean test** — removed assertions for member-local `target/`
directories. Workspace members share the root `target/` directory, so their
individual `src/<name>/target/` is never created.
- **Release build test path** — corrected expected directory from `target/release`
to `target/x86_64-unknown-linux-gnu/release`.
## [0.6.8] - 2026-05-27
Added:

2
Cargo.lock generated
View File

@@ -112,7 +112,7 @@ dependencies = [
[[package]]
name = "dcr"
version = "0.6.8"
version = "0.6.9"
dependencies = [
"ctrlc",
"git2",

View File

@@ -1,12 +1,38 @@
[package]
name = "dcr"
version = "0.6.8"
version = "0.6.9"
edition = "2024"
build = "build.rs"
description = "DCR is a utility for managing C/C++ projects in a Cargo-like style."
license = "GPL-3.0-or-later"
repository = "https://github.com/dexoron/dcr"
[package.metadata.binstall]
pkg-fmt = "bin"
pkg-url = "{ repo }/releases/download/v{ version }/dcr-{ target }-{ version }{ bin-suffix }"
[package.metadata.deb]
maintainer = "Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>"
copyright = "2026, Dexoron"
extended-description = """
Cargo-like utility to manage C/C++ projects.
"""
section = "devel"
priority = "optional"
assets = [
["target/release/dcr", "usr/bin/dcr", "755"],
]
[package.metadata.generate-rpm]
summary = "Cargo-like utility to manage C/C++ projects"
description = """
Cargo-like utility to manage C/C++ projects.
"""
assets = [
{ source = "target/release/dcr", dest = "/usr/bin/dcr", mode = "755" }
]
[dependencies]
glob = "0.3"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
@@ -14,9 +40,9 @@ serde = { version = "1", features = ["derive"] }
self-replace = "1"
sha2 = "0.10"
toml = "1.0.3"
git2 = "0.20.4"
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"] }
openssl = { version = "0.10", features = ["vendored"] }

View File

@@ -61,7 +61,7 @@ select_channel() {
echo "Choose channel:"
echo " 1) Latest stable release (default)"
echo " 2) Latest dev (pre-release)"
read -r -p "Enter 1 or 2 [1]: " choice
read -r -p "Enter 1 or 2 [1]: " choice < /dev/tty
case "${choice:-1}" in
1) CHANNEL="stable" ;;
@@ -74,7 +74,7 @@ select_install_mode() {
echo "Choose installation mode:"
echo " 1) Download prebuilt binary from GitHub Release (recommended)"
echo " 2) Build from git"
read -r -p "Enter 1 or 2 [1]: " choice
read -r -p "Enter 1 or 2 [1]: " choice < /dev/tty
case "${choice:-1}" in
1) INSTALL_MODE="release" ;;
@@ -215,4 +215,4 @@ main() {
log "Log saved to $LOGFILE"
}
main "$@"
main "$@"

1
packaging/aur/dcr-git Submodule

Submodule packaging/aur/dcr-git added at 12038a613e

View File

@@ -0,0 +1,18 @@
name: dcr
base: core22
version: '0.6.9'
summary: Cargo-like utility to manage C/C++ projects
description: |
A build tool and package manager for C/C++ projects inspired by Rust's Cargo.
grade: stable
confinement: classic
apps:
dcr:
command: bin/dcr
parts:
dcr:
plugin: dump
source: dist/

View File

@@ -69,7 +69,9 @@ pub fn build(args: &[String]) -> i32 {
if flags.target.is_none() {
let config_path = root.join("dcr.toml");
if let Ok(config) = Config::open(config_path.to_str().unwrap()) {
if let Ok(config) = Config::open(config_path.to_str().unwrap())
&& !config.is_workspace_only()
{
let bt = get_build_string_with_profile(&config, "target", "debug");
if !bt.is_empty() {
flags.target = Some(bt);
@@ -100,6 +102,7 @@ pub fn build(args: &[String]) -> i32 {
flags.target.as_deref(),
flags.force,
flags.verbose,
flags.workspace.as_deref(),
)
}) {
Ok(()) => 0,
@@ -372,8 +375,13 @@ fn get_build_steps_with_profile(
get_build_steps(config, &format!("build.{field}"))
}
fn ensure_target_dirs(items: &[String], profile: &str, target_dir: Option<String>) {
if !items.contains(&"target".to_string()) {
fn ensure_target_dirs(
items: &[String],
profile: &str,
target_dir: Option<String>,
skip_local_target: bool,
) {
if !skip_local_target && !items.contains(&"target".to_string()) {
let _ = fs::create_dir("./target");
}
if let Some(dir) = &target_dir {
@@ -414,8 +422,38 @@ fn build_from_root(
target: Option<&str>,
force: bool,
verbose: bool,
workspace: Option<&str>,
) -> Result<(), String> {
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
if config.is_workspace_only() {
let ws = parse_workspace(&config, profile, target, root)?
.ok_or_else(|| "Workspace root has no members defined".to_string())?;
let members: Vec<_> = if let Some(filter_name) = workspace {
ws.members
.into_iter()
.filter(|m| m.name == filter_name)
.collect()
} else {
ws.members
};
if members.is_empty() {
return Err("No matching workspace members to build".to_string());
}
for member in &members {
build_project_at(
&member.path,
profile,
target,
&[],
force,
verbose,
Some(root),
)?;
}
return Ok(());
}
let project_name = get_config_str(&config, "package.name");
let project_version = get_config_str(&config, "package.version");
@@ -454,20 +492,50 @@ fn build_from_root(
)
);
}
if let Some(workspace) = parse_workspace(&config, profile, build_target.as_deref(), root)? {
build_workspace(&workspace, profile, build_target.as_deref(), force, verbose)?;
let excludes: Vec<std::path::PathBuf> =
workspace.members.iter().map(|m| m.path.clone()).collect();
if let Some(ws) = parse_workspace(&config, profile, build_target.as_deref(), root)? {
if let Some(filter_name) = workspace {
for member in ws.members.iter().filter(|m| m.name == filter_name) {
build_project_at(
&member.path,
profile,
build_target.as_deref(),
&[],
force,
verbose,
Some(root),
)?;
}
} else {
build_workspace(
&ws,
profile,
build_target.as_deref(),
force,
verbose,
Some(root),
)?;
let excludes: Vec<std::path::PathBuf> =
ws.members.iter().map(|m| m.path.clone()).collect();
build_project_at(
root,
profile,
build_target.as_deref(),
&excludes,
force,
verbose,
None,
)?;
}
} else {
build_project_at(
root,
profile,
build_target.as_deref(),
&excludes,
&[],
force,
verbose,
None,
)?;
} else {
build_project_at(root, profile, build_target.as_deref(), &[], force, verbose)?;
}
}
let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
@@ -485,9 +553,18 @@ fn build_workspace(
target: Option<&str>,
force: bool,
verbose: bool,
workspace_root: Option<&Path>,
) -> Result<(), String> {
for member in &workspace.members {
build_project_at(&member.path, profile, target, &[], force, verbose)?;
build_project_at(
&member.path,
profile,
target,
&[],
force,
verbose,
workspace_root,
)?;
}
Ok(())
}
@@ -499,6 +576,7 @@ fn build_project_at(
exclude_dirs: &[std::path::PathBuf],
force: bool,
verbose: bool,
workspace_root: Option<&Path>,
) -> Result<(), String> {
with_dir(project_root, || {
check_interrupted()?;
@@ -506,7 +584,22 @@ fn build_project_at(
if !items.contains(&"dcr.toml".to_string()) {
return Err("dcr.toml file not found".to_string());
}
let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
let mut config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
// Inherit build fields from workspace root if configured
if let Some(root) = workspace_root {
let has_inherit = config
.get("build.inherit")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if has_inherit {
let root_path = root.join("dcr.toml");
if root_path.is_file() {
let parent_config =
Config::open(&root_path.to_string_lossy()).map_err(|e| e.to_string())?;
config.merge_parent(&parent_config);
}
}
}
let project_name = get_config_str(&config, "package.name");
let project_version = get_config_str(&config, "package.version");
let build_target_config = get_build_string_with_profile(&config, "target", profile);
@@ -548,17 +641,6 @@ fn build_project_at(
.or_else(|| get_config_opt(&config, "toolchain.rcc"));
let mut build_cflags =
get_list_with_profile_and_target(&config, "cflags", profile, build_target)?;
if let Some(t) = build_target
&& !t.trim().is_empty()
{
let target_flag = format!("--target={}", t.trim());
if !build_cflags
.iter()
.any(|f| f == &target_flag || f.starts_with("--target="))
{
build_cflags.insert(0, target_flag);
}
}
let mut build_ldflags =
get_list_with_profile_and_target(&config, "ldflags", profile, build_target)?;
let build_ldscript =
@@ -595,11 +677,48 @@ fn build_project_at(
tc_cxx.as_deref(),
tc_as.as_deref(),
);
if let Some(t) = build_target
&& !t.trim().is_empty()
&& resolved_compiler.to_lowercase().contains("clang")
{
let target_flag = format!("--target={}", t.trim());
if !build_cflags
.iter()
.any(|f| f == &target_flag || f.starts_with("--target="))
{
build_cflags.insert(0, target_flag);
}
}
let resolved_linker = resolve_tool("DCR_LD", tc_ld.as_deref());
let resolved_archiver = resolve_tool("DCR_AR", tc_ar.as_deref());
let target_dir = build_target.map(|t| format!("target/{t}/{profile}"));
ensure_target_dirs(&items, profile, target_dir);
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()
}
None => normalize_target(build_target.unwrap_or(""), profile).unwrap_or_default(),
};
let target_dir = if target_dir_binding.is_empty() {
None
} else {
Some(target_dir_binding.clone())
};
ensure_target_dirs(&items, profile, target_dir, workspace_root.is_some());
let deps_table = config.get("dependencies").and_then(|v| v.as_table());
let resolved = resolve_deps(&config, profile, build_target, project_root)?;
@@ -635,7 +754,15 @@ fn build_project_at(
dep_root.display()
));
}
build_project_at(&dep_root, profile, build_target, &[], force, verbose)?;
build_project_at(
&dep_root,
profile,
build_target,
&[],
force,
verbose,
None,
)?;
print!(
"\r{:100}\r {} {} v{}",
"",
@@ -724,7 +851,6 @@ fn build_project_at(
}
}
let target_dir_binding = normalize_target(build_target.unwrap_or(""), profile);
let version_info = parse_version_info(&project_version);
let substitute = |s: &str| -> String {
s.replace("{profile}", profile)
@@ -749,7 +875,11 @@ fn build_project_at(
standard: &build_standard,
cxx_standard: &build_cxx_standard,
target: build_target,
target_dir: target_dir_binding.as_deref(),
target_dir: if target_dir_binding.is_empty() {
None
} else {
Some(target_dir_binding.as_str())
},
kind: normalize_kind(&build_kind),
platform: normalize_platform(&build_platform),
linker: resolved_linker.as_deref(),

View File

@@ -21,6 +21,7 @@ use crate::utils::log::warn;
pub struct BuildRunFlags {
pub profile: String,
pub target: Option<String>,
pub workspace: Option<String>,
pub force: bool,
pub clean: bool,
pub verbose: bool,
@@ -29,6 +30,7 @@ pub struct BuildRunFlags {
pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
let mut profile = PROFILE.to_string();
let mut target = None;
let mut workspace = None;
let mut force = false;
let mut clean = false;
let mut verbose = false;
@@ -52,6 +54,15 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
verbose = true;
continue;
}
if candidate == "workspace" {
if let Some(w) = iter.next() {
workspace = Some(w.clone());
} else {
warn("--workspace requires a value");
return Err(1);
}
continue;
}
if candidate == "target" {
if let Some(t) = iter.next() {
target = Some(t.clone());
@@ -76,6 +87,7 @@ pub fn parse_build_run_flags(args: &[String]) -> Result<BuildRunFlags, i32> {
Ok(BuildRunFlags {
profile,
target,
workspace,
force,
clean,
verbose,

View File

@@ -24,6 +24,7 @@ use crate::utils::fs::find_project_root;
use crate::utils::fs::with_dir;
use crate::utils::log::error;
use crate::utils::text::{BOLD_GREEN, colored};
use std::path::Path;
use std::process::Command;
fn get_run_cmd(
@@ -82,18 +83,82 @@ pub fn run(args: &[String]) -> i32 {
return 1;
}
};
let flags = match parse_build_run_flags(args) {
Ok(v) => v,
Err(_) => return 1,
};
// Handle workspace-only root: delegate to workspace member
if config.is_workspace_only() {
let ws = match crate::core::workspace::parse_workspace(
&config,
&flags.profile,
flags.target.as_deref(),
&root,
) {
Ok(Some(ws)) => ws,
Ok(None) => {
error("Workspace root has no members defined");
return 1;
}
Err(e) => {
error(&e);
return 1;
}
};
let member = match &flags.workspace {
Some(name) => ws.members.iter().find(|m| m.name == *name),
None => ws.main_member(),
};
let member = match member {
Some(m) => m,
None => {
if let Some(name) = &flags.workspace {
error(&format!("Workspace member '{name}' not found"));
} else {
error("No workspace member to run (set `main = true` on one member)");
}
return 1;
}
};
// Build and run from the member's directory
return match with_dir(&member.path, || {
run_project(&member.path, &flags, Some(root.as_path()))
}) {
Ok(code) => code,
Err(e) => {
error(&e);
1
}
};
}
match run_project(&root, &flags, None) {
Ok(code) => code,
Err(e) => {
error(&e);
1
}
}
}
fn run_project(
root: &Path,
flags: &crate::cli::flags::BuildRunFlags,
workspace_root: Option<&Path>,
) -> Result<i32, String> {
let config =
Config::open(root.join("dcr.toml").to_str().unwrap()).map_err(|err| err.to_string())?;
let project_name: &str = config
.get("package.name")
.and_then(|v| v.as_str())
.unwrap_or("");
let mut flags = match parse_build_run_flags(args) {
Ok(v) => v,
Err(_) => return 1,
};
// If no target specified, use default host target for target-specific config
if flags.target.is_none() {
let mut target = flags.target.clone();
if target.is_none() {
let default_target = if cfg!(target_os = "linux") {
"x86_64-unknown-linux-gnu"
} else if cfg!(target_os = "macos") {
@@ -103,7 +168,7 @@ pub fn run(args: &[String]) -> i32 {
} else {
"unknown"
};
flags.target = Some(default_target.to_string());
target = Some(default_target.to_string());
}
let build_kind = config
@@ -112,25 +177,30 @@ pub fn run(args: &[String]) -> i32 {
.or_else(|| config.get("build.kind").and_then(|v| v.as_str()))
.unwrap_or("");
let normalized_target_dir = flags
.target
.as_ref()
.and_then(|t| crate::cli::build::normalize_target(t, &flags.profile));
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 version = config
.get("package.version")
.and_then(|v| v.as_str())
.unwrap_or("");
let run_cmd = get_run_cmd(&config, &flags.profile, flags.target.as_deref(), version);
let run_cmd = get_run_cmd(&config, &flags.profile, target.as_deref(), version);
let kind = build_kind.trim();
if run_cmd.is_none()
&& (kind == "staticlib" || kind == "sharedlib" || kind == "efi" || kind == "elf")
{
error("Cannot run library build");
return 1;
return Err("Cannot run library build".to_string());
}
let build_status = build(args);
let build_status = build(&args_for_build(flags));
let bin_path = crate::platform::bin_path(
&flags.profile,
project_name,
@@ -140,15 +210,15 @@ pub fn run(args: &[String]) -> i32 {
if let Some(cmd) = run_cmd {
println!("\n {} {}", colored("Running", BOLD_GREEN), cmd);
println!("--------------------------------");
return run_shell(&cmd);
return Ok(run_shell(&cmd));
}
println!("\n {} {}", colored("Running", BOLD_GREEN), bin_path);
println!("--------------------------------");
return run_binary(
return Ok(run_binary(
project_name,
&flags.profile,
normalized_target_dir.as_deref(),
);
));
}
let fallback_code = if let Some(cmd) = run_cmd {
@@ -161,11 +231,27 @@ pub fn run(args: &[String]) -> i32 {
)
};
if fallback_code != 1 {
return fallback_code;
return Ok(fallback_code);
}
error("Fix errors in the code to run the project");
1
Err("Fix errors in the code to run the project".to_string())
}
fn args_for_build(flags: &crate::cli::flags::BuildRunFlags) -> Vec<String> {
let mut args = Vec::new();
args.push(format!("--{}", flags.profile));
if let Some(ref target) = flags.target {
args.push("--target".to_string());
args.push(target.clone());
}
if let Some(ref name) = flags.workspace {
args.push("--workspace".to_string());
args.push(name.clone());
}
if flags.verbose {
args.push("--verbose".to_string());
}
args
}
fn run_shell(cmd: &str) -> i32 {

View File

@@ -77,8 +77,10 @@ pub struct Config {
#[allow(dead_code)]
#[derive(Debug, Clone, Deserialize)]
pub struct DcrConfig {
pub package: PackageConfig,
pub build: BuildConfig,
#[serde(default)]
pub package: Option<PackageConfig>,
#[serde(default)]
pub build: Option<BuildConfig>,
#[serde(default)]
pub dependencies: BTreeMap<String, DependencyConfig>,
#[serde(default)]
@@ -123,6 +125,8 @@ pub struct BuildConfig {
pub clean: Vec<String>,
#[serde(default)]
pub src_disable: Option<bool>,
#[serde(default)]
pub workspace_only: bool,
}
#[derive(Debug, Clone, Deserialize)]
@@ -201,13 +205,100 @@ impl Config {
}
#[allow(dead_code)]
pub fn package(&self) -> &PackageConfig {
&self.typed.package
pub fn package(&self) -> Option<&PackageConfig> {
self.typed.package.as_ref()
}
#[allow(dead_code)]
pub fn build_config(&self) -> &BuildConfig {
&self.typed.build
pub fn build_config(&self) -> Option<&BuildConfig> {
self.typed.build.as_ref()
}
pub fn is_workspace_only(&self) -> bool {
self.typed
.build
.as_ref()
.map(|b| b.workspace_only)
.unwrap_or(false)
&& !self.typed.workspace.is_empty()
}
/// Merge build fields from parent config into self where self has empty/default values.
/// Used for workspace member inheritance (build.inherit = true).
pub fn merge_parent(&mut self, parent: &Config) {
let fields: &[&str] = &[
"language",
"standard",
"cxx_standard",
"compiler",
"kind",
"target",
"platform",
"cflags",
"ldflags",
"exclude",
"include",
"roots",
"clean",
"src_disable",
];
let parent_build = match parent.data.get("build").and_then(|v| v.as_table()) {
Some(t) => t.clone(),
None => return,
};
let self_build = self
.data
.get("build")
.and_then(|v| v.as_table())
.cloned()
.unwrap_or_default();
let mut changes: Vec<(String, toml::Value)> = Vec::new();
for field in fields {
let val = match parent_build.get(*field) {
Some(v) => v,
None => continue,
};
let self_val = self_build.get(*field);
let should_inherit = match val {
toml::Value::Array(_) => self_val.is_none(),
toml::Value::String(_) => self_val
.and_then(|v| v.as_str())
.map(|s| s.trim().is_empty())
.unwrap_or(true),
_ => self_val.is_none(),
};
if should_inherit {
changes.push((field.to_string(), val.clone()));
}
}
if changes.is_empty() {
return;
}
if !self.data.is_table() {
let mut m = Map::new();
m.insert("build".to_string(), toml::Value::Table(Map::new()));
self.data = toml::Value::Table(m);
}
let tbl = self.data.as_table_mut().unwrap();
let build_tbl = tbl
.entry("build".to_string())
.or_insert_with(|| toml::Value::Table(Map::new()));
if let Some(build_tbl) = build_tbl.as_table_mut() {
for (key, val) in changes {
build_tbl.entry(key).or_insert(val);
}
}
if let Ok(typed) = parse_typed_config(&self.data) {
self.typed = typed;
}
}
pub fn get(&self, key: &str) -> Option<&Value> {
@@ -229,15 +320,29 @@ impl Config {
}
pub fn validate(&self) -> Result<(), ConfigError> {
if self.typed.package.name.trim().is_empty() {
if self.is_workspace_only() {
return self.validate_workspace();
}
let pkg =
self.typed.package.as_ref().ok_or_else(|| {
ConfigError::Invalid("missing [package] section (required)".into())
})?;
let build = self
.typed
.build
.as_ref()
.ok_or_else(|| ConfigError::Invalid("missing [build] section (required)".into()))?;
if pkg.name.trim().is_empty() {
return Err(ConfigError::Invalid("package.name is empty".into()));
}
if self.typed.package.version.trim().is_empty() {
if pkg.version.trim().is_empty() {
return Err(ConfigError::Invalid("package.version is empty".into()));
}
validate_package_name(&self.typed.package.name)?;
validate_package_name(&pkg.name)?;
if let Some(pkg_type) = &self.typed.package.pkg_type {
if let Some(pkg_type) = &pkg.pkg_type {
let pkg_type = pkg_type.trim();
if !pkg_type.is_empty() && pkg_type != "lib" && pkg_type != "app" && pkg_type != "none"
{
@@ -247,32 +352,32 @@ impl Config {
}
}
validate_language_config(&self.typed.build.language, "build.language")?;
if self.typed.build.standard.trim().is_empty() {
validate_language_config(&build.language, "build.language")?;
if build.standard.trim().is_empty() {
return Err(ConfigError::Invalid("build.standard is empty".into()));
}
if self.typed.build.compiler.trim().is_empty() {
if build.compiler.trim().is_empty() {
return Err(ConfigError::Invalid("build.compiler is empty".into()));
}
if let Some(platform) = &self.typed.build.platform
if let Some(platform) = &build.platform
&& platform.trim().is_empty()
{
return Err(ConfigError::Invalid("build.platform is empty".into()));
}
validate_toolchain(self.typed.toolchain.as_ref())?;
if let Some(kind) = &self.typed.build.kind {
if let Some(kind) = &build.kind {
let kind = kind.trim();
if !kind.is_empty() && !VALID_KINDS.contains(&kind) {
return Err(ConfigError::Invalid("build.kind is invalid".into()));
}
}
validate_string_list(&self.typed.build.exclude, "build.exclude")?;
validate_string_list(&self.typed.build.include, "build.include")?;
validate_string_list(&self.typed.build.roots, "build.roots")?;
validate_string_list(&self.typed.build.clean, "build.clean")?;
validate_string_list(&self.typed.build.cflags, "build.cflags")?;
validate_string_list(&self.typed.build.ldflags, "build.ldflags")?;
if let Some(target) = &self.typed.build.target {
validate_string_list(&build.exclude, "build.exclude")?;
validate_string_list(&build.include, "build.include")?;
validate_string_list(&build.roots, "build.roots")?;
validate_string_list(&build.clean, "build.clean")?;
validate_string_list(&build.cflags, "build.cflags")?;
validate_string_list(&build.ldflags, "build.ldflags")?;
if let Some(target) = &build.target {
validate_non_empty_string(target, "build.target")?;
}
for profile in ["release", "debug"] {
@@ -824,10 +929,10 @@ mod tests {
"[package]\nname = \"typed\"\nversion = \"1.2.3\"\ntype = \"lib\"\n\n[build]\nlanguage = [\"c\", \"c++\"]\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"staticlib\"\ncflags = [\"-Wall\"]\n\n[dependencies]\nfoo = \"1.0.0\"\n",
);
let config = Config::open(&path.to_string_lossy()).unwrap();
assert_eq!(config.package().name, "typed");
assert_eq!(config.typed().package.version, "1.2.3");
assert_eq!(config.build_config().compiler, "clang");
assert_eq!(config.build_config().cflags, ["-Wall"]);
assert_eq!(config.package().unwrap().name, "typed");
assert_eq!(config.typed().package.as_ref().unwrap().version, "1.2.3");
assert_eq!(config.build_config().unwrap().compiler, "clang");
assert_eq!(config.build_config().unwrap().cflags, ["-Wall"]);
assert!(config.typed().dependencies.contains_key("foo"));
}

View File

@@ -26,6 +26,7 @@ pub struct WorkspaceMember {
pub name: String,
pub path: PathBuf,
pub deps: Vec<String>,
pub main: bool,
}
#[derive(Debug, Clone)]
@@ -33,6 +34,15 @@ pub struct Workspace {
pub members: Vec<WorkspaceMember>,
}
impl Workspace {
pub fn main_member(&self) -> Option<&WorkspaceMember> {
self.members
.iter()
.find(|m| m.main)
.or_else(|| self.members.first())
}
}
pub fn parse_workspace(
config: &Config,
profile: &str,
@@ -85,6 +95,7 @@ pub fn parse_workspace(
name: name.to_string(),
path,
deps,
main: tbl.get("main").and_then(|v| v.as_bool()).unwrap_or(false),
});
}

View File

@@ -198,18 +198,6 @@ fn workspace_build_and_clean_all() {
"workspace clean --all --release should succeed"
);
for (name, _) in &members {
let member = root.join("src").join(name);
let target_dir = "target/x86_64-unknown-linux-gnu";
assert!(
!member.join(target_dir).join("release").exists(),
"member target/x86_64-unknown-linux-gnu/release should be removed"
);
assert!(
member.join(target_dir).join("debug").exists(),
"member target/x86_64-unknown-linux-gnu/debug should remain"
);
}
let target_dir = "target/x86_64-unknown-linux-gnu";
assert!(
!root.join(target_dir).join("release").exists(),

View File

@@ -280,8 +280,11 @@ fn build_release_profile() {
let out = run_dcr_env(&["build", "--release"], &dir, &envs);
assert!(out.status.success(), "release build should succeed");
assert!(
dir.join("target").join("release").is_dir(),
"target/release should exist"
dir.join("target")
.join("x86_64-unknown-linux-gnu")
.join("release")
.is_dir(),
"target/x86_64-unknown-linux-gnu/release should exist"
);
}