dcr 0.6.7

DCR is a utility for managing C/C++ projects in a Cargo-like style.
name: Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to build (e.g. v0.6.7)"
        required: true
      run_linux:
        description: "Build Linux"
        type: boolean
        default: true
      run_macos:
        description: "Build macOS"
        type: boolean
        default: true
      run_windows:
        description: "Build Windows"
        type: boolean
        default: true
      run_publish:
        description: "Publish to crates.io"
        type: boolean
        default: false

permissions:
  contents: write

jobs:
  # ── Resolve Tag ────────────────────────────────────────────────────────────
  setup:
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.tag.outputs.value }}
      version: ${{ steps.tag.outputs.version }}
      prerelease: ${{ steps.tag.outputs.prerelease }}
    steps:
      - name: Resolve tag
        id: tag
        run: |
          if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            TAG="${{ github.event.inputs.tag }}"
          else
            TAG="${GITHUB_REF_NAME}"
          fi
          VERSION="${TAG#v}"
          [[ "$TAG" == *"-"* ]] && PRERELEASE="true" || PRERELEASE="false"
          echo "value=${TAG}"         >> "$GITHUB_OUTPUT"
          echo "version=${VERSION}"   >> "$GITHUB_OUTPUT"
          echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT"

  # ── Release Notes ──────────────────────────────────────────────────────────
  notes:
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.setup.outputs.tag }}

      - name: Prepare Release Notes
        shell: bash
        run: |
          set -euo pipefail
          tag="${{ needs.setup.outputs.version }}"
          notes_file=".github/release-notes.md"
          if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then
            awk -v ver="$tag" '
              $0 ~ "^## \\[" ver "\\]" {found=1; next}
              found && $0 ~ "^## \\[" {exit}
              found {print}
            ' CHANGELOG.md | sed -e '${/^$/d;}' > "$notes_file"
          else
            echo "No CHANGELOG section for $tag." > "$notes_file"
            git log -1 --pretty=format:"%s%n%n%b" >> "$notes_file"
          fi

      - name: Upload Notes Artifact
        uses: actions/upload-artifact@v4
        with:
          name: release-notes
          path: .github/release-notes.md

  # ── Linux ──────────────────────────────────────────────────────────────────
  build-linux:
    if: ${{ github.event_name == 'push' || github.event.inputs.run_linux == 'true' }}
    needs: [setup, notes]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
          - target: aarch64-unknown-linux-gnu
          - target: x86_64-unknown-linux-musl
          - target: aarch64-unknown-linux-musl
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.setup.outputs.tag }}

      - name: Download Release Notes
        uses: actions/download-artifact@v4
        with:
          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
        with:
          targets: ${{ matrix.target }}

      - name: Build
        run: |
          if [ "${{ matrix.target }}" != "x86_64-unknown-linux-gnu" ]; then
            cargo zigbuild --release --target ${{ matrix.target }}
          else
            cargo build --release --target ${{ matrix.target }}
          fi

      - name: Prepare Artifact
        run: |
          ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}"
          cp target/${{ matrix.target }}/release/dcr "$ASSET"
          echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV"

      - name: Upload Release Asset
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.setup.outputs.tag }}
          files: ${{ env.ASSET_NAME }}
          body_path: .github/release-notes.md
          prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}

  # ── macOS ──────────────────────────────────────────────────────────────────
  build-macos:
    if: ${{ github.event_name == 'push' || github.event.inputs.run_macos == 'true' }}
    needs: [setup, notes]
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-15-intel
            target: x86_64-apple-darwin
          - os: macos-latest
            target: aarch64-apple-darwin
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.setup.outputs.tag }}

      - name: Download Release Notes
        uses: actions/download-artifact@v4
        with:
          name: release-notes
          path: .github

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Prepare Artifact
        run: |
          ASSET="dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}"
          cp target/${{ matrix.target }}/release/dcr "$ASSET"
          echo "ASSET_NAME=${ASSET}" >> "$GITHUB_ENV"

      - name: Upload Release Asset
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.setup.outputs.tag }}
          files: ${{ env.ASSET_NAME }}
          body_path: .github/release-notes.md
          prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}

  # ── Windows ────────────────────────────────────────────────────────────────
  build-windows:
    if: ${{ github.event_name == 'push' || github.event.inputs.run_windows == 'true' }}
    needs: [setup, notes]
    runs-on: windows-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-pc-windows-msvc
          - target: aarch64-pc-windows-msvc
          - target: x86_64-pc-windows-gnu
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.setup.outputs.tag }}

      - name: Download Release Notes
        uses: actions/download-artifact@v4
        with:
          name: release-notes
          path: .github

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Prepare Artifact
        shell: pwsh
        run: |
          $asset = "dcr-${{ matrix.target }}-${{ needs.setup.outputs.version }}.exe"
          Copy-Item "target/${{ matrix.target }}/release/dcr.exe" "$asset"
          "ASSET_NAME=$asset" >> $env:GITHUB_ENV

      - name: Upload Release Asset
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.setup.outputs.tag }}
          files: ${{ env.ASSET_NAME }}
          body_path: .github/release-notes.md
          prerelease: ${{ needs.setup.outputs.prerelease == 'true' }}

  # ── crates.io ──────────────────────────────────────────────────────────────
  publish-crates:
    if: |
      (github.event_name == 'push' && needs.setup.outputs.prerelease == 'false') ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
    needs: [setup, build-linux, build-macos, build-windows]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.setup.outputs.tag }}

      - uses: dtolnay/rust-toolchain@stable

      - name: Publish to crates.io
        run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}